-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsubagent-stop.mjs
More file actions
95 lines (80 loc) · 3.41 KB
/
subagent-stop.mjs
File metadata and controls
95 lines (80 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env node
/* global process, Buffer */
/**
* Hook: SubagentStop
*
* Fires when an implementer subagent completes in the orchestrator session.
* Reads the retro marker to detect deferred retrospectives and injects
* context so the orchestrator can pick up the retrospective.
*/
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const MARKER_PATH = resolve(__dirname, ".session-state", "retro-marker.json");
// ── Read stdin (SubagentStop payload) ────────────────────
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const raw = Buffer.concat(chunks).toString("utf8").trim();
let payload = {};
try { payload = JSON.parse(raw); } catch { /* no valid payload */ }
const agentName = payload.agent_name || "unknown";
// ── Load i18n lazily ─────────────────────────────────────
const { t } = await import("../../core/context.mjs");
// ── Check deferred retro marker ──────────────────────────
let retroDeferred = false;
let retroContext = null;
try {
if (existsSync(MARKER_PATH)) {
const marker = JSON.parse(readFileSync(MARKER_PATH, "utf8"));
if (marker.retro_pending && marker.deferred_to_orchestrator) {
retroDeferred = true;
retroContext = {
rx_id: marker.rx_id,
agreed_items: marker.agreed_items,
};
// Consume the deferral flag — orchestrator now owns the retro
// Keep retro_pending=true so session-gate enforces the protocol
writeFileSync(MARKER_PATH, JSON.stringify({
...marker,
deferred_to_orchestrator: false,
consumed_by_orchestrator: true,
consumed_at: new Date().toISOString(),
}, null, 2), "utf8");
}
}
} catch { /* marker read/write error — non-fatal */ }
// ── Build output ─────────────────────────────────────────
const lines = [];
lines.push(t("subagent.stop.completed", { agent: agentName }));
if (retroDeferred && retroContext) {
lines.push("");
lines.push(t("subagent.stop.deferred_retro", {
rx_id: retroContext.rx_id,
items: retroContext.agreed_items,
}));
}
if (lines.length > 0) {
process.stdout.write(lines.join("\n"));
}
// ── Emit agent.complete event to EventStore for daemon visibility ──
try {
const bridge = await import("../../core/bridge.mjs");
const { REPO_ROOT } = await import("../../core/context.mjs");
await bridge.init(REPO_ROOT);
bridge.emitEvent("agent.complete", "claude-code", {
name: agentName,
retroDeferred,
});
bridge.close();
} catch { /* bridge non-critical */ }
// ── Auto-update RTM statuses based on current file state ──
try {
const { REPO_ROOT } = await import("../../core/context.mjs");
const { updateAllRtms } = await import("../../core/rtm-updater.mjs");
const results = updateAllRtms(REPO_ROOT);
if (results.length > 0) {
const total = results.reduce((s, r) => s + r.updated, 0);
process.stderr.write(`[quorum] RTM auto-updated: ${total} row(s) across ${results.length} file(s)\n`);
}
} catch (e) { process.stderr.write(`[quorum] RTM update warning: ${e.message}\n`); }