pi-forge 1.3.5 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +1 -1
  2. package/dist/client/assets/{CodeMirrorEditor-iLMm0PBW.js → CodeMirrorEditor-BklpCqAo.js} +2 -2
  3. package/dist/client/assets/{CodeMirrorEditor-iLMm0PBW.js.map → CodeMirrorEditor-BklpCqAo.js.map} +1 -1
  4. package/dist/client/assets/index-CfPBiBJW.css +1 -0
  5. package/dist/client/assets/{index-ChVHn55j.js → index-EU82nCUh.js} +85 -84
  6. package/dist/client/assets/index-EU82nCUh.js.map +1 -0
  7. package/dist/client/index.html +2 -2
  8. package/dist/client/sw.js +1 -1
  9. package/dist/client/sw.js.map +1 -1
  10. package/dist/server/cli.js +122 -1
  11. package/dist/server/cli.js.map +1 -1
  12. package/dist/server/config.js +69 -4
  13. package/dist/server/config.js.map +1 -1
  14. package/dist/server/git-runner.js +57 -0
  15. package/dist/server/git-runner.js.map +1 -1
  16. package/dist/server/index.js +2 -0
  17. package/dist/server/index.js.map +1 -1
  18. package/dist/server/ldap-auth.js +103 -0
  19. package/dist/server/ldap-auth.js.map +1 -0
  20. package/dist/server/lifecycle-notifications.js +44 -0
  21. package/dist/server/lifecycle-notifications.js.map +1 -0
  22. package/dist/server/mcp/manager.js +40 -5
  23. package/dist/server/mcp/manager.js.map +1 -1
  24. package/dist/server/mcp/tool-bridge.js +12 -8
  25. package/dist/server/mcp/tool-bridge.js.map +1 -1
  26. package/dist/server/orchestration/config.js +10 -36
  27. package/dist/server/orchestration/config.js.map +1 -1
  28. package/dist/server/orchestration/event-bridge.js +80 -28
  29. package/dist/server/orchestration/event-bridge.js.map +1 -1
  30. package/dist/server/orchestration/inbox.js +157 -124
  31. package/dist/server/orchestration/inbox.js.map +1 -1
  32. package/dist/server/orchestration/init.js +1 -1
  33. package/dist/server/orchestration/store.js +44 -4
  34. package/dist/server/orchestration/store.js.map +1 -1
  35. package/dist/server/orchestration/tools.js +51 -132
  36. package/dist/server/orchestration/tools.js.map +1 -1
  37. package/dist/server/orchestration/types.js +8 -4
  38. package/dist/server/orchestration/types.js.map +1 -1
  39. package/dist/server/orchestration/worker-lifecycle.js +68 -0
  40. package/dist/server/orchestration/worker-lifecycle.js.map +1 -0
  41. package/dist/server/routes/_schemas.js +6 -0
  42. package/dist/server/routes/_schemas.js.map +1 -1
  43. package/dist/server/routes/auth.js +31 -9
  44. package/dist/server/routes/auth.js.map +1 -1
  45. package/dist/server/routes/control.js +52 -13
  46. package/dist/server/routes/control.js.map +1 -1
  47. package/dist/server/routes/git.js +227 -216
  48. package/dist/server/routes/git.js.map +1 -1
  49. package/dist/server/routes/health.js +3 -4
  50. package/dist/server/routes/health.js.map +1 -1
  51. package/dist/server/routes/orchestration.js +41 -15
  52. package/dist/server/routes/orchestration.js.map +1 -1
  53. package/dist/server/routes/prompt.js +40 -2
  54. package/dist/server/routes/prompt.js.map +1 -1
  55. package/dist/server/routes/sessions.js +142 -18
  56. package/dist/server/routes/sessions.js.map +1 -1
  57. package/dist/server/routes/stream.js +77 -2
  58. package/dist/server/routes/stream.js.map +1 -1
  59. package/dist/server/routes/terminal.js +18 -7
  60. package/dist/server/routes/terminal.js.map +1 -1
  61. package/dist/server/session-registry.js +159 -32
  62. package/dist/server/session-registry.js.map +1 -1
  63. package/dist/server/sse-bridge.js +58 -29
  64. package/dist/server/sse-bridge.js.map +1 -1
  65. package/dist/server/subagents-external.js +319 -0
  66. package/dist/server/subagents-external.js.map +1 -0
  67. package/package.json +5 -4
  68. package/dist/client/assets/index-ChVHn55j.js.map +0 -1
  69. package/dist/client/assets/index-D311Lak3.css +0 -1
