@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.
Files changed (188) hide show
  1. package/AGENTS.md +110 -20
  2. package/BACKLOG.md +207 -4
  3. package/CHANGELOG.md +17 -0
  4. package/README.md +33 -3
  5. package/dist/index.js +2 -1
  6. package/dist/lib/async-runs.d.ts +37 -0
  7. package/dist/lib/async-runs.js +131 -62
  8. package/dist/lib/automatic-review-runtime.d.ts +2 -0
  9. package/dist/lib/automatic-review-runtime.js +3 -2
  10. package/dist/lib/command-templates.d.ts +37 -0
  11. package/dist/lib/command-templates.js +263 -129
  12. package/dist/lib/execution-files.d.ts +32 -0
  13. package/dist/lib/execution-files.js +128 -0
  14. package/dist/lib/execution-output.js +5 -4
  15. package/dist/lib/execution-sessions.d.ts +19 -1
  16. package/dist/lib/execution-sessions.js +62 -26
  17. package/dist/lib/execution.d.ts +4 -1
  18. package/dist/lib/execution.js +6 -1
  19. package/dist/lib/extension-runtime.d.ts +10 -2
  20. package/dist/lib/extension-runtime.js +95 -14
  21. package/dist/lib/file-state.js +21 -2
  22. package/dist/lib/inspector-command.js +2 -2
  23. package/dist/lib/inspector-overlay.d.ts +25 -7
  24. package/dist/lib/inspector-overlay.js +562 -262
  25. package/dist/lib/inspector.d.ts +20 -10
  26. package/dist/lib/inspector.js +80 -91
  27. package/dist/lib/limits.d.ts +1 -0
  28. package/dist/lib/limits.js +1 -0
  29. package/dist/lib/observability.d.ts +15 -1
  30. package/dist/lib/observability.js +216 -99
  31. package/dist/lib/pi.d.ts +9 -3
  32. package/dist/lib/pi.js +80 -12
  33. package/dist/lib/recipe-projection.d.ts +18 -0
  34. package/dist/lib/recipe-projection.js +220 -0
  35. package/dist/lib/recipes-context.d.ts +2 -0
  36. package/dist/lib/recipes-context.js +30 -6
  37. package/dist/lib/registry.js +270 -36
  38. package/dist/lib/run-delivery.d.ts +213 -5
  39. package/dist/lib/run-delivery.js +1909 -40
  40. package/dist/lib/run-supervision.d.ts +107 -0
  41. package/dist/lib/run-supervision.js +412 -0
  42. package/dist/lib/run-ui-runtime.d.ts +10 -2
  43. package/dist/lib/run-ui-runtime.js +347 -124
  44. package/dist/lib/runs-artifacts.d.ts +26 -1
  45. package/dist/lib/runs-artifacts.js +68 -2
  46. package/dist/lib/runs-context-exports.d.ts +451 -0
  47. package/dist/lib/runs-context-exports.js +2315 -0
  48. package/dist/lib/runs-context-index.d.ts +97 -0
  49. package/dist/lib/runs-context-index.js +368 -0
  50. package/dist/lib/runs-control-delivery.js +46 -7
  51. package/dist/lib/runs-control.d.ts +78 -2
  52. package/dist/lib/runs-control.js +334 -2
  53. package/dist/lib/runs-controls.js +4 -1
  54. package/dist/lib/runs-descendant-launches.d.ts +80 -0
  55. package/dist/lib/runs-descendant-launches.js +287 -0
  56. package/dist/lib/runs-execution-evidence.d.ts +110 -0
  57. package/dist/lib/runs-execution-evidence.js +282 -0
  58. package/dist/lib/runs-index.d.ts +66 -1
  59. package/dist/lib/runs-index.js +349 -13
  60. package/dist/lib/runs-native-launches.d.ts +73 -0
  61. package/dist/lib/runs-native-launches.js +227 -0
  62. package/dist/lib/runs-outcomes.d.ts +141 -0
  63. package/dist/lib/runs-outcomes.js +292 -0
  64. package/dist/lib/runs-ownership.d.ts +10 -1
  65. package/dist/lib/runs-ownership.js +36 -4
  66. package/dist/lib/runs-process-windows-mi.d.ts +46 -0
  67. package/dist/lib/runs-process-windows-mi.js +114 -0
  68. package/dist/lib/runs-process.d.ts +34 -1
  69. package/dist/lib/runs-process.js +158 -26
  70. package/dist/lib/runs-results.d.ts +116 -0
  71. package/dist/lib/runs-results.js +518 -0
  72. package/dist/lib/runs-retention.d.ts +15 -2
  73. package/dist/lib/runs-retention.js +148 -9
  74. package/dist/lib/runs-session-exports.d.ts +95 -0
  75. package/dist/lib/runs-session-exports.js +470 -0
  76. package/dist/lib/runs-snapshot.d.ts +58 -0
  77. package/dist/lib/runs-snapshot.js +92 -0
  78. package/dist/lib/runs-start.d.ts +2 -0
  79. package/dist/lib/runs-start.js +44 -3
  80. package/dist/lib/runs-status.d.ts +15 -1
  81. package/dist/lib/runs-status.js +337 -13
  82. package/dist/lib/runs-supervision.d.ts +105 -0
  83. package/dist/lib/runs-supervision.js +501 -0
  84. package/dist/lib/runs-trace.d.ts +1 -0
  85. package/dist/lib/runs-trace.js +14 -3
  86. package/dist/lib/runtime.js +22 -3
  87. package/dist/lib/session-evidence.d.ts +37 -0
  88. package/dist/lib/session-evidence.js +59 -3
  89. package/dist/lib/state-readers.d.ts +4 -1
  90. package/dist/lib/state-readers.js +14 -8
  91. package/dist/lib/temp.d.ts +2 -4
  92. package/dist/lib/temp.js +4 -56
  93. package/dist/lib/tools-access.d.ts +3 -0
  94. package/dist/lib/tools-access.js +13 -0
  95. package/dist/lib/tools-inspect.d.ts +1 -0
  96. package/dist/lib/tools-inspect.js +110 -43
  97. package/dist/lib/tools-local.d.ts +3 -0
  98. package/dist/lib/tools-local.js +18 -6
  99. package/dist/lib/tools-message.d.ts +4 -1
  100. package/dist/lib/tools-message.js +126 -31
  101. package/dist/lib/tools-response.d.ts +31 -3
  102. package/dist/lib/tools-response.js +311 -47
  103. package/dist/lib/tools-spawn.d.ts +3 -0
  104. package/dist/lib/tools-spawn.js +29 -7
  105. package/dist/lib/trace-projection.d.ts +20 -0
  106. package/dist/lib/trace-projection.js +192 -43
  107. package/dist/scripts/async-runner.mjs +272 -194
  108. package/dist/scripts/conformance.mjs +14 -2
  109. package/dist/skills/actors/SKILL.md +47 -3
  110. package/dist/skills/actors/references/diagnostics.md +40 -0
  111. package/dist/skills/actors/references/runs.md +56 -2
  112. package/docs/README.md +9 -1
  113. package/docs/actor-inspector.md +84 -15
  114. package/docs/agent-experience.md +216 -0
  115. package/docs/async-runs.md +21 -3
  116. package/docs/composition-transport.md +103 -0
  117. package/docs/coordinator-delivery.md +226 -24
  118. package/docs/inspection.md +38 -2
  119. package/docs/output-projection-map.md +130 -0
  120. package/docs/releasing.md +19 -2
  121. package/docs/run-supervision.md +1622 -0
  122. package/docs/template-recipes.md +4 -0
  123. package/docs/windows-process-observation.md +248 -0
  124. package/index.ts +2 -1
  125. package/lib/async-runs.ts +164 -73
  126. package/lib/automatic-review-runtime.ts +4 -2
  127. package/lib/command-templates.ts +287 -138
  128. package/lib/execution-files.ts +118 -0
  129. package/lib/execution-output.ts +4 -4
  130. package/lib/execution-sessions.ts +57 -27
  131. package/lib/execution.ts +8 -0
  132. package/lib/extension-runtime.ts +106 -12
  133. package/lib/file-state.ts +16 -2
  134. package/lib/inspector-command.ts +2 -2
  135. package/lib/inspector-overlay.ts +524 -261
  136. package/lib/inspector.ts +101 -130
  137. package/lib/limits.ts +1 -0
  138. package/lib/observability.ts +235 -116
  139. package/lib/pi.ts +89 -17
  140. package/lib/recipe-projection.ts +232 -0
  141. package/lib/recipes-context.ts +27 -6
  142. package/lib/registry.ts +266 -36
  143. package/lib/run-delivery.ts +1737 -49
  144. package/lib/run-supervision.ts +411 -0
  145. package/lib/run-ui-runtime.ts +293 -126
  146. package/lib/runs-artifacts.ts +73 -1
  147. package/lib/runs-context-exports.ts +1871 -0
  148. package/lib/runs-context-index.ts +372 -0
  149. package/lib/runs-control-delivery.ts +36 -18
  150. package/lib/runs-control.ts +297 -1
  151. package/lib/runs-controls.ts +3 -1
  152. package/lib/runs-descendant-launches.ts +338 -0
  153. package/lib/runs-execution-evidence.ts +302 -0
  154. package/lib/runs-index.ts +345 -17
  155. package/lib/runs-native-launches.ts +270 -0
  156. package/lib/runs-outcomes.ts +377 -0
  157. package/lib/runs-ownership.ts +37 -4
  158. package/lib/runs-process-windows-mi.ts +113 -0
  159. package/lib/runs-process.ts +210 -34
  160. package/lib/runs-results.ts +601 -0
  161. package/lib/runs-retention.ts +134 -10
  162. package/lib/runs-session-exports.ts +406 -0
  163. package/lib/runs-snapshot.ts +143 -0
  164. package/lib/runs-start.ts +44 -2
  165. package/lib/runs-status.ts +252 -10
  166. package/lib/runs-supervision.ts +394 -0
  167. package/lib/runs-trace.ts +14 -3
  168. package/lib/runtime.ts +22 -3
  169. package/lib/session-evidence.ts +76 -4
  170. package/lib/state-readers.ts +16 -7
  171. package/lib/temp.ts +3 -56
  172. package/lib/tools-access.ts +13 -0
  173. package/lib/tools-inspect.ts +110 -51
  174. package/lib/tools-local.ts +37 -24
  175. package/lib/tools-message.ts +120 -37
  176. package/lib/tools-response.ts +273 -50
  177. package/lib/tools-spawn.ts +31 -10
  178. package/lib/trace-projection.ts +165 -42
  179. package/package.json +10 -6
  180. package/scripts/async-runner.mjs +272 -194
  181. package/scripts/conformance.mjs +14 -2
  182. package/skills/actors/SKILL.md +47 -3
  183. package/skills/actors/references/diagnostics.md +40 -0
  184. package/skills/actors/references/runs.md +56 -2
  185. package/banner.jpg +0 -0
  186. package/dist/lib/inspector-actions.d.ts +0 -16
  187. package/dist/lib/inspector-actions.js +0 -57
  188. package/lib/inspector-actions.ts +0 -79
