@llblab/pi-actors 0.54.0 → 0.56.0
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/AGENTS.md +110 -20
- package/BACKLOG.md +207 -4
- package/CHANGELOG.md +17 -0
- package/README.md +33 -3
- package/dist/index.js +2 -1
- package/dist/lib/async-runs.d.ts +37 -0
- package/dist/lib/async-runs.js +131 -62
- package/dist/lib/automatic-review-runtime.d.ts +2 -0
- package/dist/lib/automatic-review-runtime.js +3 -2
- package/dist/lib/command-templates.d.ts +37 -0
- package/dist/lib/command-templates.js +263 -129
- package/dist/lib/execution-files.d.ts +32 -0
- package/dist/lib/execution-files.js +128 -0
- package/dist/lib/execution-output.js +5 -4
- package/dist/lib/execution-sessions.d.ts +19 -1
- package/dist/lib/execution-sessions.js +62 -26
- package/dist/lib/execution.d.ts +4 -1
- package/dist/lib/execution.js +6 -1
- package/dist/lib/extension-runtime.d.ts +10 -2
- package/dist/lib/extension-runtime.js +95 -14
- package/dist/lib/file-state.js +21 -2
- package/dist/lib/inspector-command.js +2 -2
- package/dist/lib/inspector-overlay.d.ts +25 -7
- package/dist/lib/inspector-overlay.js +562 -262
- package/dist/lib/inspector.d.ts +20 -10
- package/dist/lib/inspector.js +80 -91
- package/dist/lib/limits.d.ts +1 -0
- package/dist/lib/limits.js +1 -0
- package/dist/lib/observability.d.ts +15 -1
- package/dist/lib/observability.js +216 -99
- package/dist/lib/pi.d.ts +9 -3
- package/dist/lib/pi.js +80 -12
- package/dist/lib/recipe-projection.d.ts +18 -0
- package/dist/lib/recipe-projection.js +220 -0
- package/dist/lib/recipes-context.d.ts +2 -0
- package/dist/lib/recipes-context.js +30 -6
- package/dist/lib/registry.js +270 -36
- package/dist/lib/run-delivery.d.ts +213 -5
- package/dist/lib/run-delivery.js +1909 -40
- package/dist/lib/run-supervision.d.ts +107 -0
- package/dist/lib/run-supervision.js +412 -0
- package/dist/lib/run-ui-runtime.d.ts +10 -2
- package/dist/lib/run-ui-runtime.js +347 -124
- package/dist/lib/runs-artifacts.d.ts +26 -1
- package/dist/lib/runs-artifacts.js +68 -2
- package/dist/lib/runs-context-exports.d.ts +451 -0
- package/dist/lib/runs-context-exports.js +2315 -0
- package/dist/lib/runs-context-index.d.ts +97 -0
- package/dist/lib/runs-context-index.js +368 -0
- package/dist/lib/runs-control-delivery.js +46 -7
- package/dist/lib/runs-control.d.ts +78 -2
- package/dist/lib/runs-control.js +334 -2
- package/dist/lib/runs-controls.js +4 -1
- package/dist/lib/runs-descendant-launches.d.ts +80 -0
- package/dist/lib/runs-descendant-launches.js +287 -0
- package/dist/lib/runs-execution-evidence.d.ts +110 -0
- package/dist/lib/runs-execution-evidence.js +282 -0
- package/dist/lib/runs-index.d.ts +66 -1
- package/dist/lib/runs-index.js +349 -13
- package/dist/lib/runs-native-launches.d.ts +73 -0
- package/dist/lib/runs-native-launches.js +227 -0
- package/dist/lib/runs-outcomes.d.ts +141 -0
- package/dist/lib/runs-outcomes.js +292 -0
- package/dist/lib/runs-ownership.d.ts +10 -1
- package/dist/lib/runs-ownership.js +36 -4
- package/dist/lib/runs-process-windows-mi.d.ts +46 -0
- package/dist/lib/runs-process-windows-mi.js +114 -0
- package/dist/lib/runs-process.d.ts +34 -1
- package/dist/lib/runs-process.js +158 -26
- package/dist/lib/runs-results.d.ts +116 -0
- package/dist/lib/runs-results.js +518 -0
- package/dist/lib/runs-retention.d.ts +15 -2
- package/dist/lib/runs-retention.js +148 -9
- package/dist/lib/runs-session-exports.d.ts +95 -0
- package/dist/lib/runs-session-exports.js +470 -0
- package/dist/lib/runs-snapshot.d.ts +58 -0
- package/dist/lib/runs-snapshot.js +92 -0
- package/dist/lib/runs-start.d.ts +2 -0
- package/dist/lib/runs-start.js +44 -3
- package/dist/lib/runs-status.d.ts +15 -1
- package/dist/lib/runs-status.js +337 -13
- package/dist/lib/runs-supervision.d.ts +105 -0
- package/dist/lib/runs-supervision.js +501 -0
- package/dist/lib/runs-trace.d.ts +1 -0
- package/dist/lib/runs-trace.js +14 -3
- package/dist/lib/runtime.js +22 -3
- package/dist/lib/session-evidence.d.ts +37 -0
- package/dist/lib/session-evidence.js +59 -3
- package/dist/lib/state-readers.d.ts +4 -1
- package/dist/lib/state-readers.js +14 -8
- package/dist/lib/temp.d.ts +2 -4
- package/dist/lib/temp.js +4 -56
- package/dist/lib/tools-access.d.ts +3 -0
- package/dist/lib/tools-access.js +13 -0
- package/dist/lib/tools-inspect.d.ts +1 -0
- package/dist/lib/tools-inspect.js +110 -43
- package/dist/lib/tools-local.d.ts +3 -0
- package/dist/lib/tools-local.js +18 -6
- package/dist/lib/tools-message.d.ts +4 -1
- package/dist/lib/tools-message.js +126 -31
- package/dist/lib/tools-response.d.ts +31 -3
- package/dist/lib/tools-response.js +311 -47
- package/dist/lib/tools-spawn.d.ts +3 -0
- package/dist/lib/tools-spawn.js +29 -7
- package/dist/lib/trace-projection.d.ts +20 -0
- package/dist/lib/trace-projection.js +192 -43
- package/dist/scripts/async-runner.mjs +272 -194
- package/dist/scripts/conformance.mjs +14 -2
- package/dist/skills/actors/SKILL.md +47 -3
- package/dist/skills/actors/references/diagnostics.md +40 -0
- package/dist/skills/actors/references/runs.md +56 -2
- package/docs/README.md +9 -1
- package/docs/actor-inspector.md +84 -15
- package/docs/agent-experience.md +216 -0
- package/docs/async-runs.md +21 -3
- package/docs/composition-transport.md +103 -0
- package/docs/coordinator-delivery.md +226 -24
- package/docs/inspection.md +38 -2
- package/docs/output-projection-map.md +130 -0
- package/docs/releasing.md +19 -2
- package/docs/run-supervision.md +1622 -0
- package/docs/template-recipes.md +4 -0
- package/docs/windows-process-observation.md +248 -0
- package/index.ts +2 -1
- package/lib/async-runs.ts +164 -73
- package/lib/automatic-review-runtime.ts +4 -2
- package/lib/command-templates.ts +287 -138
- package/lib/execution-files.ts +118 -0
- package/lib/execution-output.ts +4 -4
- package/lib/execution-sessions.ts +57 -27
- package/lib/execution.ts +8 -0
- package/lib/extension-runtime.ts +106 -12
- package/lib/file-state.ts +16 -2
- package/lib/inspector-command.ts +2 -2
- package/lib/inspector-overlay.ts +524 -261
- package/lib/inspector.ts +101 -130
- package/lib/limits.ts +1 -0
- package/lib/observability.ts +235 -116
- package/lib/pi.ts +89 -17
- package/lib/recipe-projection.ts +232 -0
- package/lib/recipes-context.ts +27 -6
- package/lib/registry.ts +266 -36
- package/lib/run-delivery.ts +1737 -49
- package/lib/run-supervision.ts +411 -0
- package/lib/run-ui-runtime.ts +293 -126
- package/lib/runs-artifacts.ts +73 -1
- package/lib/runs-context-exports.ts +1871 -0
- package/lib/runs-context-index.ts +372 -0
- package/lib/runs-control-delivery.ts +36 -18
- package/lib/runs-control.ts +297 -1
- package/lib/runs-controls.ts +3 -1
- package/lib/runs-descendant-launches.ts +338 -0
- package/lib/runs-execution-evidence.ts +302 -0
- package/lib/runs-index.ts +345 -17
- package/lib/runs-native-launches.ts +270 -0
- package/lib/runs-outcomes.ts +377 -0
- package/lib/runs-ownership.ts +37 -4
- package/lib/runs-process-windows-mi.ts +113 -0
- package/lib/runs-process.ts +210 -34
- package/lib/runs-results.ts +601 -0
- package/lib/runs-retention.ts +134 -10
- package/lib/runs-session-exports.ts +406 -0
- package/lib/runs-snapshot.ts +143 -0
- package/lib/runs-start.ts +44 -2
- package/lib/runs-status.ts +252 -10
- package/lib/runs-supervision.ts +394 -0
- package/lib/runs-trace.ts +14 -3
- package/lib/runtime.ts +22 -3
- package/lib/session-evidence.ts +76 -4
- package/lib/state-readers.ts +16 -7
- package/lib/temp.ts +3 -56
- package/lib/tools-access.ts +13 -0
- package/lib/tools-inspect.ts +110 -51
- package/lib/tools-local.ts +37 -24
- package/lib/tools-message.ts +120 -37
- package/lib/tools-response.ts +273 -50
- package/lib/tools-spawn.ts +31 -10
- package/lib/trace-projection.ts +165 -42
- package/package.json +10 -6
- package/scripts/async-runner.mjs +272 -194
- package/scripts/conformance.mjs +14 -2
- package/skills/actors/SKILL.md +47 -3
- package/skills/actors/references/diagnostics.md +40 -0
- package/skills/actors/references/runs.md +56 -2
- package/banner.jpg +0 -0
- package/dist/lib/inspector-actions.d.ts +0 -16
- package/dist/lib/inspector-actions.js +0 -57
- package/lib/inspector-actions.ts +0 -79
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ambient run observability runtime.
|
|
3
3
|
* Zones: run watcher lifecycle, terminal reconciliation, status animation, shutdown teardown
|
|
4
|
-
* Owns event-driven run UI coordination
|
|
4
|
+
* Owns event-driven run UI/delivery coordination and callback-generation fences; excludes actor execution semantics and public stream activation.
|
|
5
5
|
*/
|
|
6
|
+
import { Container, Markdown, Spacer, Text, stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
6
7
|
import * as AsyncRuns from "./async-runs.js";
|
|
7
8
|
import * as Limits from "./limits.js";
|
|
8
9
|
import * as Observability from "./observability.js";
|
|
@@ -10,10 +11,101 @@ import * as Paths from "./paths.js";
|
|
|
10
11
|
import * as Pi from "./pi.js";
|
|
11
12
|
import * as RunDelivery from "./run-delivery.js";
|
|
12
13
|
import * as RunDeliveryLineage from "./run-delivery-lineage.js";
|
|
14
|
+
import * as RunIndex from "./runs-index.js";
|
|
15
|
+
import { formatLiteralBlock, formatMarkdownField } from "./tools-response.js";
|
|
16
|
+
function formatSteerDiagnosticMessage(eventId, stage, message) {
|
|
17
|
+
const fields = [formatMarkdownField("event", eventId), formatMarkdownField("attention", "steer"),
|
|
18
|
+
formatMarkdownField("stage", stage), formatMarkdownField("result", stage === "admission" ? "remains retryable" : "request failed")];
|
|
19
|
+
if (stage === "delivery")
|
|
20
|
+
fields.push(formatMarkdownField("delivery", "unconfirmed"), formatMarkdownField("presentation", "unconfirmed"));
|
|
21
|
+
let end = Math.min(message.length, 240);
|
|
22
|
+
const last = message.charCodeAt(end - 1), next = message.charCodeAt(end);
|
|
23
|
+
if (last >= 0xd800 && last <= 0xdbff && next >= 0xdc00 && next <= 0xdfff)
|
|
24
|
+
end -= 1;
|
|
25
|
+
const preview = message.slice(0, end), clipped = end < message.length;
|
|
26
|
+
if (Buffer.from(preview).toString("utf8") !== preview) {
|
|
27
|
+
fields.push(formatMarkdownField("diagnostic", "unavailable"), formatMarkdownField("diagnostic_reason", "malformed legacy preview"), formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
fields.push(formatMarkdownField("diagnostic", preview + (clipped ? "…" : "")));
|
|
31
|
+
if (clipped)
|
|
32
|
+
fields.push(formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
|
|
33
|
+
}
|
|
34
|
+
return fields.join("\n");
|
|
35
|
+
}
|
|
36
|
+
/** Transcript-only view of the exact delivered content; never re-read a Run or infer body labels. */
|
|
37
|
+
export function registerRunCompletionRenderer(pi) {
|
|
38
|
+
pi.registerMessageRenderer(Pi.RUN_COMPLETION_BATCH_CUSTOM_TYPE, (message, { expanded, outputPad }, theme) => {
|
|
39
|
+
if (typeof message.content !== "string")
|
|
40
|
+
return undefined;
|
|
41
|
+
let presentation;
|
|
42
|
+
try {
|
|
43
|
+
presentation = Object.getOwnPropertyDescriptor(message.details, "pi_actors_presentation")?.value;
|
|
44
|
+
}
|
|
45
|
+
catch { /* no layout hint */ }
|
|
46
|
+
const view = RunDelivery.readRunCompletionPresentation(message.content, presentation);
|
|
47
|
+
if (!view)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (!expanded && view.segments) {
|
|
50
|
+
const component = new Container(), markdownTheme = { ...Pi.getMarkdownTheme(), codeBlockIndent: "" };
|
|
51
|
+
component.addChild(new Spacer(1));
|
|
52
|
+
let pieces = 0;
|
|
53
|
+
for (const segment of view.segments) {
|
|
54
|
+
// Only compiler separator/marker chunks drive spacing; whitespace bodies remain Markdown.
|
|
55
|
+
if (segment.kind === "separator" || (segment.kind === "marker" && /^\n+$/u.test(segment.text)))
|
|
56
|
+
continue;
|
|
57
|
+
if (pieces++)
|
|
58
|
+
component.addChild(new Spacer(1));
|
|
59
|
+
const text = stripTerminalSequences(segment.text).replaceAll(/\r\n|\r/gu, "\n");
|
|
60
|
+
component.addChild(segment.kind === "body"
|
|
61
|
+
? new Markdown(text, outputPad, 0, markdownTheme, { color: value => theme.fg("customMessageText", value) })
|
|
62
|
+
: new Text(theme.fg("muted", text), outputPad, 0));
|
|
63
|
+
}
|
|
64
|
+
component.addChild(new Spacer(1));
|
|
65
|
+
component.addChild(new Text(theme.fg("muted", "Expand message for technical details."), outputPad, 0));
|
|
66
|
+
component.addChild(new Spacer(1));
|
|
67
|
+
return component;
|
|
68
|
+
}
|
|
69
|
+
if (!expanded && view.blocks) {
|
|
70
|
+
const component = new Container(), markdownTheme = { ...Pi.getMarkdownTheme(), codeBlockIndent: "" };
|
|
71
|
+
component.addChild(new Spacer(1));
|
|
72
|
+
for (const [index, [kind, start, end]] of view.blocks.entries()) {
|
|
73
|
+
if (index)
|
|
74
|
+
component.addChild(new Spacer(1));
|
|
75
|
+
let text = stripTerminalSequences(message.content.slice(start, end)).replaceAll(/\r\n|\r/gu, "\n");
|
|
76
|
+
if (kind === "header") {
|
|
77
|
+
// Only validated compiler headers may hide whole reference rows; body lookalikes stay visible.
|
|
78
|
+
text = text.split("\n").filter(line => !line.startsWith("references: ") && !line.startsWith("run_files: ")).join("\n").replace(/\n$/u, "");
|
|
79
|
+
}
|
|
80
|
+
component.addChild(kind === "body"
|
|
81
|
+
? new Markdown(text, outputPad, 0, markdownTheme, { color: value => theme.fg("customMessageText", value) })
|
|
82
|
+
: new Text(theme.fg(kind === "header" || kind === "marker" ? "muted" : "customMessageText", text), outputPad, 0));
|
|
83
|
+
}
|
|
84
|
+
component.addChild(new Spacer(1));
|
|
85
|
+
component.addChild(new Text(theme.fg("muted", "Expand message for technical details."), outputPad, 0));
|
|
86
|
+
component.addChild(new Spacer(1));
|
|
87
|
+
return component;
|
|
88
|
+
}
|
|
89
|
+
const text = expanded ? message.content : view.text;
|
|
90
|
+
const label = view.yaml || (view.records && !view.single) ? "" : theme.fg("customMessageLabel", view.single ? "Run result" : "Run Batch") + "\n\n";
|
|
91
|
+
return new Text(label + theme.fg("customMessageText", stripTerminalSequences(text).replaceAll(/\r\n|\r/gu, "\n"))
|
|
92
|
+
+ (!expanded && view.compact ? "\n\n" + theme.fg("muted", "Expand message for technical details.") : ""), outputPad, 1);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
13
95
|
export function createRunUiRuntime(deps) {
|
|
96
|
+
if (deps.captureCompletionBatch && deps.completionStreamSessionAdmission)
|
|
97
|
+
throw new Error("Candidate completion capture cannot use completion streams");
|
|
98
|
+
const fencedCompletionSession = Boolean(deps.captureCompletionBatch || deps.completionStreamSessionAdmission);
|
|
14
99
|
let activeContext;
|
|
15
100
|
let activeOwnerId;
|
|
101
|
+
let sessionTicket;
|
|
102
|
+
let sessionManager;
|
|
103
|
+
let streamSessionAdmitted = false;
|
|
104
|
+
const contextTickets = new WeakMap();
|
|
105
|
+
const seenManagers = new WeakSet();
|
|
16
106
|
let animationInterval;
|
|
107
|
+
let displayRunningSubagents = 0;
|
|
108
|
+
let displayFrame = 0;
|
|
17
109
|
let deliveryTimeout;
|
|
18
110
|
let notifyTimeout;
|
|
19
111
|
let recoverQueuedSteers = false;
|
|
@@ -23,8 +115,14 @@ export function createRunUiRuntime(deps) {
|
|
|
23
115
|
const deliveryObservation = Observability.createRunUiObservationState();
|
|
24
116
|
const retirementAttempts = new Set();
|
|
25
117
|
const steerDiagnostics = new Set();
|
|
118
|
+
const completionDiagnostics = new Set();
|
|
26
119
|
const close = () => {
|
|
27
120
|
running = false;
|
|
121
|
+
displayRunningSubagents = 0;
|
|
122
|
+
displayFrame = 0;
|
|
123
|
+
sessionTicket = undefined;
|
|
124
|
+
sessionManager = undefined;
|
|
125
|
+
streamSessionAdmitted = false;
|
|
28
126
|
activeContext = undefined;
|
|
29
127
|
activeOwnerId = undefined;
|
|
30
128
|
try {
|
|
@@ -47,6 +145,7 @@ export function createRunUiRuntime(deps) {
|
|
|
47
145
|
deliveryTimeout = undefined;
|
|
48
146
|
recoverQueuedSteers = false;
|
|
49
147
|
steerDiagnostics.clear();
|
|
148
|
+
completionDiagnostics.clear();
|
|
50
149
|
if (animationInterval)
|
|
51
150
|
clearInterval(animationInterval);
|
|
52
151
|
animationInterval = undefined;
|
|
@@ -63,14 +162,15 @@ export function createRunUiRuntime(deps) {
|
|
|
63
162
|
}
|
|
64
163
|
const message = error instanceof Error ? error.message : String(error);
|
|
65
164
|
try {
|
|
66
|
-
expectedContext.ui.notify(
|
|
165
|
+
expectedContext.ui.notify([formatMarkdownField("callback", label), formatMarkdownField("result", "failed"),
|
|
166
|
+
formatMarkdownField("diagnostic", message)].join("\n"), "error");
|
|
67
167
|
}
|
|
68
168
|
catch {
|
|
69
169
|
/* stale context or unavailable UI */
|
|
70
170
|
}
|
|
71
171
|
};
|
|
72
|
-
const runActiveCallback = (label, callback) => {
|
|
73
|
-
if (!running || !activeContext || !activeOwnerId)
|
|
172
|
+
const runActiveCallback = (label, callback, expectedTicket) => {
|
|
173
|
+
if (expectedTicket !== sessionTicket || !running || !activeContext || !activeOwnerId)
|
|
74
174
|
return;
|
|
75
175
|
const ctx = activeContext;
|
|
76
176
|
try {
|
|
@@ -79,7 +179,8 @@ export function createRunUiRuntime(deps) {
|
|
|
79
179
|
callback(ctx, activeOwnerId);
|
|
80
180
|
}
|
|
81
181
|
catch (error) {
|
|
82
|
-
|
|
182
|
+
if (expectedTicket === sessionTicket)
|
|
183
|
+
stopAfterCallbackFailure(label, error, ctx);
|
|
83
184
|
}
|
|
84
185
|
};
|
|
85
186
|
const retireCandidateRuns = (ctx, summary) => {
|
|
@@ -124,17 +225,6 @@ export function createRunUiRuntime(deps) {
|
|
|
124
225
|
finishPresentedSteer(ownerId, steer);
|
|
125
226
|
}
|
|
126
227
|
};
|
|
127
|
-
const boundedSteerContent = (event) => {
|
|
128
|
-
const text = Observability.formatRunAttentionMessage(event);
|
|
129
|
-
if (Buffer.byteLength(text, "utf8") <= Limits.RUN_DELIVERY_STEER_MAX_BYTES) {
|
|
130
|
-
return text;
|
|
131
|
-
}
|
|
132
|
-
let end = Math.min(text.length, Limits.RUN_DELIVERY_STEER_MAX_BYTES - 3);
|
|
133
|
-
while (end > 0 && Buffer.byteLength(text.slice(0, end), "utf8") >
|
|
134
|
-
Limits.RUN_DELIVERY_STEER_MAX_BYTES - 3)
|
|
135
|
-
end -= 1;
|
|
136
|
-
return `${text.slice(0, end)}…`;
|
|
137
|
-
};
|
|
138
228
|
const admitSteerEvents = (ctx, ownerId, events) => {
|
|
139
229
|
for (const event of events.filter(Observability.isRunSteerAttentionEvent)) {
|
|
140
230
|
if (!event.runInstanceId) {
|
|
@@ -142,13 +232,16 @@ export function createRunUiRuntime(deps) {
|
|
|
142
232
|
const key = `${event.stateDir}:${event.id}:missing_generation`;
|
|
143
233
|
if (!steerDiagnostics.has(key)) {
|
|
144
234
|
steerDiagnostics.add(key);
|
|
145
|
-
ctx.ui.notify(
|
|
235
|
+
ctx.ui.notify(formatSteerDiagnosticMessage(event.id, "admission", "Run generation unavailable."), "warning");
|
|
146
236
|
}
|
|
147
237
|
continue;
|
|
148
238
|
}
|
|
149
239
|
try {
|
|
240
|
+
if (journal(ownerId).steers.some(steer => steer.run === event.run && steer.state_dir === event.stateDir &&
|
|
241
|
+
steer.run_instance_id === event.runInstanceId && steer.event_id === event.id))
|
|
242
|
+
continue;
|
|
150
243
|
RunDelivery.admitRunSteerEnvelope({
|
|
151
|
-
content:
|
|
244
|
+
content: Observability.formatRunSteerMessage(event),
|
|
152
245
|
eventId: event.id,
|
|
153
246
|
kind: event.kind,
|
|
154
247
|
level: event.level,
|
|
@@ -166,25 +259,132 @@ export function createRunUiRuntime(deps) {
|
|
|
166
259
|
const key = `${event.stateDir}:${event.id}:${message}`;
|
|
167
260
|
if (!steerDiagnostics.has(key)) {
|
|
168
261
|
steerDiagnostics.add(key);
|
|
169
|
-
ctx.ui.notify(
|
|
262
|
+
ctx.ui.notify(formatSteerDiagnosticMessage(event.id, "admission", message), "warning");
|
|
170
263
|
}
|
|
171
264
|
}
|
|
172
265
|
}
|
|
173
266
|
};
|
|
174
|
-
const admitCompletionTransitions = (ownerId, snapshot) => {
|
|
267
|
+
const admitCompletionTransitions = (ctx, ownerId, snapshot) => {
|
|
175
268
|
const existing = journal(ownerId).completion_batch;
|
|
176
269
|
if (existing)
|
|
177
270
|
return true;
|
|
178
271
|
const members = Observability.collectRunCompletionBatchMembers(snapshot.transitions, snapshot.summary.runs).slice(0, Limits.RUN_DELIVERY_BATCH_MAX_MEMBERS);
|
|
179
|
-
if (members.length === 0)
|
|
272
|
+
if (members.length === 0 || !isCurrentOwner(ctx))
|
|
273
|
+
return false;
|
|
274
|
+
const fileNavigation = [];
|
|
275
|
+
for (const member of RunDelivery.orderRunCompletionMembers(members, true)) {
|
|
276
|
+
let locations;
|
|
277
|
+
try {
|
|
278
|
+
const observed = snapshot.summary.runs.find(run => run.run === member.run && run.runInstanceId === member.run_instance_id && run.stateDir === member.state_dir);
|
|
279
|
+
locations = !observed ? Object.freeze({ state: "unavailable", reason: "identity_unavailable" })
|
|
280
|
+
: observed.ownerId !== ownerId ? Object.freeze({ state: "unavailable", reason: "session_mismatch" })
|
|
281
|
+
: RunIndex.readRunDirectoryNavigation(member.state_dir, { run: member.run, run_instance_id: member.run_instance_id, ownerId: observed.ownerId }, Pi.getSessionId(ctx));
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
locations = Object.freeze({ state: "unavailable", reason: "navigation_unavailable" });
|
|
285
|
+
}
|
|
286
|
+
fileNavigation.push({ member_index: members.indexOf(member), locations });
|
|
287
|
+
if (Buffer.byteLength(JSON.stringify(fileNavigation), "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) {
|
|
288
|
+
fileNavigation.pop();
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (!isCurrentOwner(ctx))
|
|
180
293
|
return false;
|
|
181
|
-
|
|
294
|
+
if (!canUseCompletionSession(ctx, ownerId))
|
|
295
|
+
return false;
|
|
296
|
+
const capture = deps.completionStreamSessionAdmission ? RunDelivery.captureRunCompletionStreamBatch : deps.captureCompletionBatch ?? RunDelivery.admitRunCompletionBatch;
|
|
297
|
+
capture({
|
|
182
298
|
members,
|
|
299
|
+
fileNavigation,
|
|
183
300
|
ownerId,
|
|
184
301
|
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
185
302
|
});
|
|
186
303
|
return true;
|
|
187
304
|
};
|
|
305
|
+
const acceptCompletionBatch = (ownerId, batchId) => {
|
|
306
|
+
const input = { batchId, ownerId, tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir };
|
|
307
|
+
if (!RunDelivery.markRunCompletionBatchQueued(input))
|
|
308
|
+
return false;
|
|
309
|
+
if (!RunDelivery.markRunCompletionBatchPresented(input))
|
|
310
|
+
return false;
|
|
311
|
+
const accepted = journal(ownerId).completion_batch;
|
|
312
|
+
return accepted ? finishPresentedBatch(ownerId, accepted) : false;
|
|
313
|
+
};
|
|
314
|
+
const reconcileCompletionBatch = (ctx, ownerId) => {
|
|
315
|
+
const batch = journal(ownerId).completion_batch;
|
|
316
|
+
if (!batch)
|
|
317
|
+
return false;
|
|
318
|
+
if (batch.phase === "pending") {
|
|
319
|
+
// Returning boundary drafts is not acceptance: prove the exact entry before acknowledging it.
|
|
320
|
+
const evidence = Pi.inspectRunCompletionBatchSessionEvidence(ctx, {
|
|
321
|
+
batchId: batch.batch_id,
|
|
322
|
+
content: RunDelivery.formatRunCompletionBatchMessage(batch),
|
|
323
|
+
});
|
|
324
|
+
if (evidence.status === "absent")
|
|
325
|
+
return false;
|
|
326
|
+
if (evidence.status !== "present") {
|
|
327
|
+
const key = `${batch.batch_id}:${evidence.status}:${evidence.reason ?? ""}`;
|
|
328
|
+
if (!completionDiagnostics.has(key)) {
|
|
329
|
+
completionDiagnostics.add(key);
|
|
330
|
+
ctx.ui.notify("\n" + [formatMarkdownField("batch", batch.batch_id), formatMarkdownField("stage", "completion recovery"),
|
|
331
|
+
formatMarkdownField("evidence", evidence.status), formatMarkdownField("phase", "pending"),
|
|
332
|
+
formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"), "warning");
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (fencedCompletionSession && !canUseCompletionSession(ctx, ownerId))
|
|
338
|
+
return true;
|
|
339
|
+
return !acceptCompletionBatch(ownerId, batch.batch_id);
|
|
340
|
+
};
|
|
341
|
+
const pendingCompletionBatch = (ctx, ownerId) => {
|
|
342
|
+
const existing = journal(ownerId).completion_batch;
|
|
343
|
+
if (existing)
|
|
344
|
+
return existing;
|
|
345
|
+
const snapshot = Observability.readRunDeliverySnapshot(deliveryObservation, ownerId);
|
|
346
|
+
admitCompletionTransitions(ctx, ownerId, snapshot);
|
|
347
|
+
Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
|
|
348
|
+
return journal(ownerId).completion_batch;
|
|
349
|
+
};
|
|
350
|
+
const isCurrentOwner = (ctx) => {
|
|
351
|
+
if (!running || !activeOwnerId)
|
|
352
|
+
return false;
|
|
353
|
+
if (!fencedCompletionSession) {
|
|
354
|
+
try {
|
|
355
|
+
return Pi.getSessionId(ctx) === activeOwnerId;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const ticket = sessionTicket, ownerId = activeOwnerId, known = contextTickets.get(ctx);
|
|
362
|
+
if (!ticket || (known && known !== ticket))
|
|
363
|
+
return false;
|
|
364
|
+
try {
|
|
365
|
+
if (deps.getActiveContext() !== activeContext)
|
|
366
|
+
return false;
|
|
367
|
+
const manager = ctx.sessionManager;
|
|
368
|
+
if (manager !== sessionManager || manager.getSessionId() !== ownerId || ticket !== sessionTicket || !running)
|
|
369
|
+
return false;
|
|
370
|
+
contextTickets.set(ctx, ticket);
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
const canUseCompletionSession = (ctx, ownerId) => {
|
|
378
|
+
if (deps.captureCompletionBatch && !isCurrentOwner(ctx))
|
|
379
|
+
return false;
|
|
380
|
+
if (!deps.completionStreamSessionAdmission)
|
|
381
|
+
return !journal(ownerId).completion_stream;
|
|
382
|
+
if (!isCurrentOwner(ctx))
|
|
383
|
+
return false;
|
|
384
|
+
const ticket = sessionTicket, state = journal(ownerId).completion_stream;
|
|
385
|
+
// No metadata is not disconnected. The opt-in path never bootstraps or falls back to local indices.
|
|
386
|
+
return streamSessionAdmitted && Boolean(state) && ticket === sessionTicket && isCurrentOwner(ctx);
|
|
387
|
+
};
|
|
188
388
|
const isIdle = (ctx) => typeof ctx.isIdle !== "function" || ctx.isIdle();
|
|
189
389
|
const isDeliveryRoot = (ownerId) => {
|
|
190
390
|
const inheritedOwner = RunDeliveryLineage.getProcessDeliveryOwnerId();
|
|
@@ -213,7 +413,9 @@ export function createRunUiRuntime(deps) {
|
|
|
213
413
|
const key = `${steer.steer_id}:${evidence.status}:${evidence.reason ?? ""}`;
|
|
214
414
|
if (!steerDiagnostics.has(key)) {
|
|
215
415
|
steerDiagnostics.add(key);
|
|
216
|
-
ctx.ui.notify(
|
|
416
|
+
ctx.ui.notify([formatMarkdownField("event", steer.event_id), formatMarkdownField("attention", "steer"),
|
|
417
|
+
formatMarkdownField("stage", "recovery"), formatMarkdownField("evidence", evidence.status),
|
|
418
|
+
formatMarkdownField("phase", "queued"), formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"), "warning");
|
|
217
419
|
}
|
|
218
420
|
continue;
|
|
219
421
|
}
|
|
@@ -242,7 +444,7 @@ export function createRunUiRuntime(deps) {
|
|
|
242
444
|
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
243
445
|
});
|
|
244
446
|
const message = error instanceof Error ? error.message : String(error);
|
|
245
|
-
ctx.ui.notify(
|
|
447
|
+
ctx.ui.notify(formatSteerDiagnosticMessage(steer.event_id, "delivery", message), "error");
|
|
246
448
|
continue;
|
|
247
449
|
}
|
|
248
450
|
RunDelivery.markRunSteerQueued({
|
|
@@ -257,28 +459,45 @@ export function createRunUiRuntime(deps) {
|
|
|
257
459
|
const scheduleCompletionFlush = () => {
|
|
258
460
|
if (!running)
|
|
259
461
|
return;
|
|
462
|
+
const ticket = sessionTicket;
|
|
260
463
|
if (deliveryTimeout)
|
|
261
464
|
clearTimeout(deliveryTimeout);
|
|
262
465
|
deliveryTimeout = setTimeout(() => {
|
|
466
|
+
if (ticket !== sessionTicket)
|
|
467
|
+
return;
|
|
263
468
|
deliveryTimeout = undefined;
|
|
264
469
|
runActiveCallback("completion delivery callback", (ctx) => {
|
|
265
470
|
flushCompletionBatch(ctx);
|
|
266
|
-
});
|
|
471
|
+
}, ticket);
|
|
267
472
|
}, deps.deliveryDebounceMs ?? 100);
|
|
268
473
|
deliveryTimeout.unref?.();
|
|
269
474
|
};
|
|
270
475
|
const update = (ctx, ownerId, notify = false, terminalOnly = false) => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
476
|
+
// Derive both projections before any UI/retirement callback. The subsequent
|
|
477
|
+
// flush still observes afresh; these facts only drive display/candidate scheduling.
|
|
478
|
+
const ticket = sessionTicket;
|
|
479
|
+
const pair = notify && isDeliveryRoot(ownerId)
|
|
480
|
+
? Observability.readRunUiAndDeliverySnapshots(observation, deliveryObservation, ownerId)
|
|
481
|
+
: undefined;
|
|
482
|
+
const snapshot = pair?.ui ?? Observability.readRunUiSnapshot(observation, ownerId);
|
|
483
|
+
if (ticket !== sessionTicket || (fencedCompletionSession && !isCurrentOwner(ctx)))
|
|
484
|
+
return false;
|
|
485
|
+
// Animation retains display data only; all admission paths continue observing independently.
|
|
486
|
+
displayRunningSubagents = snapshot.summary.runningSubagents;
|
|
487
|
+
displayFrame = 0;
|
|
488
|
+
const status = snapshot.status && ctx.mode === "tui" ? ctx.ui.theme.fg("dim", snapshot.status) : snapshot.status;
|
|
489
|
+
if (ticket !== sessionTicket)
|
|
490
|
+
return false;
|
|
491
|
+
ctx.ui.setStatus("zz-pi-actors-runs", status);
|
|
492
|
+
if (ticket !== sessionTicket)
|
|
493
|
+
return false;
|
|
494
|
+
if (!notify || (fencedCompletionSession && !isCurrentOwner(ctx)))
|
|
274
495
|
return false;
|
|
275
496
|
const sink = Pi.createNotificationSink(deps.pi, ctx);
|
|
276
497
|
retireCandidateRuns(ctx, snapshot.summary);
|
|
277
|
-
const deliverySnapshot =
|
|
278
|
-
? Observability.readRunDeliverySnapshot(deliveryObservation, ownerId)
|
|
279
|
-
: undefined;
|
|
498
|
+
const deliverySnapshot = pair?.delivery;
|
|
280
499
|
const hasCompletionCandidates = deliverySnapshot
|
|
281
|
-
? Observability.
|
|
500
|
+
? Observability.hasRunCompletionBatchMembers(deliverySnapshot.transitions, deliverySnapshot.summary.runs)
|
|
282
501
|
: false;
|
|
283
502
|
const hasCompletionBatch = isDeliveryRoot(ownerId) &&
|
|
284
503
|
Boolean(journal(ownerId).completion_batch);
|
|
@@ -304,30 +523,35 @@ export function createRunUiRuntime(deps) {
|
|
|
304
523
|
ctx.ui.notify(diagnostic.message, diagnostic.code === "rearmed" ? "info" : "warning");
|
|
305
524
|
}
|
|
306
525
|
};
|
|
307
|
-
const scheduleUpdate = () => {
|
|
308
|
-
if (!running)
|
|
526
|
+
const scheduleUpdate = (ticket) => {
|
|
527
|
+
if (ticket !== sessionTicket || !running)
|
|
309
528
|
return;
|
|
310
529
|
if (notifyTimeout)
|
|
311
530
|
clearTimeout(notifyTimeout);
|
|
312
531
|
notifyTimeout = setTimeout(() => {
|
|
532
|
+
if (ticket !== sessionTicket)
|
|
533
|
+
return;
|
|
313
534
|
runActiveCallback("Run watcher callback", (ctx, ownerId) => {
|
|
314
535
|
watcher.refresh();
|
|
315
536
|
const completionDeferred = update(ctx, ownerId, true);
|
|
537
|
+
if (ticket !== sessionTicket)
|
|
538
|
+
return;
|
|
316
539
|
if (!completionDeferred)
|
|
317
540
|
deps.onRunEvent();
|
|
318
|
-
|
|
319
|
-
|
|
541
|
+
if (ticket === sessionTicket)
|
|
542
|
+
reportDiagnostics(ctx);
|
|
543
|
+
}, ticket);
|
|
320
544
|
}, deps.notificationDelayMs ?? 50);
|
|
321
545
|
notifyTimeout.unref?.();
|
|
322
546
|
};
|
|
323
|
-
const
|
|
547
|
+
const createWatcher = (ticket) => (deps.createRunStateWatcher ?? Observability.createRunStateWatcher)({
|
|
324
548
|
stateRoot: Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
|
|
325
|
-
onChange: scheduleUpdate,
|
|
549
|
+
onChange: () => scheduleUpdate(ticket),
|
|
326
550
|
});
|
|
327
|
-
const
|
|
551
|
+
const createReconciliation = (ticket) => (deps.createRunTerminalReconciliationLoop ??
|
|
328
552
|
Observability.createRunTerminalReconciliationLoop)({
|
|
329
553
|
onError: (error) => {
|
|
330
|
-
if (!running || !activeContext)
|
|
554
|
+
if (ticket !== sessionTicket || !running || !activeContext)
|
|
331
555
|
return;
|
|
332
556
|
stopAfterCallbackFailure("terminal reconciliation callback", error, activeContext);
|
|
333
557
|
},
|
|
@@ -335,67 +559,41 @@ export function createRunUiRuntime(deps) {
|
|
|
335
559
|
runActiveCallback("terminal reconciliation callback", (ctx, ownerId) => {
|
|
336
560
|
update(ctx, ownerId, true);
|
|
337
561
|
reportDiagnostics(ctx);
|
|
338
|
-
});
|
|
562
|
+
}, ticket);
|
|
339
563
|
},
|
|
340
564
|
refreshWatcher: () => {
|
|
341
|
-
if (running)
|
|
565
|
+
if (ticket === sessionTicket && running)
|
|
342
566
|
watcher.refresh();
|
|
343
567
|
},
|
|
344
568
|
});
|
|
569
|
+
let watcher = createWatcher(undefined);
|
|
570
|
+
let reconciliation = createReconciliation(undefined);
|
|
345
571
|
flushCompletionBatch = (ctx) => {
|
|
346
572
|
const ownerId = activeOwnerId;
|
|
347
|
-
if (!
|
|
348
|
-
return false;
|
|
349
|
-
try {
|
|
350
|
-
if (Pi.getSessionId(ctx) !== ownerId)
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
catch {
|
|
573
|
+
if (!ownerId || !isCurrentOwner(ctx))
|
|
354
574
|
return false;
|
|
355
|
-
}
|
|
356
575
|
if (flushSteers(ctx, ownerId))
|
|
357
576
|
return true;
|
|
358
577
|
if (!isDeliveryRoot(ownerId))
|
|
359
578
|
return false;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
batch = journal(ownerId).completion_batch;
|
|
366
|
-
}
|
|
579
|
+
if (!canUseCompletionSession(ctx, ownerId))
|
|
580
|
+
return true;
|
|
581
|
+
if (reconcileCompletionBatch(ctx, ownerId))
|
|
582
|
+
return true;
|
|
583
|
+
const batch = pendingCompletionBatch(ctx, ownerId);
|
|
367
584
|
if (!batch)
|
|
368
585
|
return false;
|
|
369
|
-
if (batch.phase === "presented") {
|
|
370
|
-
finishPresentedBatch(ownerId, batch);
|
|
371
|
-
const snapshot = Observability.readRunDeliverySnapshot(deliveryObservation, ownerId);
|
|
372
|
-
admitCompletionTransitions(ownerId, snapshot);
|
|
373
|
-
Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
|
|
374
|
-
batch = journal(ownerId).completion_batch;
|
|
375
|
-
if (!batch)
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
586
|
if (!isIdle(ctx))
|
|
379
587
|
return true;
|
|
380
|
-
const
|
|
381
|
-
if (
|
|
382
|
-
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
383
|
-
batchId: batch.batch_id,
|
|
384
|
-
ownerId,
|
|
385
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
386
|
-
}))
|
|
387
|
-
return true;
|
|
388
|
-
const accepted = journal(ownerId).completion_batch;
|
|
389
|
-
if (accepted)
|
|
390
|
-
finishPresentedBatch(ownerId, accepted);
|
|
391
|
-
return flushCompletionBatch(ctx);
|
|
392
|
-
}
|
|
393
|
-
if (!isIdle(ctx))
|
|
588
|
+
const projected = RunDelivery.projectRunCompletionBatchPresentation(batch);
|
|
589
|
+
if (!isIdle(ctx) || !isCurrentOwner(ctx))
|
|
394
590
|
return true;
|
|
395
591
|
try {
|
|
396
|
-
Pi.sendRunCompletionBatch(deps.pi, batch.batch_id, content);
|
|
592
|
+
Pi.sendRunCompletionBatch(deps.pi, batch.batch_id, projected.content, projected.presentation);
|
|
397
593
|
}
|
|
398
594
|
catch (error) {
|
|
595
|
+
if (fencedCompletionSession && !isCurrentOwner(ctx))
|
|
596
|
+
throw error;
|
|
399
597
|
RunDelivery.recordRunCompletionBatchDeliveryFailure({
|
|
400
598
|
batchId: batch.batch_id,
|
|
401
599
|
error,
|
|
@@ -404,40 +602,38 @@ export function createRunUiRuntime(deps) {
|
|
|
404
602
|
});
|
|
405
603
|
throw error;
|
|
406
604
|
}
|
|
407
|
-
if (!
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
411
|
-
})) {
|
|
412
|
-
throw new Error("Completion batch changed before queue acknowledgment");
|
|
413
|
-
}
|
|
414
|
-
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
415
|
-
batchId: batch.batch_id,
|
|
416
|
-
ownerId,
|
|
417
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
418
|
-
})) {
|
|
605
|
+
if (fencedCompletionSession && !canUseCompletionSession(ctx, ownerId))
|
|
606
|
+
return true;
|
|
607
|
+
if (!acceptCompletionBatch(ownerId, batch.batch_id)) {
|
|
419
608
|
throw new Error("Completion batch changed before durable acceptance");
|
|
420
609
|
}
|
|
421
|
-
const accepted = journal(ownerId).completion_batch;
|
|
422
|
-
if (accepted)
|
|
423
|
-
finishPresentedBatch(ownerId, accepted);
|
|
424
|
-
ctx.ui.notify(`Actor completions ready: ${batch.members.length}`, "info");
|
|
425
610
|
return true;
|
|
426
611
|
};
|
|
427
612
|
return {
|
|
428
613
|
close,
|
|
614
|
+
isCurrentSessionContext: isCurrentOwner,
|
|
429
615
|
flushCompletionBatch,
|
|
616
|
+
onTurnEnd(event, ctx) {
|
|
617
|
+
const ownerId = activeOwnerId;
|
|
618
|
+
if (!ownerId || !isCurrentOwner(ctx) || !isDeliveryRoot(ownerId) ||
|
|
619
|
+
event.outcome !== "completed" || isIdle(ctx) || ctx.signal?.aborted ||
|
|
620
|
+
ctx.hasPendingMessages?.() || event.context.pendingMessages.length > 0)
|
|
621
|
+
return undefined;
|
|
622
|
+
if (flushSteers(ctx, ownerId) || !canUseCompletionSession(ctx, ownerId) || reconcileCompletionBatch(ctx, ownerId))
|
|
623
|
+
return undefined;
|
|
624
|
+
const batch = pendingCompletionBatch(ctx, ownerId);
|
|
625
|
+
if (!batch || !isCurrentOwner(ctx))
|
|
626
|
+
return undefined;
|
|
627
|
+
const projected = RunDelivery.projectRunCompletionBatchPresentation(batch);
|
|
628
|
+
const entry = Pi.createRunCompletionBatchEntry(batch.batch_id, projected.content, projected.presentation);
|
|
629
|
+
if (!canUseCompletionSession(ctx, ownerId))
|
|
630
|
+
return undefined;
|
|
631
|
+
return { entries: [...event.entries, entry], continue: true };
|
|
632
|
+
},
|
|
430
633
|
projectContext(messages, ctx) {
|
|
431
634
|
const ownerId = activeOwnerId;
|
|
432
|
-
if (!
|
|
635
|
+
if (!ownerId || !isCurrentOwner(ctx))
|
|
433
636
|
return messages;
|
|
434
|
-
try {
|
|
435
|
-
if (Pi.getSessionId(ctx) !== ownerId)
|
|
436
|
-
return messages;
|
|
437
|
-
}
|
|
438
|
-
catch {
|
|
439
|
-
return messages;
|
|
440
|
-
}
|
|
441
637
|
const steerContext = Pi.dedupeRunSteerContext(messages);
|
|
442
638
|
let contextMessages = steerContext.messages;
|
|
443
639
|
for (const steer of journal(ownerId).steers) {
|
|
@@ -464,6 +660,8 @@ export function createRunUiRuntime(deps) {
|
|
|
464
660
|
if (durable)
|
|
465
661
|
finishPresentedSteer(ownerId, durable);
|
|
466
662
|
}
|
|
663
|
+
if (!canUseCompletionSession(ctx, ownerId))
|
|
664
|
+
return contextMessages;
|
|
467
665
|
const projected = Pi.dedupeRunCompletionBatchContext(contextMessages);
|
|
468
666
|
const batch = journal(ownerId).completion_batch;
|
|
469
667
|
if (!batch)
|
|
@@ -472,21 +670,8 @@ export function createRunUiRuntime(deps) {
|
|
|
472
670
|
if (content !== RunDelivery.formatRunCompletionBatchMessage(batch)) {
|
|
473
671
|
return Pi.removeRunCompletionBatchFromContext(projected.messages, batch.batch_id);
|
|
474
672
|
}
|
|
475
|
-
if (!
|
|
476
|
-
|
|
477
|
-
ownerId,
|
|
478
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
479
|
-
}))
|
|
480
|
-
return projected.messages;
|
|
481
|
-
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
482
|
-
batchId: batch.batch_id,
|
|
483
|
-
ownerId,
|
|
484
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
485
|
-
}))
|
|
486
|
-
return projected.messages;
|
|
487
|
-
const presented = journal(ownerId).completion_batch;
|
|
488
|
-
if (presented)
|
|
489
|
-
finishPresentedBatch(ownerId, presented);
|
|
673
|
+
if (!fencedCompletionSession || canUseCompletionSession(ctx, ownerId))
|
|
674
|
+
acceptCompletionBatch(ownerId, batch.batch_id);
|
|
490
675
|
return projected.messages;
|
|
491
676
|
},
|
|
492
677
|
shutdown(eventReason, ownerId, ctx) {
|
|
@@ -496,27 +681,65 @@ export function createRunUiRuntime(deps) {
|
|
|
496
681
|
if (teardown.failed === 0 || !ctx)
|
|
497
682
|
return;
|
|
498
683
|
try {
|
|
499
|
-
ctx.ui.notify(
|
|
684
|
+
ctx.ui.notify([formatMarkdownField("stage", "shutdown teardown"), formatMarkdownField("killed", String(teardown.killed)),
|
|
685
|
+
formatMarkdownField("failed", String(teardown.failed)), formatMarkdownField("skipped", String(teardown.skipped)),
|
|
686
|
+
formatMarkdownField("discovery_failed", String(teardown.discoveryFailed)),
|
|
687
|
+
formatMarkdownField("summary", teardown.summaryPath == null ? "unavailable" : formatLiteralBlock(teardown.summaryPath)),
|
|
688
|
+
formatMarkdownField("basis", "teardown report; not scope closure")].join("\n"), "warning");
|
|
500
689
|
}
|
|
501
690
|
catch {
|
|
502
691
|
/* stale shutdown context */
|
|
503
692
|
}
|
|
504
693
|
},
|
|
505
|
-
start(ctx, ownerId) {
|
|
694
|
+
start(ctx, ownerId, event) {
|
|
506
695
|
close();
|
|
696
|
+
const ticket = {};
|
|
697
|
+
sessionTicket = ticket;
|
|
507
698
|
activeContext = ctx;
|
|
508
699
|
activeOwnerId = ownerId;
|
|
509
700
|
recoverQueuedSteers = true;
|
|
510
701
|
running = true;
|
|
511
702
|
try {
|
|
512
|
-
|
|
703
|
+
if (fencedCompletionSession) {
|
|
704
|
+
sessionManager = ctx.sessionManager;
|
|
705
|
+
const fresh = !seenManagers.has(sessionManager) && !contextTickets.has(ctx);
|
|
706
|
+
seenManagers.add(sessionManager);
|
|
707
|
+
if (!contextTickets.has(ctx))
|
|
708
|
+
contextTickets.set(ctx, ticket);
|
|
709
|
+
if (deps.captureCompletionBatch && !fresh)
|
|
710
|
+
sessionManager = undefined;
|
|
711
|
+
if (deps.completionStreamSessionAdmission && fresh && event && (event.reason === "new" || event.reason === "fork") && isCurrentOwner(ctx)) {
|
|
712
|
+
const admitted = deps.completionStreamSessionAdmission(ctx, event) === true && isCurrentOwner(ctx);
|
|
713
|
+
if (ticket !== sessionTicket)
|
|
714
|
+
return;
|
|
715
|
+
streamSessionAdmitted = admitted;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (ticket !== sessionTicket)
|
|
719
|
+
return;
|
|
720
|
+
watcher = createWatcher(ticket);
|
|
721
|
+
reconciliation = createReconciliation(ticket);
|
|
722
|
+
if (canUseCompletionSession(ctx, ownerId))
|
|
723
|
+
recoverPresentedBatch(ownerId);
|
|
513
724
|
recoverPresentedSteers(ownerId);
|
|
514
725
|
Observability.primeRunAttentionState(observation, ownerId);
|
|
515
726
|
update(ctx, ownerId, true, true);
|
|
727
|
+
if (ticket !== sessionTicket)
|
|
728
|
+
return;
|
|
516
729
|
watcher.refresh();
|
|
730
|
+
if (ticket !== sessionTicket)
|
|
731
|
+
return;
|
|
517
732
|
reconciliation.start();
|
|
733
|
+
if (ticket !== sessionTicket || ctx.mode !== "tui")
|
|
734
|
+
return;
|
|
518
735
|
animationInterval = setInterval(() => {
|
|
519
|
-
runActiveCallback("status animation callback", (current
|
|
736
|
+
runActiveCallback("status animation callback", (current) => {
|
|
737
|
+
const status = Observability.renderSubagentStatus(displayRunningSubagents, ++displayFrame);
|
|
738
|
+
const themed = status ? current.ui.theme.fg("dim", status) : status;
|
|
739
|
+
if (ticket !== sessionTicket)
|
|
740
|
+
return;
|
|
741
|
+
current.ui.setStatus("zz-pi-actors-runs", themed);
|
|
742
|
+
}, ticket);
|
|
520
743
|
}, deps.animationIntervalMs ?? 1000);
|
|
521
744
|
animationInterval.unref?.();
|
|
522
745
|
}
|