Compare commits

...
Author SHA1 Message Date
dev60-review bdaa8717bd feat(plugins): give the openrig plugin a PNG icon for Claude's directory
Claude's plugin directory takes a square PNG or JPEG icon of 512 to
2048 px at .claude-plugin/icon.png, not SVG, and uses it as the
listing icon only the first time a plugin is saved or submitted. The
openrig plugin had only assets/logo.svg. This adds that mark rendered
at 1024x1024 (RGBA PNG, 53,823 bytes).
2026-11-12 01:31:02 +00:00
dev60-review b68742c3c2 fix(plugins): give each plugin manifest an https repository URL
Claude's plugin directory doesn't accept the github: shorthand in a
plugin's repository field (LISTING_URL_SCHEME) and drops it from the
listing preview. The five manifests of the openrig and openrig-core
plugins (Claude, Codex and the shared plugin.json) now carry
https://github.com/... URLs for the same repositories.
2026-11-12 01:23:59 +00:00
Mike Schwarz 80cfb7d01a fix(cli,daemon): an adopted session's route says to end its tmux session; rig seat stop's help matches (#3006)
An adopted session's recovery route told people to close its terminal before relaunching, but closing a viewer leaves the tmux session and its name alive, so the relaunch could still collide with it. The route now says to end the underlying tmux session, and rig seat stop's help says the same. Guidance and help text only, with the test pins updated.
2026-11-11 17:18:51 -08:00
12 changed files with 36 additions and 19 deletions

No files matched your search

+4 -3
View File
@@ -1493,9 +1493,10 @@ Examples:
.addHelpText("after", `
Siblings are untouched. A dead seat is refused (use rig seat clean). An adopted
session is refused: to end only it, run rig unclaim <session> (it releases the
session and leaves its process running), then end that process in its own
terminal. rig down <rig> ends every seat in its rig, so it is the route only
for a one-seat rig.
session and leaves its process running), then end that process and the
underlying tmux session, including any remaining shell. Closing only its
viewing window leaves the tmux session running. rig down <rig> leaves a
session OpenRig didn't start running.
A seat handed over (fresh, fork or rebuild) before OpenRig 0.7.9 still reads
adopted and is refused too, until its next launched handover or managed launch;
the refusal says so. Stopping ends that agent's session and any work in progress:
@@ -7,7 +7,7 @@
"url": "https://github.com/mvschwarz"
},
"homepage": "https://github.com/mvschwarz/openrig-plugins",
"repository": "github:mvschwarz/openrig-plugins",
"repository": "https://github.com/mvschwarz/openrig-plugins",
"license": "Apache-2.0",
"keywords": ["openrig", "agent", "topology", "rig", "continuity", "coordination", "compaction", "handoff"],
"skills": "./skills",
@@ -7,7 +7,7 @@
"url": "https://github.com/mvschwarz"
},
"homepage": "https://github.com/mvschwarz/openrig-plugins",
"repository": "github:mvschwarz/openrig-plugins",
"repository": "https://github.com/mvschwarz/openrig-plugins",
"license": "Apache-2.0",
"keywords": ["openrig", "agent", "topology", "rig", "continuity", "coordination", "compaction", "handoff"],
"skills": "./skills",
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@@ -7,7 +7,7 @@
"url": "https://github.com/mvschwarz"
},
"homepage": "https://github.com/mvschwarz/openrig",
"repository": "github:mvschwarz/openrig",
"repository": "https://github.com/mvschwarz/openrig",
"license": "Apache-2.0",
"keywords": ["openrig", "agents", "team", "delegation", "mcp"],
"skills": "./skills",
@@ -7,7 +7,7 @@
"url": "https://github.com/mvschwarz"
},
"homepage": "https://github.com/mvschwarz/openrig",
"repository": "github:mvschwarz/openrig",
"repository": "https://github.com/mvschwarz/openrig",
"license": "Apache-2.0",
"keywords": [
"openrig",
@@ -8,7 +8,7 @@
"url": "https://github.com/mvschwarz"
},
"homepage": "https://github.com/mvschwarz/openrig",
"repository": "github:mvschwarz/openrig",
"repository": "https://github.com/mvschwarz/openrig",
"license": "Apache-2.0",
"keywords": ["openrig", "agents", "team", "delegation", "mcp"]
}
@@ -73,7 +73,8 @@ const SEAT_LOOKUP_GUIDANCE = "List seats with: rig ps --nodes";
/**
* The supported route for a session registered as adopted (origin=claimed): `rig unclaim` releases
* it and leaves its process running, so ending that process in its own terminal ends only this seat.
* it and leaves its process running, so ending that process and its tmux session ends only this seat. Closing only
* a viewing window detaches from the tmux session and leaves it running.
* `rig down` stops only a session carrying the seat's OpenRig marker, which OpenRig sets on the sessions
* it starts and an adopted, bound or self-attached session lacks, so it isn't a route here and the
* guidance says why. Only the guidance names the route; the refusal stands. A seat handed over before
@@ -85,9 +86,13 @@ export function claimedSessionRoutes(rigName: string, sessionName: string, exter
const release = `To release it from OpenRig and leave its process running, run rig unclaim ${session}.`;
if (externalCli) return `This seat was joined with rig attach as an external CLI, so rig down leaves it running. ${release} End the process in its own terminal.`;
return `To end this session, run rig unclaim ${session} (it releases the session from OpenRig and leaves its process running), `
+ `then end that process in its own terminal. ${rigDownLeavesIt(rigName)}`;
+ `then end that process and the underlying tmux session, including any remaining shell, so no session keeps the seat's name. `
+ `${VIEWER_LEAVES_IT} ${rigDownLeavesIt(rigName)}`;
}
/** Closing a viewer detaches; the tmux session, its shell and the seat's name stay. */
const VIEWER_LEAVES_IT = "Closing only its viewing window leaves the tmux session running.";
/** Why `rig down <rig>` isn't the route for an adopted session. */
function rigDownLeavesIt(rigName: string): string {
return `rig down ${shellQuote(rigName)} leaves a session OpenRig didn't start running.`;
@@ -1381,6 +1386,8 @@ export class SeatLifecycleService {
ok: false,
code: "unmanaged_session_collision",
message: `Canonical tmux session "${canonicalSessionName}" exists but is not owned by this seat's current managed rows; refusing to overwrite it.`,
guidance: "If you released this session with rig unclaim, end that underlying tmux session, including any remaining shell, "
+ "then launch again. Closing only its viewing window doesn't end the session.",
};
}
if (!stop) {
@@ -1424,8 +1431,10 @@ export class SeatLifecycleService {
return {
ok: false, code: "claimed_session",
message: `Seat "${seatRef}" has an adopted/operator-owned occupant; fresh launch refuses even with --stop.`,
guidance: `Release it with rig unclaim ${shellQuote(claimed.session_name)} (its process keeps running), end that process yourself, then launch fresh.`
+ (externalCli ? "" : ` ${rigDownLeavesIt(seat.rigName)}`),
guidance: `Release it with rig unclaim ${shellQuote(claimed.session_name)} (its process keeps running), `
+ (externalCli
? "end that process and close its terminal, then launch fresh."
: `end that process and the underlying tmux session, including any remaining shell, then launch fresh. ${VIEWER_LEAVES_IT} ${rigDownLeavesIt(seat.rigName)}`),
};
}
const binding = this.sessionRegistry.getBindingForNode(nodeId);
@@ -90,7 +90,8 @@ describe("openrig-core plugin — manifest shape (HG-2.2)", () => {
// Hook + skills wiring
expect(manifest["skills"]).toBe("./skills");
expect(manifest["hooks"]).toBe("./hooks/claude.json");
expect(manifest["repository"]).toMatch(/github:mvschwarz\/openrig-plugins/);
// An https URL: Claude's plugin directory drops a `github:` shorthand from its listing.
expect(manifest["repository"]).toBe("https://github.com/mvschwarz/openrig-plugins");
expect(manifest["license"]).toBeDefined();
});
@@ -499,7 +499,8 @@ describe("SeatLifecycleService.launchFresh", () => {
code: "claimed_session",
message: `Seat "${seat.sessionName}" has an adopted/operator-owned occupant; fresh launch refuses even with --stop.`,
// B5: rig unclaim releases it (the process keeps running); rig down leaves a session OpenRig didn't start.
guidance: "Release it with rig unclaim 'dev-impl@fresh-rig' (its process keeps running), end that process yourself, then launch fresh. "
guidance: "Release it with rig unclaim 'dev-impl@fresh-rig' (its process keeps running), end that process and the underlying tmux session, "
+ "including any remaining shell, then launch fresh. Closing only its viewing window leaves the tmux session running. "
+ "rig down 'fresh-rig' leaves a session OpenRig didn't start running.",
};
const before = db.serialize();
@@ -686,6 +687,11 @@ describe("SeatLifecycleService.launchFresh", () => {
alive.add(seat.sessionName);
const result = await service.launchFresh({ seatRef: "dev.impl", fresh: true, stop: true, reason: "x" });
expect(result).toMatchObject({ ok: false, code: "unmanaged_session_collision" });
// A session someone released with rig unclaim and left open is the usual cause: say how to clear it.
expect(result).toMatchObject({
guidance: "If you released this session with rig unclaim, end that underlying tmux session, including any remaining shell, "
+ "then launch again. Closing only its viewing window doesn't end the session.",
});
expect(killed).toEqual([]);
expect(alive.has(seat.sessionName)).toBe(true);
});
@@ -447,7 +447,7 @@ describe("SeatLifecycleService", () => {
// the refusal names the supported route, rig unclaim, and says rig down doesn't stop a session OpenRig didn't start,
// even in a one-seat rig.
expect(claimedResult.guidance).toBe("To end this session, run rig unclaim 'dev-adopted@s5-rig' (it releases the session from OpenRig "
+ "and leaves its process running), then end that process in its own terminal. rig down 's5-rig' leaves a session OpenRig didn't start running.");
+ "and leaves its process running), then end that process and the underlying tmux session, including any remaining shell, so no session keeps the seat's name. Closing only its viewing window leaves the tmux session running. rig down 's5-rig' leaves a session OpenRig didn't start running.");
const probed = seatFixture("s5-rig", "dev.flaky");
tmux.failProbeFor(probed.sessionName);
@@ -471,7 +471,7 @@ describe("SeatLifecycleService", () => {
const shared = await service.stopSeat({ seatRef: claimed.sessionName, reason: "x" });
if (shared.ok) throw new Error("expected refusal");
expect(shared.guidance).toBe("To end this session, run rig unclaim 'dev-adopted@adopted-rig' (it releases the session from OpenRig "
+ "and leaves its process running), then end that process in its own terminal. rig down 'adopted-rig' leaves a session OpenRig didn't start running.");
+ "and leaves its process running), then end that process and the underlying tmux session, including any remaining shell, so no session keeps the seat's name. Closing only its viewing window leaves the tmux session running. rig down 'adopted-rig' leaves a session OpenRig didn't start running.");
sessionRegistry.updateBinding(claimed.node.id, { attachmentType: "external_cli", externalSessionName: claimed.sessionName });
const external = await service.stopSeat({ seatRef: claimed.sessionName, reason: "x" });
@@ -485,7 +485,7 @@ describe("SeatLifecycleService", () => {
it("claimedSessionRoutes routes a tmux session through rig unclaim, never rig down", () => {
expect(claimedSessionRoutes("r", "a@r")).toBe("To end this session, run rig unclaim 'a@r' (it releases the session from OpenRig "
+ "and leaves its process running), then end that process in its own terminal. rig down 'r' leaves a session OpenRig didn't start running.");
+ "and leaves its process running), then end that process and the underlying tmux session, including any remaining shell, so no session keeps the seat's name. Closing only its viewing window leaves the tmux session running. rig down 'r' leaves a session OpenRig didn't start running.");
});
// ---- P5 / P6 — clean ----
@@ -636,10 +636,10 @@ describe("rig seat restart --keep-conversation: gates before any signal", () =>
w.db.prepare("UPDATE sessions SET origin = 'claimed' WHERE id = ?").run(w.session.id);
const claimed = await w.service.restartInPlace({ seatRef: SEAT, plan: true });
expect(claimed).toMatchObject({ ok: false, code: "claimed_session" });
// B5: the refusal's route is rig unclaim, then ending the process in its own terminal; rig down leaves a session
// B5: the refusal's route is rig unclaim, then ending the process and its tmux session; rig down leaves a session
// OpenRig didn't start running, so it isn't offered as the way to end this one.
const guidance = (claimed as { guidance?: string }).guidance ?? "";
expect(guidance).toMatch(/^To end this session, run rig unclaim '[^']+' \(it releases the session from OpenRig and leaves its process running\), then end that process in its own terminal\. /);
expect(guidance).toMatch(/^To end this session, run rig unclaim '[^']+' \(it releases the session from OpenRig and leaves its process running\), then end that process and the underlying tmux session, including any remaining shell, so no session keeps the seat's name\. Closing only its viewing window leaves the tmux session running\. /);
expect(guidance).toMatch(/rig down '[^']+' leaves a session OpenRig didn't start running\.$/);
expect(guidance).not.toMatch(/run rig down/);
});