@@ -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 without owning actor execution semantics.
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(`Actor ${label} failed: ${message}`, "error");
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
- `Actor urgent steer ${event.id} remains retryable because its Run generation is unavailable.`,
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: boundedSteerContent(event),
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
- `Actor urgent steer ${event.id} remains retryable: ${message.replaceAll(/\s+/g, " ").slice(0, 240)}`,
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.admitRunCompletionBatch({
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
- `Actor urgent steer recovery is ${evidence.status}: ${evidence.reason ?? "conflicting session evidence"}. Event ${steer.event_id} remains queued.`,
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
- `Actor urgent steer delivery failed for event ${steer.event_id}: ${message.replaceAll(/\s+/g, " ").slice(0, 240)}`,
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
- const snapshot = Observability.readRunUiSnapshot(observation, ownerId);
334
- ctx.ui.setStatus(
335
- "zz-pi-actors-runs",
336
- snapshot.status ? ctx.ui.theme.fg("dim", snapshot.status) : undefined,
337
- );
338
- if (!notify) return false;
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 = isDeliveryRoot(ownerId)
342
- ? Observability.readRunDeliverySnapshot(deliveryObservation, ownerId)
343
- : undefined;
526
+ const deliverySnapshot = pair?.delivery;
344
527
  const hasCompletionCandidates = deliverySnapshot
345
- ? Observability.collectRunCompletionBatchMembers(
528
+ ? Observability.hasRunCompletionBatchMembers(
346
529
  deliverySnapshot.transitions,
347
530
  deliverySnapshot.summary.runs,
348
- ).length > 0
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 watcher = (deps.createRunStateWatcher ?? Observability.createRunStateWatcher)({
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 reconciliation = (
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 (!running || !ownerId) return false;
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
- let batch = journal(ownerId).completion_batch;
434
- if (!batch) {
435
- const snapshot = Observability.readRunDeliverySnapshot(
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 (!RunDelivery.markRunCompletionBatchQueued({
480
- batchId: batch.batch_id,
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 (!running || !ownerId) return messages;
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 (!RunDelivery.markRunCompletionBatchQueued({
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
- `Actor shutdown teardown: killed=${teardown.killed} failed=${teardown.failed} skipped=${teardown.skipped} discovery_failed=${teardown.discoveryFailed}. Summary: ${teardown.summaryPath ?? "unavailable"}.`,
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
- recoverPresentedBatch(ownerId);
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, currentOwnerId) =>
600
- update(current, currentOwnerId),
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) {