Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"email": "hi@okis.dev"
},
"description": "Multi-model orchestration marketplace for Claude Code.",
"version": "0.0.33",
"version": "0.0.34",
"plugins": [
{
"name": "grok",
"source": "./plugins/grok",
"displayName": "Grok Companion",
"description": "Local Grok CLI delegation: task, review, resumable history, best-of-n tournaments, background jobs, stats, and setup health checks.",
"version": "0.0.33",
"version": "0.0.34",
"author": {
"name": "Harry Yep"
},
Expand All @@ -34,7 +34,7 @@
"source": "./plugins/codex",
"displayName": "Codex Companion",
"description": "First party local Codex CLI delegation for tasks, reviews, resumable threads, and durable background jobs.",
"version": "0.0.33",
"version": "0.0.34",
"author": {
"name": "Harry Yep"
},
Expand All @@ -55,7 +55,7 @@
"source": "./plugins/fusion",
"displayName": "Fusion Orchestrator",
"description": "Multi-model orchestration: tier agents, routing rules, blind panel, ultra fleet, model config, and drift doctor.",
"version": "0.0.33",
"version": "0.0.34",
"author": {
"name": "Harry Yep"
},
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# changelog

## 0.0.34

- the settle-only advisory stops repeating itself: when settlement is legitimately deferred (a package's verification waiting on a sibling landing), the pending-verdict block re-emitted identically at every stop boundary with no new information; it now dedupes by a persisted pending-set signature, the same mechanism the in-flight advisory has used since 0.0.31, emitting once per distinct set and again only when a record enters or leaves; blocking collection demands are unaffected and refresh the signature when they emit combined

## 0.0.33

- notification collection reads the transcript the notification names: every peer wrapper record in a live traffic window had a null `transcriptPath`, so the completed path extracted nothing, mislabeled clean consult wrappers (nine to eleven turns of a sixty budget, backing peer jobs all clean) as `turn_limit` incomplete, demanded manual raw transcript reads, and skipped peer footer seeding; the reconciler now stamps the record's `transcriptPath` from the notification's own output file element before transition, and a no-text completion classifies `missing_final_text` unless real turn evidence supports `turn_limit`
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "codex",
"displayName": "Codex Companion",
"version": "0.0.33",
"version": "0.0.34",
"description": "First party local Codex CLI delegation for tasks, reviews, resumable threads, and durable background jobs.",
"author": {
"name": "Harry Yep"
Expand Down
2 changes: 1 addition & 1 deletion plugins/fusion/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "fusion",
"displayName": "Fusion Orchestrator",
"version": "0.0.33",
"version": "0.0.34",
"description": "Multi-model orchestration: tier agents, routing rules, blind panel, ultra fleet, model config, and drift doctor.",
"author": {
"name": "Harry Yep"
Expand Down
29 changes: 28 additions & 1 deletion plugins/fusion/scripts/worker-lifecycle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,24 @@ function clearInFlightAdvisory(sessionId, env) {
});
}

function shouldWriteSettleOnlyAdvisory(sessionId, signature, env) {
return readWorkerSessionState(sessionId, env)?.settleOnlyAdvisorySignature !== signature;
}

function recordSettleOnlyAdvisory(sessionId, signature, env) {
updateWorkerSessionState(sessionId, env, (current) => ({ ...(current ?? {}), settleOnlyAdvisorySignature: signature }));
}

function clearSettleOnlyAdvisory(sessionId, env) {
updateWorkerSessionState(sessionId, env, (current) => {
if (!current || !("settleOnlyAdvisorySignature" in current)) {
return null;
}
const { settleOnlyAdvisorySignature, ...next } = current;
return next;
});
}

function handleStop(input, env) {
const tasks = Array.isArray(input.background_tasks) ? input.background_tasks : [];
reconcileTaskNotifications(input, env);
Expand Down Expand Up @@ -1538,8 +1556,17 @@ function handleStop(input, env) {
}
const terminalUncollected = currentRecords.filter(terminalCollectionPending);
const settleOnly = settleOnlyRecords(currentRecords);
const settleOnlySignature = settleOnly.length > 0 ? inFlightSignature(settleOnly) : null;
if (!settleOnlySignature) {
clearSettleOnlyAdvisory(input.session_id, env);
}
if (terminalUncollected.length + settleOnly.length > 1) {
writeCombinedStopPending(terminalUncollected, settleOnly);
if (terminalUncollected.length > 0 || shouldWriteSettleOnlyAdvisory(input.session_id, settleOnlySignature, env)) {
writeCombinedStopPending(terminalUncollected, settleOnly);
if (settleOnlySignature) {
recordSettleOnlyAdvisory(input.session_id, settleOnlySignature, env);
}
}
return;
}
if (terminalUncollected.length > 0) {
Expand Down
2 changes: 1 addition & 1 deletion plugins/grok/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "grok",
"displayName": "Grok Companion",
"version": "0.0.33",
"version": "0.0.34",
"description": "Local Grok CLI delegation: task, review, resumable history, best-of-n tournaments, background jobs, stats, and setup health checks.",
"author": {
"name": "Harry Yep"
Expand Down
102 changes: 102 additions & 0 deletions tests/fusion-worker-lifecycle.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ function record(box) {
return records[0];
}

function createSettleOnlyRecord(box, taskId) {
const worker = createWorkerRecord({
taskId,
sessionId: "session-1",
agentType: "fusion:fast-worker",
workspaceRoot: box.cwd
}, envFor(box));
const collectedAt = new Date().toISOString();
return updateWorkerRecord(worker.taskId, envFor(box), (current) => ({ ...current, transportStatus: "done", collectedAt, collectionMethod: WORKER_COLLECTION_METHODS.SUBAGENT_STOP, awaitingVerdict: true, awaitingVerdictArmedAt: collectedAt }));
}

function stop(box) {
return run(box, {
hook_event_name: "Stop",
session_id: "session-1",
cwd: box.cwd,
transcript_path: box.transcript,
stop_hook_active: false,
background_tasks: []
});
}

test("legacy collection methods normalize when worker records load", (t) => {
const box = sandbox(t);
const worker = createWorkerRecord({
Expand Down Expand Up @@ -3348,6 +3370,86 @@ test("Stop combines multi-record collection and settlement instructions", (t) =>
assert.match(decision.reason, new RegExp(`settle-only: ${settling.taskId}: /fusion:stats --record ${settling.taskId}=accepted\\|rejected\\|unverified`));
});

test("Stop emits a settle-only advisory once for an unchanged pending set", (t) => {
const box = sandbox(t);
const first = createSettleOnlyRecord(box, `fusion-${"1".repeat(24)}`);
const second = createSettleOnlyRecord(box, `fusion-${"2".repeat(24)}`);

const emitted = stop(box);
assert.match(emitted.stdout, new RegExp(`settle-only: ${first.taskId}`));
assert.match(emitted.stdout, new RegExp(`settle-only: ${second.taskId}`));
assert.strictEqual(readWorkerSessionState("session-1", envFor(box)).settleOnlyAdvisorySignature, [first.taskId, second.taskId].sort().join(","));

const repeated = stop(box);
assert.strictEqual(repeated.status, 0);
assert.strictEqual(repeated.stdout, "");
});

test("Stop re-emits a settle-only advisory when a record enters the pending set", (t) => {
const box = sandbox(t);
const first = createSettleOnlyRecord(box, `fusion-${"1".repeat(24)}`);
const second = createSettleOnlyRecord(box, `fusion-${"2".repeat(24)}`);
stop(box);
const added = createSettleOnlyRecord(box, `fusion-${"3".repeat(24)}`);

const emitted = stop(box);
assert.match(emitted.stdout, new RegExp(`settle-only: ${first.taskId}`));
assert.match(emitted.stdout, new RegExp(`settle-only: ${second.taskId}`));
assert.match(emitted.stdout, new RegExp(`settle-only: ${added.taskId}`));
assert.strictEqual(readWorkerSessionState("session-1", envFor(box)).settleOnlyAdvisorySignature, [first.taskId, second.taskId, added.taskId].sort().join(","));
});

test("Stop re-emits a settle-only advisory when a record settles", (t) => {
const box = sandbox(t);
const settled = createSettleOnlyRecord(box, `fusion-${"1".repeat(24)}`);
const firstRemaining = createSettleOnlyRecord(box, `fusion-${"2".repeat(24)}`);
const secondRemaining = createSettleOnlyRecord(box, `fusion-${"3".repeat(24)}`);
stop(box);
recordWorkerAcceptance({ taskId: settled.taskId, acceptance: "accepted", env: envFor(box), source: "main-loop" });

const emitted = stop(box);
assert.doesNotMatch(emitted.stdout, new RegExp(`settle-only: ${settled.taskId}`));
assert.match(emitted.stdout, new RegExp(`settle-only: ${firstRemaining.taskId}`));
assert.match(emitted.stdout, new RegExp(`settle-only: ${secondRemaining.taskId}`));
assert.strictEqual(readWorkerSessionState("session-1", envFor(box)).settleOnlyAdvisorySignature, [firstRemaining.taskId, secondRemaining.taskId].sort().join(","));
});

test("Stop combines a blocking collection demand with an unchanged settle-only advisory", (t) => {
const box = sandbox(t);
const first = createSettleOnlyRecord(box, `fusion-${"1".repeat(24)}`);
const second = createSettleOnlyRecord(box, `fusion-${"2".repeat(24)}`);
stop(box);
const collecting = createWorkerRecord({
taskId: `fusion-${"3".repeat(24)}`,
sessionId: "session-1",
agentType: "codex:codex-rescue",
workspaceRoot: box.cwd
}, envFor(box));
updateWorkerRecord(collecting.taskId, envFor(box), (current) => ({ ...current, agentId: "collecting-peer", backgroundTaskId: "collecting-peer", transportStatus: "ready_uncollected", runtimeAsync: true }));

const emitted = stop(box);
const decision = JSON.parse(emitted.stdout);
assert.strictEqual(decision.decision, "block");
assert.match(decision.reason, /Call TaskOutput with block=true for terminal owned task collecting-peer/);
assert.match(decision.reason, new RegExp(`settle-only: ${first.taskId}`));
assert.match(decision.reason, new RegExp(`settle-only: ${second.taskId}`));
assert.strictEqual(readWorkerSessionState("session-1", envFor(box)).settleOnlyAdvisorySignature, [first.taskId, second.taskId].sort().join(","));
});

test("Stop clears the settle-only advisory signature when no pending records remain", (t) => {
const box = sandbox(t);
const first = createSettleOnlyRecord(box, `fusion-${"1".repeat(24)}`);
const second = createSettleOnlyRecord(box, `fusion-${"2".repeat(24)}`);
stop(box);
assert.ok(readWorkerSessionState("session-1", envFor(box)).settleOnlyAdvisorySignature);
recordWorkerAcceptance({ taskId: first.taskId, acceptance: "accepted", env: envFor(box), source: "main-loop" });
recordWorkerAcceptance({ taskId: second.taskId, acceptance: "accepted", env: envFor(box), source: "main-loop" });

const quiet = stop(box);
assert.strictEqual(quiet.stdout, "");
assert.strictEqual(Object.hasOwn(readWorkerSessionState("session-1", envFor(box)), "settleOnlyAdvisorySignature"), false);
});

test("a delivered completion notification prevents Stop from demanding TaskStop for an expired worker", (t) => {
const box = sandbox(t);
const workerTranscript = path.join(box.root, "notification-expired.output");
Expand Down
Loading