@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
package/lib/run-ui-runtime.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
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
6
|
|
|
7
|
+
import { Container, Markdown, Spacer, Text, stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
8
|
+
|
|
7
9
|
import * as AsyncRuns from "./async-runs.ts";
|
|
8
10
|
import * as Limits from "./limits.ts";
|
|
9
11
|
import * as Observability from "./observability.ts";
|
|
@@ -11,20 +13,27 @@ import * as Paths from "./paths.ts";
|
|
|
11
13
|
import * as Pi from "./pi.ts";
|
|
12
14
|
import * as RunDelivery from "./run-delivery.ts";
|
|
13
15
|
import * as RunDeliveryLineage from "./run-delivery-lineage.ts";
|
|
16
|
+
import * as RunIndex from "./runs-index.ts";
|
|
17
|
+
import { formatLiteralBlock, formatMarkdownField } from "./tools-response.ts";
|
|
14
18
|
|
|
15
19
|
export interface RunUiRuntime {
|
|
16
20
|
close(): void;
|
|
21
|
+
isCurrentSessionContext(ctx: Pi.ExtensionContext): boolean;
|
|
17
22
|
flushCompletionBatch(ctx: Pi.ExtensionContext): boolean;
|
|
23
|
+
onTurnEnd(event: Pi.TurnEndEvent, ctx: Pi.ExtensionContext): Pi.TurnEndEventResult | undefined;
|
|
18
24
|
projectContext(messages: unknown[], ctx: Pi.ExtensionContext): unknown[];
|
|
19
25
|
shutdown(
|
|
20
26
|
eventReason: string,
|
|
21
27
|
ownerId: string | undefined,
|
|
22
28
|
ctx?: Pi.ExtensionContext,
|
|
23
29
|
): void;
|
|
24
|
-
start(ctx: Pi.ExtensionContext, ownerId: string): void;
|
|
30
|
+
start(ctx: Pi.ExtensionContext, ownerId: string, event?: Pi.SessionStartEvent): void;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export interface RunUiRuntimeDeps {
|
|
34
|
+
/** Trusted kernel opt-in for verified fresh creation; never actor data, continuity proof or bootstrap. */
|
|
35
|
+
completionStreamSessionAdmission?: (ctx: Pi.ExtensionContext, event: Pi.SessionStartEvent) => boolean;
|
|
36
|
+
captureCompletionBatch?: typeof RunDelivery.admitRunCompletionBatch;
|
|
28
37
|
animationIntervalMs?: number;
|
|
29
38
|
createRunStateWatcher?: typeof Observability.createRunStateWatcher;
|
|
30
39
|
createRunTerminalReconciliationLoop?:
|
|
@@ -38,10 +47,89 @@ export interface RunUiRuntimeDeps {
|
|
|
38
47
|
teardownRunsOwnedByParent?: typeof AsyncRuns.teardownRunsOwnedByParent;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
function formatSteerDiagnosticMessage(eventId: string, stage: "admission" | "delivery", message: string): string {
|
|
51
|
+
const fields = [formatMarkdownField("event", eventId), formatMarkdownField("attention", "steer"),
|
|
52
|
+
formatMarkdownField("stage", stage), formatMarkdownField("result", stage === "admission" ? "remains retryable" : "request failed")];
|
|
53
|
+
if (stage === "delivery") fields.push(formatMarkdownField("delivery", "unconfirmed"), formatMarkdownField("presentation", "unconfirmed"));
|
|
54
|
+
let end = Math.min(message.length, 240);
|
|
55
|
+
const last = message.charCodeAt(end - 1), next = message.charCodeAt(end);
|
|
56
|
+
if (last >= 0xd800 && last <= 0xdbff && next >= 0xdc00 && next <= 0xdfff) end -= 1;
|
|
57
|
+
const preview = message.slice(0, end), clipped = end < message.length;
|
|
58
|
+
if (Buffer.from(preview).toString("utf8") !== preview) {
|
|
59
|
+
fields.push(formatMarkdownField("diagnostic", "unavailable"), formatMarkdownField("diagnostic_reason", "malformed legacy preview"),
|
|
60
|
+
formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
|
|
61
|
+
} else {
|
|
62
|
+
fields.push(formatMarkdownField("diagnostic", preview + (clipped ? "…" : "")));
|
|
63
|
+
if (clipped) fields.push(formatMarkdownField("diagnostic_omission", "[omitted: unknown]"));
|
|
64
|
+
}
|
|
65
|
+
return fields.join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Transcript-only view of the exact delivered content; never re-read a Run or infer body labels. */
|
|
69
|
+
export function registerRunCompletionRenderer(pi: Pi.ExtensionAPI): void {
|
|
70
|
+
pi.registerMessageRenderer(Pi.RUN_COMPLETION_BATCH_CUSTOM_TYPE, (message, { expanded, outputPad }, theme) => {
|
|
71
|
+
if (typeof message.content !== "string") return undefined;
|
|
72
|
+
let presentation: unknown;
|
|
73
|
+
try { presentation = Object.getOwnPropertyDescriptor(message.details as object, "pi_actors_presentation")?.value; } catch { /* no layout hint */ }
|
|
74
|
+
const view = RunDelivery.readRunCompletionPresentation(message.content, presentation);
|
|
75
|
+
if (!view) return undefined;
|
|
76
|
+
if (!expanded && view.segments) {
|
|
77
|
+
const component = new Container(), markdownTheme = { ...Pi.getMarkdownTheme(), codeBlockIndent: "" };
|
|
78
|
+
component.addChild(new Spacer(1));
|
|
79
|
+
let pieces = 0;
|
|
80
|
+
for (const segment of view.segments) {
|
|
81
|
+
// Only compiler separator/marker chunks drive spacing; whitespace bodies remain Markdown.
|
|
82
|
+
if (segment.kind === "separator" || (segment.kind === "marker" && /^\n+$/u.test(segment.text))) continue;
|
|
83
|
+
if (pieces++) component.addChild(new Spacer(1));
|
|
84
|
+
const text = stripTerminalSequences(segment.text).replaceAll(/\r\n|\r/gu, "\n");
|
|
85
|
+
component.addChild(segment.kind === "body"
|
|
86
|
+
? new Markdown(text, outputPad, 0, markdownTheme, { color: value => theme.fg("customMessageText", value) })
|
|
87
|
+
: new Text(theme.fg("muted", text), outputPad, 0));
|
|
88
|
+
}
|
|
89
|
+
component.addChild(new Spacer(1));
|
|
90
|
+
component.addChild(new Text(theme.fg("muted", "Expand message for technical details."), outputPad, 0));
|
|
91
|
+
component.addChild(new Spacer(1));
|
|
92
|
+
return component;
|
|
93
|
+
}
|
|
94
|
+
if (!expanded && view.blocks) {
|
|
95
|
+
const component = new Container(), markdownTheme = { ...Pi.getMarkdownTheme(), codeBlockIndent: "" };
|
|
96
|
+
component.addChild(new Spacer(1));
|
|
97
|
+
for (const [index, [kind, start, end]] of view.blocks.entries()) {
|
|
98
|
+
if (index) component.addChild(new Spacer(1));
|
|
99
|
+
let text = stripTerminalSequences(message.content.slice(start, end)).replaceAll(/\r\n|\r/gu, "\n");
|
|
100
|
+
if (kind === "header") {
|
|
101
|
+
// Only validated compiler headers may hide whole reference rows; body lookalikes stay visible.
|
|
102
|
+
text = text.split("\n").filter(line => !line.startsWith("references: ") && !line.startsWith("run_files: ")).join("\n").replace(/\n$/u, "");
|
|
103
|
+
}
|
|
104
|
+
component.addChild(kind === "body"
|
|
105
|
+
? new Markdown(text, outputPad, 0, markdownTheme, { color: value => theme.fg("customMessageText", value) })
|
|
106
|
+
: new Text(theme.fg(kind === "header" || kind === "marker" ? "muted" : "customMessageText", text), outputPad, 0));
|
|
107
|
+
}
|
|
108
|
+
component.addChild(new Spacer(1));
|
|
109
|
+
component.addChild(new Text(theme.fg("muted", "Expand message for technical details."), outputPad, 0));
|
|
110
|
+
component.addChild(new Spacer(1));
|
|
111
|
+
return component;
|
|
112
|
+
}
|
|
113
|
+
const text = expanded ? message.content : view.text;
|
|
114
|
+
const label = view.yaml || (view.records && !view.single) ? "" : theme.fg("customMessageLabel", view.single ? "Run result" : "Run Batch") + "\n\n";
|
|
115
|
+
return new Text(label + theme.fg("customMessageText", stripTerminalSequences(text).replaceAll(/\r\n|\r/gu, "\n"))
|
|
116
|
+
+ (!expanded && view.compact ? "\n\n" + theme.fg("muted", "Expand message for technical details.") : ""), outputPad, 1);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
41
120
|
export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
121
|
+
if (deps.captureCompletionBatch && deps.completionStreamSessionAdmission) throw new Error("Candidate completion capture cannot use completion streams");
|
|
122
|
+
const fencedCompletionSession = Boolean(deps.captureCompletionBatch || deps.completionStreamSessionAdmission);
|
|
42
123
|
let activeContext: Pi.ExtensionContext | undefined;
|
|
43
124
|
let activeOwnerId: string | undefined;
|
|
125
|
+
let sessionTicket: object | undefined;
|
|
126
|
+
let sessionManager: Pi.ExtensionContext["sessionManager"] | undefined;
|
|
127
|
+
let streamSessionAdmitted = false;
|
|
128
|
+
const contextTickets = new WeakMap<Pi.ExtensionContext, object>();
|
|
129
|
+
const seenManagers = new WeakSet<Pi.ExtensionContext["sessionManager"]>();
|
|
44
130
|
let animationInterval: NodeJS.Timeout | undefined;
|
|
131
|
+
let displayRunningSubagents = 0;
|
|
132
|
+
let displayFrame = 0;
|
|
45
133
|
let deliveryTimeout: NodeJS.Timeout | undefined;
|
|
46
134
|
let notifyTimeout: NodeJS.Timeout | undefined;
|
|
47
135
|
let recoverQueuedSteers = false;
|
|
@@ -51,9 +139,15 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
51
139
|
const deliveryObservation = Observability.createRunUiObservationState();
|
|
52
140
|
const retirementAttempts = new Set<string>();
|
|
53
141
|
const steerDiagnostics = new Set<string>();
|
|
142
|
+
const completionDiagnostics = new Set<string>();
|
|
54
143
|
|
|
55
144
|
const close = (): void => {
|
|
56
145
|
running = false;
|
|
146
|
+
displayRunningSubagents = 0;
|
|
147
|
+
displayFrame = 0;
|
|
148
|
+
sessionTicket = undefined;
|
|
149
|
+
sessionManager = undefined;
|
|
150
|
+
streamSessionAdmitted = false;
|
|
57
151
|
activeContext = undefined;
|
|
58
152
|
activeOwnerId = undefined;
|
|
59
153
|
try {
|
|
@@ -72,6 +166,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
72
166
|
deliveryTimeout = undefined;
|
|
73
167
|
recoverQueuedSteers = false;
|
|
74
168
|
steerDiagnostics.clear();
|
|
169
|
+
completionDiagnostics.clear();
|
|
75
170
|
if (animationInterval) clearInterval(animationInterval);
|
|
76
171
|
animationInterval = undefined;
|
|
77
172
|
};
|
|
@@ -89,7 +184,8 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
89
184
|
}
|
|
90
185
|
const message = error instanceof Error ? error.message : String(error);
|
|
91
186
|
try {
|
|
92
|
-
expectedContext.ui.notify(
|
|
187
|
+
expectedContext.ui.notify([formatMarkdownField("callback", label), formatMarkdownField("result", "failed"),
|
|
188
|
+
formatMarkdownField("diagnostic", message)].join("\n"), "error");
|
|
93
189
|
} catch {
|
|
94
190
|
/* stale context or unavailable UI */
|
|
95
191
|
}
|
|
@@ -97,14 +193,15 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
97
193
|
const runActiveCallback = (
|
|
98
194
|
label: string,
|
|
99
195
|
callback: (ctx: Pi.ExtensionContext, ownerId: string) => void,
|
|
196
|
+
expectedTicket: object | undefined,
|
|
100
197
|
): void => {
|
|
101
|
-
if (!running || !activeContext || !activeOwnerId) return;
|
|
198
|
+
if (expectedTicket !== sessionTicket || !running || !activeContext || !activeOwnerId) return;
|
|
102
199
|
const ctx = activeContext;
|
|
103
200
|
try {
|
|
104
201
|
if (deps.getActiveContext() !== ctx) return;
|
|
105
202
|
callback(ctx, activeOwnerId);
|
|
106
203
|
} catch (error) {
|
|
107
|
-
stopAfterCallbackFailure(label, error, ctx);
|
|
204
|
+
if (expectedTicket === sessionTicket) stopAfterCallbackFailure(label, error, ctx);
|
|
108
205
|
}
|
|
109
206
|
};
|
|
110
207
|
const retireCandidateRuns = (
|
|
@@ -169,16 +266,6 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
169
266
|
if (steer.phase === "presented") finishPresentedSteer(ownerId, steer);
|
|
170
267
|
}
|
|
171
268
|
};
|
|
172
|
-
const boundedSteerContent = (event: Observability.RunAttentionEvent): string => {
|
|
173
|
-
const text = Observability.formatRunAttentionMessage(event);
|
|
174
|
-
if (Buffer.byteLength(text, "utf8") <= Limits.RUN_DELIVERY_STEER_MAX_BYTES) {
|
|
175
|
-
return text;
|
|
176
|
-
}
|
|
177
|
-
let end = Math.min(text.length, Limits.RUN_DELIVERY_STEER_MAX_BYTES - 3);
|
|
178
|
-
while (end > 0 && Buffer.byteLength(text.slice(0, end), "utf8") >
|
|
179
|
-
Limits.RUN_DELIVERY_STEER_MAX_BYTES - 3) end -= 1;
|
|
180
|
-
return `${text.slice(0, end)}…`;
|
|
181
|
-
};
|
|
182
269
|
const admitSteerEvents = (
|
|
183
270
|
ctx: Pi.ExtensionContext,
|
|
184
271
|
ownerId: string,
|
|
@@ -191,15 +278,17 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
191
278
|
if (!steerDiagnostics.has(key)) {
|
|
192
279
|
steerDiagnostics.add(key);
|
|
193
280
|
ctx.ui.notify(
|
|
194
|
-
|
|
281
|
+
formatSteerDiagnosticMessage(event.id, "admission", "Run generation unavailable."),
|
|
195
282
|
"warning",
|
|
196
283
|
);
|
|
197
284
|
}
|
|
198
285
|
continue;
|
|
199
286
|
}
|
|
200
287
|
try {
|
|
288
|
+
if (journal(ownerId).steers.some(steer => steer.run === event.run && steer.state_dir === event.stateDir &&
|
|
289
|
+
steer.run_instance_id === event.runInstanceId && steer.event_id === event.id)) continue;
|
|
201
290
|
RunDelivery.admitRunSteerEnvelope({
|
|
202
|
-
content:
|
|
291
|
+
content: Observability.formatRunSteerMessage(event),
|
|
203
292
|
eventId: event.id,
|
|
204
293
|
kind: event.kind,
|
|
205
294
|
level: event.level,
|
|
@@ -217,7 +306,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
217
306
|
if (!steerDiagnostics.has(key)) {
|
|
218
307
|
steerDiagnostics.add(key);
|
|
219
308
|
ctx.ui.notify(
|
|
220
|
-
|
|
309
|
+
formatSteerDiagnosticMessage(event.id, "admission", message),
|
|
221
310
|
"warning",
|
|
222
311
|
);
|
|
223
312
|
}
|
|
@@ -225,6 +314,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
225
314
|
}
|
|
226
315
|
};
|
|
227
316
|
const admitCompletionTransitions = (
|
|
317
|
+
ctx: Pi.ExtensionContext,
|
|
228
318
|
ownerId: string,
|
|
229
319
|
snapshot: Observability.RunUiSnapshot,
|
|
230
320
|
): boolean => {
|
|
@@ -234,14 +324,95 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
234
324
|
snapshot.transitions,
|
|
235
325
|
snapshot.summary.runs,
|
|
236
326
|
).slice(0, Limits.RUN_DELIVERY_BATCH_MAX_MEMBERS);
|
|
237
|
-
if (members.length === 0) return false;
|
|
238
|
-
RunDelivery.
|
|
327
|
+
if (members.length === 0 || !isCurrentOwner(ctx)) return false;
|
|
328
|
+
const fileNavigation: RunDelivery.RunCompletionFileNavigation[] = [];
|
|
329
|
+
for (const member of RunDelivery.orderRunCompletionMembers(members, true)) {
|
|
330
|
+
let locations: Readonly<RunIndex.RunDirectoryNavigation>;
|
|
331
|
+
try {
|
|
332
|
+
const observed = snapshot.summary.runs.find(run => run.run === member.run && run.runInstanceId === member.run_instance_id && run.stateDir === member.state_dir);
|
|
333
|
+
locations = !observed ? Object.freeze({ state: "unavailable", reason: "identity_unavailable" })
|
|
334
|
+
: observed.ownerId !== ownerId ? Object.freeze({ state: "unavailable", reason: "session_mismatch" })
|
|
335
|
+
: RunIndex.readRunDirectoryNavigation(member.state_dir, { run: member.run, run_instance_id: member.run_instance_id, ownerId: observed.ownerId }, Pi.getSessionId(ctx));
|
|
336
|
+
} catch { locations = Object.freeze({ state: "unavailable", reason: "navigation_unavailable" }); }
|
|
337
|
+
fileNavigation.push({ member_index: members.indexOf(member), locations });
|
|
338
|
+
if (Buffer.byteLength(JSON.stringify(fileNavigation), "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) { fileNavigation.pop(); break; }
|
|
339
|
+
}
|
|
340
|
+
if (!isCurrentOwner(ctx)) return false;
|
|
341
|
+
if (!canUseCompletionSession(ctx, ownerId)) return false;
|
|
342
|
+
const capture = deps.completionStreamSessionAdmission ? RunDelivery.captureRunCompletionStreamBatch : deps.captureCompletionBatch ?? RunDelivery.admitRunCompletionBatch;
|
|
343
|
+
capture({
|
|
239
344
|
members,
|
|
345
|
+
fileNavigation,
|
|
240
346
|
ownerId,
|
|
241
347
|
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
242
348
|
});
|
|
243
349
|
return true;
|
|
244
350
|
};
|
|
351
|
+
const acceptCompletionBatch = (ownerId: string, batchId: string): boolean => {
|
|
352
|
+
const input = { batchId, ownerId, tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir };
|
|
353
|
+
if (!RunDelivery.markRunCompletionBatchQueued(input)) return false;
|
|
354
|
+
if (!RunDelivery.markRunCompletionBatchPresented(input)) return false;
|
|
355
|
+
const accepted = journal(ownerId).completion_batch;
|
|
356
|
+
return accepted ? finishPresentedBatch(ownerId, accepted) : false;
|
|
357
|
+
};
|
|
358
|
+
const reconcileCompletionBatch = (ctx: Pi.ExtensionContext, ownerId: string): boolean => {
|
|
359
|
+
const batch = journal(ownerId).completion_batch;
|
|
360
|
+
if (!batch) return false;
|
|
361
|
+
if (batch.phase === "pending") {
|
|
362
|
+
// Returning boundary drafts is not acceptance: prove the exact entry before acknowledging it.
|
|
363
|
+
const evidence = Pi.inspectRunCompletionBatchSessionEvidence(ctx, {
|
|
364
|
+
batchId: batch.batch_id,
|
|
365
|
+
content: RunDelivery.formatRunCompletionBatchMessage(batch),
|
|
366
|
+
});
|
|
367
|
+
if (evidence.status === "absent") return false;
|
|
368
|
+
if (evidence.status !== "present") {
|
|
369
|
+
const key = `${batch.batch_id}:${evidence.status}:${evidence.reason ?? ""}`;
|
|
370
|
+
if (!completionDiagnostics.has(key)) {
|
|
371
|
+
completionDiagnostics.add(key);
|
|
372
|
+
ctx.ui.notify(
|
|
373
|
+
"\n" + [formatMarkdownField("batch", batch.batch_id), formatMarkdownField("stage", "completion recovery"),
|
|
374
|
+
formatMarkdownField("evidence", evidence.status), formatMarkdownField("phase", "pending"),
|
|
375
|
+
formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"),
|
|
376
|
+
"warning",
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (fencedCompletionSession && !canUseCompletionSession(ctx, ownerId)) return true;
|
|
383
|
+
return !acceptCompletionBatch(ownerId, batch.batch_id);
|
|
384
|
+
};
|
|
385
|
+
const pendingCompletionBatch = (ctx: Pi.ExtensionContext, ownerId: string): RunDelivery.RunCompletionBatch | undefined => {
|
|
386
|
+
const existing = journal(ownerId).completion_batch;
|
|
387
|
+
if (existing) return existing;
|
|
388
|
+
const snapshot = Observability.readRunDeliverySnapshot(deliveryObservation, ownerId);
|
|
389
|
+
admitCompletionTransitions(ctx, ownerId, snapshot);
|
|
390
|
+
Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
|
|
391
|
+
return journal(ownerId).completion_batch;
|
|
392
|
+
};
|
|
393
|
+
const isCurrentOwner = (ctx: Pi.ExtensionContext): boolean => {
|
|
394
|
+
if (!running || !activeOwnerId) return false;
|
|
395
|
+
if (!fencedCompletionSession) {
|
|
396
|
+
try { return Pi.getSessionId(ctx) === activeOwnerId; } catch { return false; }
|
|
397
|
+
}
|
|
398
|
+
const ticket = sessionTicket, ownerId = activeOwnerId, known = contextTickets.get(ctx);
|
|
399
|
+
if (!ticket || (known && known !== ticket)) return false;
|
|
400
|
+
try {
|
|
401
|
+
if (deps.getActiveContext() !== activeContext) return false;
|
|
402
|
+
const manager = ctx.sessionManager;
|
|
403
|
+
if (manager !== sessionManager || manager.getSessionId() !== ownerId || ticket !== sessionTicket || !running) return false;
|
|
404
|
+
contextTickets.set(ctx, ticket);
|
|
405
|
+
return true;
|
|
406
|
+
} catch { return false; }
|
|
407
|
+
};
|
|
408
|
+
const canUseCompletionSession = (ctx: Pi.ExtensionContext, ownerId: string): boolean => {
|
|
409
|
+
if (deps.captureCompletionBatch && !isCurrentOwner(ctx)) return false;
|
|
410
|
+
if (!deps.completionStreamSessionAdmission) return !journal(ownerId).completion_stream;
|
|
411
|
+
if (!isCurrentOwner(ctx)) return false;
|
|
412
|
+
const ticket = sessionTicket, state = journal(ownerId).completion_stream;
|
|
413
|
+
// No metadata is not disconnected. The opt-in path never bootstraps or falls back to local indices.
|
|
414
|
+
return streamSessionAdmitted && Boolean(state) && ticket === sessionTicket && isCurrentOwner(ctx);
|
|
415
|
+
};
|
|
245
416
|
const isIdle = (ctx: Pi.ExtensionContext): boolean =>
|
|
246
417
|
typeof ctx.isIdle !== "function" || ctx.isIdle();
|
|
247
418
|
const isDeliveryRoot = (ownerId: string): boolean => {
|
|
@@ -270,7 +441,9 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
270
441
|
if (!steerDiagnostics.has(key)) {
|
|
271
442
|
steerDiagnostics.add(key);
|
|
272
443
|
ctx.ui.notify(
|
|
273
|
-
|
|
444
|
+
[formatMarkdownField("event", steer.event_id), formatMarkdownField("attention", "steer"),
|
|
445
|
+
formatMarkdownField("stage", "recovery"), formatMarkdownField("evidence", evidence.status),
|
|
446
|
+
formatMarkdownField("phase", "queued"), formatMarkdownField("reason", evidence.reason ?? "conflicting session evidence")].join("\n"),
|
|
274
447
|
"warning",
|
|
275
448
|
);
|
|
276
449
|
}
|
|
@@ -299,7 +472,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
299
472
|
});
|
|
300
473
|
const message = error instanceof Error ? error.message : String(error);
|
|
301
474
|
ctx.ui.notify(
|
|
302
|
-
|
|
475
|
+
formatSteerDiagnosticMessage(steer.event_id, "delivery", message),
|
|
303
476
|
"error",
|
|
304
477
|
);
|
|
305
478
|
continue;
|
|
@@ -315,12 +488,14 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
315
488
|
};
|
|
316
489
|
const scheduleCompletionFlush = (): void => {
|
|
317
490
|
if (!running) return;
|
|
491
|
+
const ticket = sessionTicket;
|
|
318
492
|
if (deliveryTimeout) clearTimeout(deliveryTimeout);
|
|
319
493
|
deliveryTimeout = setTimeout(() => {
|
|
494
|
+
if (ticket !== sessionTicket) return;
|
|
320
495
|
deliveryTimeout = undefined;
|
|
321
496
|
runActiveCallback("completion delivery callback", (ctx) => {
|
|
322
497
|
flushCompletionBatch(ctx);
|
|
323
|
-
});
|
|
498
|
+
}, ticket);
|
|
324
499
|
}, deps.deliveryDebounceMs ?? 100);
|
|
325
500
|
deliveryTimeout.unref?.();
|
|
326
501
|
};
|
|
@@ -330,22 +505,30 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
330
505
|
notify = false,
|
|
331
506
|
terminalOnly = false,
|
|
332
507
|
): boolean => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
508
|
+
// Derive both projections before any UI/retirement callback. The subsequent
|
|
509
|
+
// flush still observes afresh; these facts only drive display/candidate scheduling.
|
|
510
|
+
const ticket = sessionTicket;
|
|
511
|
+
const pair = notify && isDeliveryRoot(ownerId)
|
|
512
|
+
? Observability.readRunUiAndDeliverySnapshots(observation, deliveryObservation, ownerId)
|
|
513
|
+
: undefined;
|
|
514
|
+
const snapshot = pair?.ui ?? Observability.readRunUiSnapshot(observation, ownerId);
|
|
515
|
+
if (ticket !== sessionTicket || (fencedCompletionSession && !isCurrentOwner(ctx))) return false;
|
|
516
|
+
// Animation retains display data only; all admission paths continue observing independently.
|
|
517
|
+
displayRunningSubagents = snapshot.summary.runningSubagents;
|
|
518
|
+
displayFrame = 0;
|
|
519
|
+
const status = snapshot.status && ctx.mode === "tui" ? ctx.ui.theme.fg("dim", snapshot.status) : snapshot.status;
|
|
520
|
+
if (ticket !== sessionTicket) return false;
|
|
521
|
+
ctx.ui.setStatus("zz-pi-actors-runs", status);
|
|
522
|
+
if (ticket !== sessionTicket) return false;
|
|
523
|
+
if (!notify || (fencedCompletionSession && !isCurrentOwner(ctx))) return false;
|
|
339
524
|
const sink = Pi.createNotificationSink(deps.pi, ctx);
|
|
340
525
|
retireCandidateRuns(ctx, snapshot.summary);
|
|
341
|
-
const deliverySnapshot =
|
|
342
|
-
? Observability.readRunDeliverySnapshot(deliveryObservation, ownerId)
|
|
343
|
-
: undefined;
|
|
526
|
+
const deliverySnapshot = pair?.delivery;
|
|
344
527
|
const hasCompletionCandidates = deliverySnapshot
|
|
345
|
-
? Observability.
|
|
528
|
+
? Observability.hasRunCompletionBatchMembers(
|
|
346
529
|
deliverySnapshot.transitions,
|
|
347
530
|
deliverySnapshot.summary.runs,
|
|
348
|
-
)
|
|
531
|
+
)
|
|
349
532
|
: false;
|
|
350
533
|
const hasCompletionBatch = isDeliveryRoot(ownerId) &&
|
|
351
534
|
Boolean(journal(ownerId).completion_batch);
|
|
@@ -380,29 +563,31 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
380
563
|
);
|
|
381
564
|
}
|
|
382
565
|
};
|
|
383
|
-
const scheduleUpdate = (): void => {
|
|
384
|
-
if (!running) return;
|
|
566
|
+
const scheduleUpdate = (ticket: object | undefined): void => {
|
|
567
|
+
if (ticket !== sessionTicket || !running) return;
|
|
385
568
|
if (notifyTimeout) clearTimeout(notifyTimeout);
|
|
386
569
|
notifyTimeout = setTimeout(() => {
|
|
570
|
+
if (ticket !== sessionTicket) return;
|
|
387
571
|
runActiveCallback("Run watcher callback", (ctx, ownerId) => {
|
|
388
572
|
watcher.refresh();
|
|
389
573
|
const completionDeferred = update(ctx, ownerId, true);
|
|
574
|
+
if (ticket !== sessionTicket) return;
|
|
390
575
|
if (!completionDeferred) deps.onRunEvent();
|
|
391
|
-
reportDiagnostics(ctx);
|
|
392
|
-
});
|
|
576
|
+
if (ticket === sessionTicket) reportDiagnostics(ctx);
|
|
577
|
+
}, ticket);
|
|
393
578
|
}, deps.notificationDelayMs ?? 50);
|
|
394
579
|
notifyTimeout.unref?.();
|
|
395
580
|
};
|
|
396
|
-
const
|
|
581
|
+
const createWatcher = (ticket: object | undefined) => (deps.createRunStateWatcher ?? Observability.createRunStateWatcher)({
|
|
397
582
|
stateRoot: Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
|
|
398
|
-
onChange: scheduleUpdate,
|
|
583
|
+
onChange: () => scheduleUpdate(ticket),
|
|
399
584
|
});
|
|
400
|
-
const
|
|
585
|
+
const createReconciliation = (ticket: object | undefined) => (
|
|
401
586
|
deps.createRunTerminalReconciliationLoop ??
|
|
402
587
|
Observability.createRunTerminalReconciliationLoop
|
|
403
588
|
)({
|
|
404
589
|
onError: (error) => {
|
|
405
|
-
if (!running || !activeContext) return;
|
|
590
|
+
if (ticket !== sessionTicket || !running || !activeContext) return;
|
|
406
591
|
stopAfterCallbackFailure(
|
|
407
592
|
"terminal reconciliation callback",
|
|
408
593
|
error,
|
|
@@ -413,61 +598,31 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
413
598
|
runActiveCallback("terminal reconciliation callback", (ctx, ownerId) => {
|
|
414
599
|
update(ctx, ownerId, true);
|
|
415
600
|
reportDiagnostics(ctx);
|
|
416
|
-
});
|
|
601
|
+
}, ticket);
|
|
417
602
|
},
|
|
418
603
|
refreshWatcher: () => {
|
|
419
|
-
if (running) watcher.refresh();
|
|
604
|
+
if (ticket === sessionTicket && running) watcher.refresh();
|
|
420
605
|
},
|
|
421
606
|
});
|
|
607
|
+
let watcher = createWatcher(undefined);
|
|
608
|
+
let reconciliation = createReconciliation(undefined);
|
|
422
609
|
|
|
423
610
|
flushCompletionBatch = (ctx: Pi.ExtensionContext): boolean => {
|
|
424
611
|
const ownerId = activeOwnerId;
|
|
425
|
-
if (!
|
|
426
|
-
try {
|
|
427
|
-
if (Pi.getSessionId(ctx) !== ownerId) return false;
|
|
428
|
-
} catch {
|
|
429
|
-
return false;
|
|
430
|
-
}
|
|
612
|
+
if (!ownerId || !isCurrentOwner(ctx)) return false;
|
|
431
613
|
if (flushSteers(ctx, ownerId)) return true;
|
|
432
614
|
if (!isDeliveryRoot(ownerId)) return false;
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
deliveryObservation,
|
|
437
|
-
ownerId,
|
|
438
|
-
);
|
|
439
|
-
admitCompletionTransitions(ownerId, snapshot);
|
|
440
|
-
Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
|
|
441
|
-
batch = journal(ownerId).completion_batch;
|
|
442
|
-
}
|
|
615
|
+
if (!canUseCompletionSession(ctx, ownerId)) return true;
|
|
616
|
+
if (reconcileCompletionBatch(ctx, ownerId)) return true;
|
|
617
|
+
const batch = pendingCompletionBatch(ctx, ownerId);
|
|
443
618
|
if (!batch) return false;
|
|
444
|
-
if (batch.phase === "presented") {
|
|
445
|
-
finishPresentedBatch(ownerId, batch);
|
|
446
|
-
const snapshot = Observability.readRunDeliverySnapshot(
|
|
447
|
-
deliveryObservation,
|
|
448
|
-
ownerId,
|
|
449
|
-
);
|
|
450
|
-
admitCompletionTransitions(ownerId, snapshot);
|
|
451
|
-
Observability.pruneRunUiObservationState(deliveryObservation, snapshot);
|
|
452
|
-
batch = journal(ownerId).completion_batch;
|
|
453
|
-
if (!batch) return false;
|
|
454
|
-
}
|
|
455
|
-
if (!isIdle(ctx)) return true;
|
|
456
|
-
const content = RunDelivery.formatRunCompletionBatchMessage(batch);
|
|
457
|
-
if (batch.phase === "queued") {
|
|
458
|
-
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
459
|
-
batchId: batch.batch_id,
|
|
460
|
-
ownerId,
|
|
461
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
462
|
-
})) return true;
|
|
463
|
-
const accepted = journal(ownerId).completion_batch;
|
|
464
|
-
if (accepted) finishPresentedBatch(ownerId, accepted);
|
|
465
|
-
return flushCompletionBatch(ctx);
|
|
466
|
-
}
|
|
467
619
|
if (!isIdle(ctx)) return true;
|
|
620
|
+
const projected = RunDelivery.projectRunCompletionBatchPresentation(batch);
|
|
621
|
+
if (!isIdle(ctx) || !isCurrentOwner(ctx)) return true;
|
|
468
622
|
try {
|
|
469
|
-
Pi.sendRunCompletionBatch(deps.pi, batch.batch_id, content);
|
|
623
|
+
Pi.sendRunCompletionBatch(deps.pi, batch.batch_id, projected.content, projected.presentation);
|
|
470
624
|
} catch (error) {
|
|
625
|
+
if (fencedCompletionSession && !isCurrentOwner(ctx)) throw error;
|
|
471
626
|
RunDelivery.recordRunCompletionBatchDeliveryFailure({
|
|
472
627
|
batchId: batch.batch_id,
|
|
473
628
|
error,
|
|
@@ -476,40 +631,35 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
476
631
|
});
|
|
477
632
|
throw error;
|
|
478
633
|
}
|
|
479
|
-
if (!
|
|
480
|
-
|
|
481
|
-
ownerId,
|
|
482
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
483
|
-
})) {
|
|
484
|
-
throw new Error("Completion batch changed before queue acknowledgment");
|
|
485
|
-
}
|
|
486
|
-
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
487
|
-
batchId: batch.batch_id,
|
|
488
|
-
ownerId,
|
|
489
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
490
|
-
})) {
|
|
634
|
+
if (fencedCompletionSession && !canUseCompletionSession(ctx, ownerId)) return true;
|
|
635
|
+
if (!acceptCompletionBatch(ownerId, batch.batch_id)) {
|
|
491
636
|
throw new Error("Completion batch changed before durable acceptance");
|
|
492
637
|
}
|
|
493
|
-
const accepted = journal(ownerId).completion_batch;
|
|
494
|
-
if (accepted) finishPresentedBatch(ownerId, accepted);
|
|
495
|
-
ctx.ui.notify(
|
|
496
|
-
`Actor completions ready: ${batch.members.length}`,
|
|
497
|
-
"info",
|
|
498
|
-
);
|
|
499
638
|
return true;
|
|
500
639
|
};
|
|
501
640
|
|
|
502
641
|
return {
|
|
503
642
|
close,
|
|
643
|
+
isCurrentSessionContext: isCurrentOwner,
|
|
504
644
|
flushCompletionBatch,
|
|
645
|
+
onTurnEnd(event, ctx) {
|
|
646
|
+
const ownerId = activeOwnerId;
|
|
647
|
+
if (
|
|
648
|
+
!ownerId || !isCurrentOwner(ctx) || !isDeliveryRoot(ownerId) ||
|
|
649
|
+
event.outcome !== "completed" || isIdle(ctx) || ctx.signal?.aborted ||
|
|
650
|
+
ctx.hasPendingMessages?.() || event.context.pendingMessages.length > 0
|
|
651
|
+
) return undefined;
|
|
652
|
+
if (flushSteers(ctx, ownerId) || !canUseCompletionSession(ctx, ownerId) || reconcileCompletionBatch(ctx, ownerId)) return undefined;
|
|
653
|
+
const batch = pendingCompletionBatch(ctx, ownerId);
|
|
654
|
+
if (!batch || !isCurrentOwner(ctx)) return undefined;
|
|
655
|
+
const projected = RunDelivery.projectRunCompletionBatchPresentation(batch);
|
|
656
|
+
const entry = Pi.createRunCompletionBatchEntry(batch.batch_id, projected.content, projected.presentation);
|
|
657
|
+
if (!canUseCompletionSession(ctx, ownerId)) return undefined;
|
|
658
|
+
return { entries: [...event.entries, entry], continue: true };
|
|
659
|
+
},
|
|
505
660
|
projectContext(messages, ctx) {
|
|
506
661
|
const ownerId = activeOwnerId;
|
|
507
|
-
if (!
|
|
508
|
-
try {
|
|
509
|
-
if (Pi.getSessionId(ctx) !== ownerId) return messages;
|
|
510
|
-
} catch {
|
|
511
|
-
return messages;
|
|
512
|
-
}
|
|
662
|
+
if (!ownerId || !isCurrentOwner(ctx)) return messages;
|
|
513
663
|
const steerContext = Pi.dedupeRunSteerContext(messages);
|
|
514
664
|
let contextMessages = steerContext.messages;
|
|
515
665
|
for (const steer of journal(ownerId).steers) {
|
|
@@ -539,6 +689,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
539
689
|
item.steer_id === steer.steer_id);
|
|
540
690
|
if (durable) finishPresentedSteer(ownerId, durable);
|
|
541
691
|
}
|
|
692
|
+
if (!canUseCompletionSession(ctx, ownerId)) return contextMessages;
|
|
542
693
|
const projected = Pi.dedupeRunCompletionBatchContext(contextMessages);
|
|
543
694
|
const batch = journal(ownerId).completion_batch;
|
|
544
695
|
if (!batch) return projected.messages;
|
|
@@ -549,18 +700,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
549
700
|
batch.batch_id,
|
|
550
701
|
);
|
|
551
702
|
}
|
|
552
|
-
if (!
|
|
553
|
-
batchId: batch.batch_id,
|
|
554
|
-
ownerId,
|
|
555
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
556
|
-
})) return projected.messages;
|
|
557
|
-
if (!RunDelivery.markRunCompletionBatchPresented({
|
|
558
|
-
batchId: batch.batch_id,
|
|
559
|
-
ownerId,
|
|
560
|
-
tempDir: Paths.EXTENSION_RUNTIME_PATHS.tempDir,
|
|
561
|
-
})) return projected.messages;
|
|
562
|
-
const presented = journal(ownerId).completion_batch;
|
|
563
|
-
if (presented) finishPresentedBatch(ownerId, presented);
|
|
703
|
+
if (!fencedCompletionSession || canUseCompletionSession(ctx, ownerId)) acceptCompletionBatch(ownerId, batch.batch_id);
|
|
564
704
|
return projected.messages;
|
|
565
705
|
},
|
|
566
706
|
shutdown(eventReason, ownerId, ctx) {
|
|
@@ -575,30 +715,57 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
|
|
|
575
715
|
if (teardown.failed === 0 || !ctx) return;
|
|
576
716
|
try {
|
|
577
717
|
ctx.ui.notify(
|
|
578
|
-
|
|
718
|
+
[formatMarkdownField("stage", "shutdown teardown"), formatMarkdownField("killed", String(teardown.killed)),
|
|
719
|
+
formatMarkdownField("failed", String(teardown.failed)), formatMarkdownField("skipped", String(teardown.skipped)),
|
|
720
|
+
formatMarkdownField("discovery_failed", String(teardown.discoveryFailed)),
|
|
721
|
+
formatMarkdownField("summary", teardown.summaryPath == null ? "unavailable" : formatLiteralBlock(teardown.summaryPath)),
|
|
722
|
+
formatMarkdownField("basis", "teardown report; not scope closure")].join("\n"),
|
|
579
723
|
"warning",
|
|
580
724
|
);
|
|
581
725
|
} catch {
|
|
582
726
|
/* stale shutdown context */
|
|
583
727
|
}
|
|
584
728
|
},
|
|
585
|
-
start(ctx, ownerId) {
|
|
729
|
+
start(ctx, ownerId, event) {
|
|
586
730
|
close();
|
|
731
|
+
const ticket = {};
|
|
732
|
+
sessionTicket = ticket;
|
|
587
733
|
activeContext = ctx;
|
|
588
734
|
activeOwnerId = ownerId;
|
|
589
735
|
recoverQueuedSteers = true;
|
|
590
736
|
running = true;
|
|
591
737
|
try {
|
|
592
|
-
|
|
738
|
+
if (fencedCompletionSession) {
|
|
739
|
+
sessionManager = ctx.sessionManager;
|
|
740
|
+
const fresh = !seenManagers.has(sessionManager) && !contextTickets.has(ctx);
|
|
741
|
+
seenManagers.add(sessionManager);
|
|
742
|
+
if (!contextTickets.has(ctx)) contextTickets.set(ctx, ticket);
|
|
743
|
+
if (deps.captureCompletionBatch && !fresh) sessionManager = undefined;
|
|
744
|
+
if (deps.completionStreamSessionAdmission && fresh && event && (event.reason === "new" || event.reason === "fork") && isCurrentOwner(ctx)) {
|
|
745
|
+
const admitted = deps.completionStreamSessionAdmission(ctx, event) === true && isCurrentOwner(ctx);
|
|
746
|
+
if (ticket !== sessionTicket) return;
|
|
747
|
+
streamSessionAdmitted = admitted;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (ticket !== sessionTicket) return;
|
|
751
|
+
watcher = createWatcher(ticket);
|
|
752
|
+
reconciliation = createReconciliation(ticket);
|
|
753
|
+
if (canUseCompletionSession(ctx, ownerId)) recoverPresentedBatch(ownerId);
|
|
593
754
|
recoverPresentedSteers(ownerId);
|
|
594
755
|
Observability.primeRunAttentionState(observation, ownerId);
|
|
595
756
|
update(ctx, ownerId, true, true);
|
|
757
|
+
if (ticket !== sessionTicket) return;
|
|
596
758
|
watcher.refresh();
|
|
759
|
+
if (ticket !== sessionTicket) return;
|
|
597
760
|
reconciliation.start();
|
|
761
|
+
if (ticket !== sessionTicket || ctx.mode !== "tui") return;
|
|
598
762
|
animationInterval = setInterval(() => {
|
|
599
|
-
runActiveCallback("status animation callback", (current
|
|
600
|
-
|
|
601
|
-
|
|
763
|
+
runActiveCallback("status animation callback", (current) => {
|
|
764
|
+
const status = Observability.renderSubagentStatus(displayRunningSubagents, ++displayFrame);
|
|
765
|
+
const themed = status ? current.ui.theme.fg("dim", status) : status;
|
|
766
|
+
if (ticket !== sessionTicket) return;
|
|
767
|
+
current.ui.setStatus("zz-pi-actors-runs", themed);
|
|
768
|
+
}, ticket);
|
|
602
769
|
}, deps.animationIntervalMs ?? 1000);
|
|
603
770
|
animationInterval.unref?.();
|
|
604
771
|
} catch (error) {
|