@@ -1,4 +1,5 @@
1
1
  import { bridgeWorkerEvent } from "./inbox.js";
2
+ import { getWorkerRecord, updateWorkerLifecycle } from "./store.js";
2
3
  function findLastAssistant(messages) {
3
4
  for (let i = messages.length - 1; i >= 0; i--) {
4
5
  const m = messages[i];
@@ -28,54 +29,81 @@ function extractAssistantText(content) {
28
29
  }
29
30
  if (parts.length === 0)
30
31
  return undefined;
31
- // Cap to 600 chars in the inbox payload — the supervisor LLM
32
- // sees this as a quick summary; if it wants more it calls
33
- // `orchestrate_read_worker`. Bigger payloads bloat the inbox
34
- // file and the supervisor's tool-result context.
35
- const joined = parts.join("\n");
36
- return joined.length > 600 ? `${joined.slice(0, 600)}…` : joined;
32
+ // Worker completion notifications are the supervisor's actionable
33
+ // context now, so include the full final assistant text rather than a
34
+ // preview that requires a follow-up inbox/transcript read.
35
+ return parts.join("\n");
37
36
  }
38
37
  /**
39
38
  * Called from `session-registry.makeSubscribeHandler` for every
40
- * AgentSessionEvent on every live session. Filters for the two SDK
41
- * events that map to inbox items; everything else is ignored.
39
+ * AgentSessionEvent on every live session. Only authoritative
40
+ * lifecycle signals wake the supervisor: agent_end (final turn
41
+ * outcome) and explicit blocked state from ask_user_question (below).
42
+ * Retry events are deliberately ignored here; if retries ultimately
43
+ * fail, the SDK reports the terminal outcome on agent_end or the
44
+ * explicit stop/delete path reports no agent_end was observed.
42
45
  */
43
46
  export async function bridgeWorkerAgentEvent(meta, event) {
44
47
  const e = event;
45
- if (e.type === "agent_end") {
46
- const messages = meta.session.messages;
47
- const lastAssistant = findLastAssistant(messages);
48
- const errorMessage = meta.session.errorMessage ??
49
- lastAssistant?.errorMessage;
50
- await bridgeWorkerEvent(meta.sessionId, "worker.ended", {
51
- stopReason: lastAssistant?.stopReason ?? null,
52
- errorMessage: errorMessage ?? null,
53
- assistantTextPreview: lastAssistant?.text ?? null,
48
+ const now = new Date().toISOString();
49
+ if (e.type === "agent_start") {
50
+ await updateWorkerLifecycle(meta.sessionId, {
51
+ state: "running",
52
+ turnOpen: true,
53
+ lastStateAt: now,
54
+ lastAgentStartAt: now,
55
+ stopReason: null,
56
+ errorMessage: null,
54
57
  });
55
58
  return;
56
59
  }
57
- if (e.type === "auto_retry_end" && e.success === false) {
58
- await bridgeWorkerEvent(meta.sessionId, "worker.auto_retry_failed", {
59
- attempt: e.attempt ?? null,
60
- maxAttempts: e.maxAttempts ?? null,
61
- finalError: e.finalError ?? null,
62
- });
60
+ if (e.type !== "agent_end")
63
61
  return;
64
- }
62
+ const messages = meta.session.messages;
63
+ const lastAssistant = findLastAssistant(messages);
64
+ const errorMessage = meta.session.errorMessage ??
65
+ lastAssistant?.errorMessage;
66
+ const hasError = typeof errorMessage === "string" && errorMessage.length > 0;
67
+ await updateWorkerLifecycle(meta.sessionId, {
68
+ state: hasError ? "errored" : "ended",
69
+ turnOpen: false,
70
+ lastStateAt: now,
71
+ lastAgentEndAt: now,
72
+ stopReason: lastAssistant?.stopReason ?? null,
73
+ errorMessage: errorMessage ?? null,
74
+ });
75
+ await bridgeWorkerEvent(meta.sessionId, "worker.ended", {
76
+ stopReason: lastAssistant?.stopReason ?? null,
77
+ errorMessage: errorMessage ?? null,
78
+ assistantText: lastAssistant?.text ?? null,
79
+ });
65
80
  }
66
81
  export async function bridgeWorkerAskUserQuestion(sessionId, questions, requestId) {
82
+ await updateWorkerLifecycle(sessionId, {
83
+ state: "awaiting_question",
84
+ lastStateAt: new Date().toISOString(),
85
+ });
67
86
  await bridgeWorkerEvent(sessionId, "worker.ask_user", {
68
87
  requestId,
69
88
  questionCount: questions.length,
70
- // Keep the inbox payload tight preview is the first question's
71
- // header so the supervisor sees enough to decide whether to read
72
- // the worker's transcript. Full question detail is in the
73
- // worker's session, fetched via orchestrate_read_worker.
89
+ // Include the question detail directly so the supervisor can react
90
+ // from the pushed notification without first draining an inbox.
74
91
  firstQuestionHeader: questions[0]?.header ?? null,
75
92
  firstQuestionText: questions[0]?.question ?? null,
76
93
  });
77
94
  }
95
+ export function shouldBridgeWorkerProcessAlert(_reason) {
96
+ // Worker process alerts are intentionally not escalated to the
97
+ // supervisor inbox. The worker session itself still receives the
98
+ // in-chat process alert because it explicitly requested an
99
+ // alertOn* notification, and the running worker agent can react
100
+ // locally. Waking the orchestrator for process success/failure/kill
101
+ // outcomes has proven too noisy and duplicative.
102
+ return false;
103
+ }
78
104
  export async function bridgeWorkerProcessAlert(sessionId, info, reason) {
105
+ if (!shouldBridgeWorkerProcessAlert(reason))
106
+ return;
79
107
  await bridgeWorkerEvent(sessionId, "worker.process_alert", {
80
108
  reason,
81
109
  processId: info.id,
@@ -85,7 +113,31 @@ export async function bridgeWorkerProcessAlert(sessionId, info, reason) {
85
113
  success: info.success,
86
114
  });
87
115
  }
116
+ export async function bridgeWorkerExecutionStopped(sessionId, meta) {
117
+ const rec = await getWorkerRecord(sessionId);
118
+ if (rec?.turnOpen !== true)
119
+ return;
120
+ await updateWorkerLifecycle(sessionId, {
121
+ state: "stopped",
122
+ turnOpen: false,
123
+ lastStateAt: new Date().toISOString(),
124
+ stopReason: meta.reason,
125
+ });
126
+ await bridgeWorkerEvent(sessionId, "worker.execution_stopped_without_agent_end", {
127
+ reason: meta.reason,
128
+ wasLive: meta.wasLive,
129
+ lastAgentStartAt: rec.lastAgentStartAt ?? null,
130
+ });
131
+ }
88
132
  export async function bridgeWorkerDeleted(sessionId, meta) {
133
+ const reason = meta.reason ?? "deleted";
134
+ await bridgeWorkerExecutionStopped(sessionId, { wasLive: meta.wasLive, reason });
135
+ await updateWorkerLifecycle(sessionId, {
136
+ state: "deleted",
137
+ turnOpen: false,
138
+ lastStateAt: new Date().toISOString(),
139
+ stopReason: reason,
140
+ });
89
141
  await bridgeWorkerEvent(sessionId, "worker.deleted", {
90
142
  wasLive: meta.wasLive,
91
143
  });
@@ -1 +1 @@
1
- {"version":3,"file":"event-bridge.js","sourceRoot":"","sources":["../../src/orchestration/event-bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"event-bridge.js","sourceRoot":"","sources":["../../src/orchestration/event-bridge.ts"],"names":[],"mappings":"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"}
@@ -1,47 +1,19 @@
1
1
  /**
2
- * Inbox surface — the bridge between worker events and supervisor
3
- * wake-up. Wraps the raw inbox queue (in `store.ts`) with the
4
- * "fire a prompt at an idle supervisor" mechanism that makes
5
- * "session A watches session B" actually work without polling.
2
+ * Worker notification surface — the bridge between worker events and
3
+ * supervisor custom messages. The persisted inbox store remains as UI
4
+ * history/audit data, but the supervisor LLM no longer needs a separate
5
+ * read-inbox tool: each `orchestration-notify` message carries the
6
+ * actionable event payload directly.
6
7
  *
7
- * Design:
8
- * 1. PULL primary: supervisor's LLM calls `orchestrate_read_inbox`
9
- * to drain pending items. Items stay in the file (cap-evicted)
10
- * so the REST UI can show recent activity even after the LLM
11
- * consumed them.
12
- * 2. PUSH secondary: when an event enqueues AND the supervisor
13
- * session is live AND idle (not currently streaming), we fire
14
- * a tiny `[orchestration]` prompt at the supervisor so it
15
- * starts a new turn that surfaces the items. If the supervisor
16
- * is busy, the push skips — the items wait on the queue, and
17
- * the supervisor's next prompt will see them via
18
- * `orchestrate_read_inbox`.
19
- * 3. Recovery: when the supervisor's own `agent_end` fires (i.e.
20
- * it just became idle), we check for pending items and PUSH
21
- * again if any. Closes the "supervisor finished a turn but
22
- * didn't call read_inbox" gap.
23
- *
24
- * All PUSH paths are best-effort: the queue is the source of truth.
25
- * A missed wake-up means the supervisor sees the items on its next
26
- * turn, not that the items vanish.
8
+ * Delivery policy:
9
+ * - If the supervisor is active, every worker update is delivered as
10
+ * `steer` so it can enter the current run at the next model step.
11
+ * - If the supervisor is idle, every update except delete/unregister
12
+ * starts a new turn; delete/unregister only appends a visible card.
27
13
  */
14
+ import { sendCustomLifecycleMessage } from "../lifecycle-notifications.js";
28
15
  import { getSession } from "../session-registry.js";
29
- import { enqueueInboxItem, getSupervisorIdForWorker, pendingInboxCount, readPendingInbox, } from "./store.js";
30
- /**
31
- * Per-supervisor "already pushed once for this idle window" flag.
32
- * Without this, every inbox enqueue while the supervisor is briefly
33
- * idle would fire a fresh `prompt()` — the supervisor's first push
34
- * starts a turn, but the SDK may not flip isStreaming to true
35
- * immediately (it's set after the first message_start event), so a
36
- * second enqueue 1ms later would still see "idle" and fire a
37
- * duplicate prompt. The flag is cleared in two places:
38
- * - On the supervisor's own `agent_end` (handled in
39
- * `notifySupervisorIdle` below) — the supervisor's turn finished
40
- * and the next idle window is a new wake-up opportunity.
41
- * - On supervisor dispose (handled in session-registry) — clear
42
- * so a re-resumed session can wake again.
43
- */
44
- const pendingWakePush = new Set();
16
+ import { enqueueInboxItem, getSupervisorIdForWorker, readPendingInbox } from "./store.js";
45
17
  /**
46
18
  * Per-supervisor sequence number for wake-up prompts. Surfaces in
47
19
  * stderr logs to make it possible to grep "how many times have we
@@ -57,84 +29,156 @@ function logInbox(level, payload) {
57
29
  process.stderr.write(`${JSON.stringify({ level, time: new Date().toISOString(), ...payload })}\n`);
58
30
  }
59
31
  /**
60
- * Decide whether the live supervisor session is "idle" i.e.,
61
- * not currently mid-turn. The SDK exposes `isStreaming`; that's a
62
- * stable proxy for "the agent loop is producing output right now."
63
- * A session that has never prompted is also idle.
64
- */
65
- function isSupervisorIdle(supervisorId) {
66
- const live = getSession(supervisorId);
67
- if (live === undefined)
68
- return false; // not live → no idle to push to
69
- // SDK exposes isStreaming as a getter on AgentSession.
70
- return live.session.isStreaming === false;
71
- }
72
- /**
73
- * Build the wake-up prompt text. Kept short — the prompt's only job
74
- * is to nudge the supervisor LLM to call `orchestrate_read_inbox`.
75
- * The bracket marker is also what the client UI uses to style the
76
- * message distinctly from a user message (recognised by the
77
- * `OrchestrationWakePrefix` constant exported below).
32
+ * Build the supervisor notification text. The content is intentionally
33
+ * actionable on its own so the supervisor does not need a separate
34
+ * read-inbox call before deciding what to do.
78
35
  */
79
36
  export const ORCHESTRATION_WAKE_PREFIX = "[orchestration]";
80
- function buildWakeText(pendingCount) {
81
- return (`${ORCHESTRATION_WAKE_PREFIX} ${pendingCount} pending worker event(s). ` +
82
- `Call \`orchestrate_read_inbox\` to inspect, then decide whether to ` +
83
- `\`orchestrate_read_worker\`, \`orchestrate_send_to_worker\`, or take no action.`);
37
+ export const ORCHESTRATION_NOTIFY_TYPE = "orchestration-notify";
38
+ export function shouldTriggerWorkerTurn(type) {
39
+ return type !== "worker.deleted";
40
+ }
41
+ function workerStateForInboxType(type) {
42
+ if (type === "worker.ended")
43
+ return "ended";
44
+ if (type === "worker.execution_stopped_without_agent_end")
45
+ return "failed";
46
+ if (type === "worker.deleted")
47
+ return "deleted";
48
+ if (type === "worker.ask_user")
49
+ return "awaiting_question";
50
+ if (type === "worker.process_alert")
51
+ return "process_alert";
52
+ return type.replace(/^worker\./, "");
53
+ }
54
+ function buildNotificationText(type, workerId, data) {
55
+ const state = workerStateForInboxType(type);
56
+ const lines = [`Worker ${workerId} reported ${state}.`];
57
+ const detail = summarizeWorkerEventData(type, data);
58
+ if (detail !== "")
59
+ lines.push(detail);
60
+ return lines.join("\n\n");
61
+ }
62
+ function summarizeWorkerEventData(type, data) {
63
+ if (type === "worker.ended") {
64
+ const stop = typeof data.stopReason === "string" ? data.stopReason : "unknown";
65
+ const err = typeof data.errorMessage === "string" && data.errorMessage.length > 0
66
+ ? data.errorMessage
67
+ : "";
68
+ const text = typeof data.assistantText === "string" && data.assistantText.length > 0
69
+ ? data.assistantText
70
+ : "";
71
+ const parts = [`stopReason: ${stop}`];
72
+ if (err !== "")
73
+ parts.push(`errorMessage:\n${err}`);
74
+ if (text !== "")
75
+ parts.push(`finalMessage:\n${text}`);
76
+ return parts.join("\n\n");
77
+ }
78
+ if (type === "worker.ask_user") {
79
+ const count = typeof data.questionCount === "number" ? data.questionCount : 1;
80
+ const header = typeof data.firstQuestionHeader === "string" ? data.firstQuestionHeader : "";
81
+ const text = typeof data.firstQuestionText === "string" ? data.firstQuestionText : "";
82
+ return [
83
+ `questionCount: ${count}`,
84
+ header !== "" ? `firstQuestionHeader: ${header}` : "",
85
+ text !== "" ? `firstQuestionText:\n${text}` : "",
86
+ ]
87
+ .filter((part) => part !== "")
88
+ .join("\n\n");
89
+ }
90
+ if (type === "worker.execution_stopped_without_agent_end") {
91
+ const reason = typeof data.reason === "string" ? data.reason : "stopped";
92
+ const lastStart = typeof data.lastAgentStartAt === "string" ? data.lastAgentStartAt : "";
93
+ return `reason: ${reason}${lastStart !== "" ? `\nlastAgentStartAt: ${lastStart}` : ""}`;
94
+ }
95
+ if (type === "worker.deleted") {
96
+ return `wasLive: ${data.wasLive === true ? "true" : "false"}`;
97
+ }
98
+ try {
99
+ return JSON.stringify(data, null, 2);
100
+ }
101
+ catch {
102
+ return "";
103
+ }
84
104
  }
85
105
  /**
86
- * Attempt to wake the supervisor with a tiny system-style prompt.
87
- * No-op if the supervisor is offline, busy, or already has an
88
- * in-flight push for this idle window.
89
- *
90
- * Returns true if a prompt was actually fired (used by tests).
106
+ * Deliver a worker notification to the supervisor. No-op if the
107
+ * supervisor is offline, or if this exact inbox item was already
108
+ * surfaced as a durable custom message.
91
109
  */
92
- async function tryWakeSupervisor(supervisorId) {
110
+ async function tryWakeSupervisor(supervisorId, event) {
93
111
  const live = getSession(supervisorId);
94
112
  if (live === undefined)
95
113
  return false;
96
- if (!isSupervisorIdle(supervisorId))
114
+ const pendingItems = await readPendingInbox(supervisorId);
115
+ const pending = pendingItems.length;
116
+ const fallbackItem = pendingItems.find((it) => shouldTriggerWorkerTurn(it.type)) ?? pendingItems[0];
117
+ const effectiveEvent = event ??
118
+ (fallbackItem === undefined
119
+ ? undefined
120
+ : {
121
+ type: fallbackItem.type,
122
+ workerId: fallbackItem.workerId,
123
+ itemId: fallbackItem.id,
124
+ data: fallbackItem.data,
125
+ });
126
+ if (effectiveEvent === undefined)
97
127
  return false;
98
- if (pendingWakePush.has(supervisorId))
99
- return false;
100
- const pending = await pendingInboxCount(supervisorId);
101
- if (pending === 0)
102
- return false;
103
- pendingWakePush.add(supervisorId);
128
+ const triggerTurn = shouldTriggerWorkerTurn(effectiveEvent.type);
104
129
  const seq = nextWakeCounter(supervisorId);
105
- const text = buildWakeText(pending);
106
- // Fire-and-forget. The SDK's session.prompt is async; we let it
130
+ const type = effectiveEvent.type;
131
+ const workerId = effectiveEvent.workerId;
132
+ const text = buildNotificationText(type, workerId, effectiveEvent.data);
133
+ // Fire-and-forget. The SDK's sendCustomMessage is async; we let it
107
134
  // run in the background so the event-bridge caller (often the
108
- // hot path of an AgentSessionEvent subscriber) returns
109
- // immediately. Errors are surfaced via stderr — most likely
110
- // cause is "no model configured on the supervisor session," in
111
- // which case the supervisor is unusable for orchestration
112
- // regardless and the operator needs to see why.
113
- live.session
114
- .prompt(text)
115
- .then(() => {
116
- logInbox("info", {
117
- msg: "orchestration-wake-delivered",
118
- supervisorId,
119
- pending,
120
- seq,
121
- });
122
- })
123
- .catch((err) => {
124
- logInbox("warn", {
125
- msg: "orchestration-wake-failed",
126
- supervisorId,
127
- pending,
128
- seq,
129
- err: err instanceof Error ? err.message : String(err),
130
- });
135
+ // hot path of an AgentSessionEvent subscriber) returns immediately.
136
+ // Errors are surfaced via stderr — most likely cause is "session was
137
+ // disposed between enqueue and wake" or no model configured when a
138
+ // triggering notification starts a turn.
139
+ sendCustomLifecycleMessage(live.session, {
140
+ customType: ORCHESTRATION_NOTIFY_TYPE,
141
+ content: text,
142
+ display: true,
143
+ details: {
144
+ source: "orchestration",
145
+ state: workerStateForInboxType(type),
146
+ eventType: type,
147
+ workerId,
148
+ inboxItemId: effectiveEvent.itemId,
149
+ pendingCount: pending,
150
+ data: effectiveEvent.data,
151
+ },
152
+ }, {
153
+ triggerTurn,
154
+ dedupe: { detailKey: "inboxItemId", detailValue: effectiveEvent.itemId },
155
+ onError: (err) => {
156
+ logInbox("warn", {
157
+ msg: "orchestration-wake-failed",
158
+ supervisorId,
159
+ workerId,
160
+ type,
161
+ pending,
162
+ seq,
163
+ triggerTurn,
164
+ err: err instanceof Error ? err.message : String(err),
165
+ });
166
+ },
167
+ });
168
+ logInbox("info", {
169
+ msg: "orchestration-wake-delivered",
170
+ supervisorId,
171
+ workerId,
172
+ type,
173
+ pending,
174
+ seq,
175
+ triggerTurn,
131
176
  });
132
177
  return true;
133
178
  }
134
179
  /**
135
180
  * Public entry point: a worker session emitted an interesting
136
- * event, route it to the right supervisor's inbox and try to
137
- * wake the supervisor.
181
+ * event, store it for UI history and notify the supervisor.
138
182
  *
139
183
  * Silently skips when the worker isn't linked to a supervisor —
140
184
  * happens transiently when a worker is being detached/disposed,
@@ -151,6 +195,7 @@ export async function bridgeWorkerEvent(workerId, type, data) {
151
195
  workerId,
152
196
  occurredAt: new Date().toISOString(),
153
197
  data,
198
+ delivered: true,
154
199
  });
155
200
  logInbox("info", {
156
201
  msg: "orchestration-inbox-enqueued",
@@ -159,39 +204,27 @@ export async function bridgeWorkerEvent(workerId, type, data) {
159
204
  type,
160
205
  itemId: item.id,
161
206
  });
162
- // Best-effort wake-up. tryWake handles its own debounce + offline
163
- // checks. We DON'T await the worker event handler that called
164
- // us shouldn't block on the supervisor's LLM round-trip.
165
- void tryWakeSupervisor(supervisorId);
207
+ // Best-effort wake-up/status append. We DON'T await the worker event
208
+ // handler that called us shouldn't block on the supervisor's LLM round-trip.
209
+ void tryWakeSupervisor(supervisorId, { type, workerId, itemId: item.id, data });
166
210
  return item;
167
211
  }
168
212
  /**
169
- * Called from session-registry when a supervisor's own session
170
- * emits `agent_end` — i.e., the supervisor just became idle.
171
- * Clears the per-idle-window dedupe flag and re-pushes if pending
172
- * items accumulated during the just-finished turn.
173
- *
174
- * This closes the "supervisor finished a turn without reading the
175
- * inbox" gap: if the supervisor's LLM ignored a prior wake-up (or
176
- * the wake-up arrived after the supervisor was already mid-turn),
177
- * this gives it one more nudge before going truly idle.
213
+ * Called from session-registry when a supervisor's own session emits
214
+ * `agent_end` — i.e., the supervisor just became idle. Compatibility
215
+ * recovery for any older undelivered history items.
178
216
  */
179
217
  export async function notifySupervisorIdle(supervisorId) {
180
- pendingWakePush.delete(supervisorId);
181
218
  void tryWakeSupervisor(supervisorId);
182
219
  }
183
- /**
184
- * Called from session-registry when a supervisor session is
185
- * disposed. Drops the dedupe flag so a future re-resume of the
186
- * same id can wake again from scratch.
187
- */
220
+ /** Called from session-registry when a supervisor session is disposed. */
188
221
  export function notifySupervisorDisposed(supervisorId) {
189
- pendingWakePush.delete(supervisorId);
190
222
  wakeCounters.delete(supervisorId);
191
223
  }
192
224
  /**
193
- * Drain a supervisor's pending inbox items and mark them delivered.
194
- * The tool-side wrapper for `orchestrate_read_inbox`.
225
+ * Drain a supervisor's pending history items and mark them delivered.
226
+ * Retained for REST/UI history compatibility; no agent-facing inbox tool
227
+ * is registered.
195
228
  */
196
229
  export async function drainInbox(supervisorId) {
197
230
  return readPendingInbox(supervisorId, { markDelivered: true });
@@ -1 +1 @@
1
- {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/orchestration/inbox.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/orchestration/inbox.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * Boot-time wiring of the orchestration event bridge to the
3
3
  * forge-native singleton event channels (ask-user-question, processes).
4
4
  *
5
- * Per-AgentSession events (agent_end, auto_retry_end) are dispatched
5
+ * Per-AgentSession events (agent_start, agent_end) are dispatched
6
6
  * from inside `session-registry.makeSubscribeHandler` — those need
7
7
  * the LiveSession context at construction time. session-registry
8
8
  * also calls `notifySupervisorIdle` on supervisor agent_end and
@@ -54,6 +54,18 @@ function isSupervisorRecord(v) {
54
54
  Array.isArray(r.workerIds) &&
55
55
  r.workerIds.every((id) => typeof id === "string"));
56
56
  }
57
+ const WORKER_LIFECYCLE_STATES = new Set([
58
+ "idle",
59
+ "running",
60
+ "ended",
61
+ "errored",
62
+ "stopped",
63
+ "deleted",
64
+ "awaiting_question",
65
+ ]);
66
+ function isWorkerLifecycleState(v) {
67
+ return typeof v === "string" && WORKER_LIFECYCLE_STATES.has(v);
68
+ }
57
69
  function isWorkerRecord(v) {
58
70
  if (typeof v !== "object" || v === null)
59
71
  return false;
@@ -67,6 +79,18 @@ function isWorkerRecord(v) {
67
79
  if (sf.mode !== "fresh" && sf.mode !== "summary")
68
80
  return false;
69
81
  }
82
+ if (r.state !== undefined && !isWorkerLifecycleState(r.state))
83
+ return false;
84
+ if (r.turnOpen !== undefined && typeof r.turnOpen !== "boolean")
85
+ return false;
86
+ for (const key of ["lastStateAt", "lastAgentStartAt", "lastAgentEndAt"]) {
87
+ if (r[key] !== undefined && typeof r[key] !== "string")
88
+ return false;
89
+ }
90
+ for (const key of ["stopReason", "errorMessage"]) {
91
+ if (r[key] !== undefined && r[key] !== null && typeof r[key] !== "string")
92
+ return false;
93
+ }
70
94
  return true;
71
95
  }
72
96
  async function readStoreFile() {
@@ -175,9 +199,13 @@ export async function registerWorker(opts) {
175
199
  throw new OrchestrationError("depth_limit_exceeded", `Session ${opts.workerId} is already a supervisor; cannot also be a worker.`);
176
200
  }
177
201
  sup.workerIds.push(opts.workerId);
202
+ const now = new Date().toISOString();
178
203
  const wrec = {
179
204
  supervisorId: opts.supervisorId,
180
- spawnedAt: new Date().toISOString(),
205
+ spawnedAt: now,
206
+ state: "idle",
207
+ turnOpen: false,
208
+ lastStateAt: now,
181
209
  };
182
210
  if (opts.spawnedFrom !== undefined)
183
211
  wrec.spawnedFrom = opts.spawnedFrom;
@@ -231,6 +259,17 @@ export async function getWorkerRecord(workerId) {
231
259
  const s = await readStoreFile();
232
260
  return s.workers[workerId];
233
261
  }
262
+ export async function updateWorkerLifecycle(workerId, patch) {
263
+ return withStoreLock(async () => {
264
+ const s = await readStoreFile();
265
+ const rec = s.workers[workerId];
266
+ if (rec === undefined)
267
+ return undefined;
268
+ Object.assign(rec, patch);
269
+ await writeStoreFile(s);
270
+ return { ...rec };
271
+ });
272
+ }
234
273
  // ---- orchestrator-inbox.json (queue) ----
235
274
  let inboxLock = Promise.resolve();
236
275
  function withInboxLock(fn) {
@@ -287,7 +326,8 @@ export async function enqueueInboxItem(supervisorId, item) {
287
326
  return withInboxLock(async () => {
288
327
  const s = await readInboxFile();
289
328
  const existing = s.inboxes[supervisorId] ?? [];
290
- const full = { id: randomUUID(), delivered: false, ...item };
329
+ const { delivered = false, ...rest } = item;
330
+ const full = { id: randomUUID(), delivered, ...rest };
291
331
  existing.push(full);
292
332
  // FIFO trim from the front so the most recent N stay.
293
333
  const trimmed = existing.length > MAX_INBOX_ITEMS
@@ -337,8 +377,8 @@ export async function pendingInboxCount(supervisorId) {
337
377
  return items.filter((it) => !it.delivered).length;
338
378
  }
339
379
  /**
340
- * Drop a supervisor's inbox entirely. Called when supervisor mode
341
- * is disabled or the supervisor session is deleted.
380
+ * Drop a supervisor's worker-event history entirely. Called when
381
+ * supervisor mode is disabled or the supervisor session is deleted.
342
382
  */
343
383
  export async function clearInbox(supervisorId) {
344
384
  await withInboxLock(async () => {