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.
- package/README.md +1 -1
- package/dist/client/assets/{CodeMirrorEditor-iLMm0PBW.js → CodeMirrorEditor-BklpCqAo.js} +2 -2
- package/dist/client/assets/{CodeMirrorEditor-iLMm0PBW.js.map → CodeMirrorEditor-BklpCqAo.js.map} +1 -1
- package/dist/client/assets/index-CfPBiBJW.css +1 -0
- package/dist/client/assets/{index-ChVHn55j.js → index-EU82nCUh.js} +85 -84
- package/dist/client/assets/index-EU82nCUh.js.map +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.map +1 -1
- package/dist/server/cli.js +122 -1
- package/dist/server/cli.js.map +1 -1
- package/dist/server/config.js +69 -4
- package/dist/server/config.js.map +1 -1
- package/dist/server/git-runner.js +57 -0
- package/dist/server/git-runner.js.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/ldap-auth.js +103 -0
- package/dist/server/ldap-auth.js.map +1 -0
- package/dist/server/lifecycle-notifications.js +44 -0
- package/dist/server/lifecycle-notifications.js.map +1 -0
- package/dist/server/mcp/manager.js +40 -5
- package/dist/server/mcp/manager.js.map +1 -1
- package/dist/server/mcp/tool-bridge.js +12 -8
- package/dist/server/mcp/tool-bridge.js.map +1 -1
- package/dist/server/orchestration/config.js +10 -36
- package/dist/server/orchestration/config.js.map +1 -1
- package/dist/server/orchestration/event-bridge.js +80 -28
- package/dist/server/orchestration/event-bridge.js.map +1 -1
- package/dist/server/orchestration/inbox.js +157 -124
- package/dist/server/orchestration/inbox.js.map +1 -1
- package/dist/server/orchestration/init.js +1 -1
- package/dist/server/orchestration/store.js +44 -4
- package/dist/server/orchestration/store.js.map +1 -1
- package/dist/server/orchestration/tools.js +51 -132
- package/dist/server/orchestration/tools.js.map +1 -1
- package/dist/server/orchestration/types.js +8 -4
- package/dist/server/orchestration/types.js.map +1 -1
- package/dist/server/orchestration/worker-lifecycle.js +68 -0
- package/dist/server/orchestration/worker-lifecycle.js.map +1 -0
- package/dist/server/routes/_schemas.js +6 -0
- package/dist/server/routes/_schemas.js.map +1 -1
- package/dist/server/routes/auth.js +31 -9
- package/dist/server/routes/auth.js.map +1 -1
- package/dist/server/routes/control.js +52 -13
- package/dist/server/routes/control.js.map +1 -1
- package/dist/server/routes/git.js +227 -216
- package/dist/server/routes/git.js.map +1 -1
- package/dist/server/routes/health.js +3 -4
- package/dist/server/routes/health.js.map +1 -1
- package/dist/server/routes/orchestration.js +41 -15
- package/dist/server/routes/orchestration.js.map +1 -1
- package/dist/server/routes/prompt.js +40 -2
- package/dist/server/routes/prompt.js.map +1 -1
- package/dist/server/routes/sessions.js +142 -18
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/routes/stream.js +77 -2
- package/dist/server/routes/stream.js.map +1 -1
- package/dist/server/routes/terminal.js +18 -7
- package/dist/server/routes/terminal.js.map +1 -1
- package/dist/server/session-registry.js +159 -32
- package/dist/server/session-registry.js.map +1 -1
- package/dist/server/sse-bridge.js +58 -29
- package/dist/server/sse-bridge.js.map +1 -1
- package/dist/server/subagents-external.js +319 -0
- package/dist/server/subagents-external.js.map +1 -0
- package/package.json +5 -4
- package/dist/client/assets/index-ChVHn55j.js.map +0 -1
- 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
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
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.
|
|
41
|
-
*
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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
|
-
//
|
|
71
|
-
//
|
|
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":"AA2BA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
106
|
-
|
|
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
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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,
|
|
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.
|
|
163
|
-
//
|
|
164
|
-
|
|
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
|
-
*
|
|
171
|
-
*
|
|
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
|
|
194
|
-
*
|
|
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":"AAAA
|
|
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 (
|
|
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:
|
|
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
|
|
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
|
|
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 () => {
|