@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
@@ -7,8 +7,12 @@ import { closeSync, existsSync, fstatSync, openSync, readdirSync, readFileSync,
7
7
  import { basename, dirname, isAbsolute, join, relative, } from "node:path";
8
8
  import * as AsyncRuns from "./async-runs.js";
9
9
  import * as Paths from "./paths.js";
10
+ import * as Limits from "./limits.js";
10
11
  import * as RunsTrace from "./runs-trace.js";
12
+ import { readRunCompletionTextSource, readRunResultArtifactInventory, readRunResultDiagnosticOutcome } from "./runs-context-exports.js";
11
13
  import { readJsonlFileResilient } from "./state-readers.js";
14
+ import { Document } from "yaml";
15
+ import { formatLiteralBlock, formatMarkdownField } from "./tools-response.js";
12
16
  export function createRunUiObservationState() {
13
17
  return {
14
18
  attentionEventIds: new Map(),
@@ -21,10 +25,12 @@ export function primeRunAttentionState(state, ownerId, stateRoot) {
21
25
  detectRunAttentionEvents(state.legacyEventLines, summarizeRuns(stateRoot, ownerId), state.attentionEventIds, true);
22
26
  }
23
27
  export function readRunUiSnapshot(state, ownerId, options = {}) {
24
- const summary = summarizeRuns(options.stateRoot, ownerId);
28
+ return projectRunUiSnapshot(state, summarizeRuns(options.stateRoot, ownerId), options.includeAttention);
29
+ }
30
+ function projectRunUiSnapshot(state, summary, includeAttention) {
25
31
  const status = renderRunStatus(summary, state.frame++);
26
32
  return {
27
- attentionEvents: options.includeAttention === false
33
+ attentionEvents: includeAttention === false
28
34
  ? []
29
35
  : detectRunAttentionEvents(state.legacyEventLines, summary, state.attentionEventIds),
30
36
  status,
@@ -34,7 +40,9 @@ export function readRunUiSnapshot(state, ownerId, options = {}) {
34
40
  }
35
41
  /** Observe every Run generation routed to one root coordinator. */
36
42
  export function readRunDeliverySnapshot(state, deliveryOwnerId, stateRoot) {
37
- const summary = summarizeRuns(stateRoot, undefined, deliveryOwnerId);
43
+ return projectRunDeliverySnapshot(state, summarizeRuns(stateRoot, undefined, deliveryOwnerId));
44
+ }
45
+ function projectRunDeliverySnapshot(state, summary) {
38
46
  return {
39
47
  attentionEvents: [],
40
48
  status: undefined,
@@ -42,6 +50,14 @@ export function readRunDeliverySnapshot(state, deliveryOwnerId, stateRoot) {
42
50
  transitions: detectRunTransitions(state.observed, summary),
43
51
  };
44
52
  }
53
+ /** Same-pass display/candidate projections only. No frame escapes or native fact is reused by flush/publication/control. */
54
+ export function readRunUiAndDeliverySnapshots(uiState, deliveryState, ownerId, stateRoot = Paths.getRunStateRoot()) {
55
+ const runs = readRunObservations(stateRoot);
56
+ return {
57
+ ui: projectRunUiSnapshot(uiState, summarizeRunObservations(runs, stateRoot, ownerId)),
58
+ delivery: projectRunDeliverySnapshot(deliveryState, summarizeRunObservations(runs, stateRoot, undefined, ownerId)),
59
+ };
60
+ }
45
61
  export function pruneRunUiObservationState(state, snapshot) {
46
62
  pruneRunObservationState(state.observed, state.legacyEventLines, snapshot.summary, snapshot.transitions.map((transition) => transition.stateDir ?? transition.run), state.attentionEventIds);
47
63
  }
@@ -90,7 +106,10 @@ export function createRunStateWatcher(input) {
90
106
  diagnostics.push({
91
107
  code,
92
108
  id: ++diagnosticId,
93
- message: `Run-state ${scope} watcher ${code.replace("_", " ")} for ${path}${detail}${recovery}`,
109
+ message: [formatMarkdownField("watcher", code), formatMarkdownField("scope", scope),
110
+ formatMarkdownField("path", formatLiteralBlock(path)),
111
+ ...(detail ? [formatMarkdownField("diagnostic", detail.slice(2))] : []),
112
+ formatMarkdownField("recovery", recovery.slice(2))].join("\n"),
94
113
  path,
95
114
  scope,
96
115
  ts: new Date().toISOString(),
@@ -317,12 +336,14 @@ function terminalSemanticResult(status, stateDir, observedStatus) {
317
336
  const type = reviewCompleted
318
337
  ? "review.completed"
319
338
  : observedStatus === "done" ? "run.done" : "run.failed";
320
- const stdoutBody = observedStatus === "done"
321
- ? semanticBodyFromStdout(stateDir) : undefined;
339
+ const hasPrimary = Object.hasOwn(result, "primary_text_protocol");
340
+ const hasDiagnostics = Object.hasOwn(result, "result_diagnostics_protocol");
341
+ const stdoutBody = !hasPrimary && !hasDiagnostics && observedStatus === "done" ? semanticBodyFromStdout(stateDir) : undefined;
322
342
  return {
323
- ...(stdoutBody
324
- ? { body: stdoutBody }
325
- : typeof result.error === "string" ? { body: result.error } : {}),
343
+ ...(hasPrimary ? { hasPrimaryText: true } : {}),
344
+ ...(Object.hasOwn(result, "artifact_inventory_protocol") ? { hasArtifactInventory: true } : {}),
345
+ ...(hasDiagnostics ? { hasResultDiagnostics: true } : {}),
346
+ ...(!hasDiagnostics && (stdoutBody || typeof result.error === "string") ? { body: stdoutBody ?? result.error } : {}),
326
347
  ...(correlationId ? { correlationId } : {}),
327
348
  metadata: {
328
349
  ...(status.transport_context &&
@@ -339,9 +360,9 @@ function terminalSemanticResult(status, stateDir, observedStatus) {
339
360
  type,
340
361
  };
341
362
  }
342
- function observeRun(stateDir) {
363
+ function observeRun(stateDir, observed) {
343
364
  try {
344
- const status = AsyncRuns.getRunStatus(stateDir);
365
+ const status = observed ?? AsyncRuns.getRunStatus(stateDir);
345
366
  const progress = getProgress(status);
346
367
  const run = typeof status.run === "string" ? status.run : undefined;
347
368
  if (!run)
@@ -414,8 +435,19 @@ function observeRun(stateDir) {
414
435
  return undefined;
415
436
  }
416
437
  }
438
+ function readRunObservations(stateRoot) {
439
+ if (!existsSync(stateRoot))
440
+ return undefined;
441
+ const directories = AsyncRuns.readRunStateIndex(stateRoot)?.map((entry) => entry.state_dir) ?? scanRunStateDirs(stateRoot);
442
+ return AsyncRuns.getRunInventoryStatuses(directories)
443
+ .map((status, index) => status ? observeRun(directories[index], status) : undefined)
444
+ .filter((run) => Boolean(run));
445
+ }
417
446
  export function summarizeRuns(stateRoot = Paths.getRunStateRoot(), ownerId, deliveryOwnerId) {
418
- if (!existsSync(stateRoot)) {
447
+ return summarizeRunObservations(readRunObservations(stateRoot), stateRoot, ownerId, deliveryOwnerId);
448
+ }
449
+ function summarizeRunObservations(observations, stateRoot, ownerId, deliveryOwnerId) {
450
+ if (observations === undefined) {
419
451
  return {
420
452
  cancelled: 0,
421
453
  done: 0,
@@ -428,10 +460,7 @@ export function summarizeRuns(stateRoot = Paths.getRunStateRoot(), ownerId, deli
428
460
  total: 0,
429
461
  };
430
462
  }
431
- const runs = (AsyncRuns.readRunStateIndex(stateRoot)?.map((entry) => entry.state_dir) ??
432
- scanRunStateDirs(stateRoot))
433
- .map((stateDir) => observeRun(stateDir))
434
- .filter((run) => Boolean(run))
463
+ const runs = observations
435
464
  .filter((run) => deliveryOwnerId !== undefined
436
465
  ? run.deliveryOwnerId === deliveryOwnerId ||
437
466
  (run.deliveryOwnerId === undefined && run.ownerId === deliveryOwnerId)
@@ -532,8 +561,12 @@ function summarizeRunsWithoutSubagents(stateRoot, ownerId) {
532
561
  };
533
562
  }
534
563
  export function countRunningSubagentsByRun(stateRoot = Paths.getRunStateRoot(), ownerId) {
564
+ // This backend cannot contribute without procfs; do not pay for native Run
565
+ // identity probes (notably synchronous Windows/macOS probes) just to discard them.
566
+ if (!existsSync("/proc"))
567
+ return new Map();
535
568
  const runPidMap = getRunningRunPidMap(stateRoot, ownerId);
536
- if (runPidMap.size === 0 || !existsSync("/proc"))
569
+ if (runPidMap.size === 0)
537
570
  return new Map();
538
571
  const signature = [...runPidMap.keys()].sort().join(",");
539
572
  const cacheKey = `${stateRoot}\0${ownerId ?? ""}`;
@@ -639,6 +672,17 @@ export function findRunRetirementCandidates(summary) {
639
672
  terminalChildRuns: item.terminalChildRuns,
640
673
  }));
641
674
  }
675
+ function formatRunRetirementNotice(run, action, message = "") {
676
+ const fields = [formatMarkdownField("run", run.startsWith("run:") ? run.slice(4) : run), formatMarkdownField("retirement", action)];
677
+ if (action === "stop")
678
+ fields.push(formatMarkdownField("result", "request returned"), formatMarkdownField("reason", "child runs reached terminal state"));
679
+ else if (action === "cancel")
680
+ fields.push(formatMarkdownField("result", "cancellation reported"), formatMarkdownField("reason", "stop path failed"));
681
+ else
682
+ fields.push(formatMarkdownField("result", action === "skip" ? "skipped" : "failed"), formatMarkdownField("diagnostic", message));
683
+ fields.push(formatMarkdownField("closure", "unconfirmed"));
684
+ return fields.join("\n");
685
+ }
642
686
  export async function executeRunRetirements(summary, options) {
643
687
  const results = [];
644
688
  for (const candidate of findRunRetirementCandidates(summary)) {
@@ -653,7 +697,7 @@ export async function executeRunRetirements(summary, options) {
653
697
  options.attempted?.add(candidate.stateDir);
654
698
  try {
655
699
  await options.sendStop(candidate);
656
- options.notify?.(`Retiring actor ${candidate.run} after child runs reached terminal state`, "info");
700
+ options.notify?.(formatRunRetirementNotice(candidate.run, "stop"), "info");
657
701
  results.push({
658
702
  action: "stop",
659
703
  run: candidate.run,
@@ -666,8 +710,8 @@ export async function executeRunRetirements(summary, options) {
666
710
  const cancelResult = options.cancelRun(candidate);
667
711
  const cancelled = Boolean(cancelResult.cancelled);
668
712
  options.notify?.(cancelled
669
- ? `Retiring actor ${candidate.run} by cancellation after graceful stop failed`
670
- : `Actor retirement skipped for ${candidate.run}: ${error instanceof Error ? error.message : String(error)}`, cancelled ? "warning" : "error");
713
+ ? formatRunRetirementNotice(candidate.run, "cancel")
714
+ : formatRunRetirementNotice(candidate.run, "skip", `${error instanceof Error ? error.message : String(error)}`), cancelled ? "warning" : "error");
671
715
  results.push({
672
716
  action: cancelled ? "cancel" : "skip",
673
717
  ...(cancelled
@@ -683,7 +727,7 @@ export async function executeRunRetirements(summary, options) {
683
727
  const message = cancelError instanceof Error
684
728
  ? cancelError.message
685
729
  : String(cancelError);
686
- options.notify?.(`Actor retirement failed for ${candidate.run}: ${message}`, "error");
730
+ options.notify?.(formatRunRetirementNotice(candidate.run, "failed", `${message}`), "error");
687
731
  results.push({
688
732
  action: "failed",
689
733
  error: message,
@@ -709,6 +753,7 @@ export function detectRunTransitions(previous, summary) {
709
753
  transitions.push({
710
754
  from: old ?? "running",
711
755
  run: run.run,
756
+ ...(run.ownerId === undefined ? {} : { ownerId: run.ownerId }),
712
757
  ...(run.deliveryParent ? { deliveryParent: run.deliveryParent } : {}),
713
758
  ...(run.stateDir ? { stateDir: run.stateDir } : {}),
714
759
  ...(run.artifacts ? { artifacts: run.artifacts } : {}),
@@ -892,26 +937,6 @@ function relativeName(base, path) {
892
937
  const name = relative(base, path);
893
938
  return name && !name.startsWith("..") ? name : basename(path) || path;
894
939
  }
895
- function formatPathGroup(label, paths) {
896
- const unique = [...new Set(paths.filter(Boolean))].slice(0, 8);
897
- if (unique.length === 0)
898
- return "";
899
- const base = commonDirectory(unique);
900
- const names = unique
901
- .map((path) => `\`${relativeName(base, path)}\``)
902
- .join(", ");
903
- return `\n${label}:\n- Base: ${base ? `\`${base}\`` : "current run"}\n- Files: ${names}`;
904
- }
905
- function formatRunFileList(files) {
906
- if (!Array.isArray(files))
907
- return "";
908
- return formatPathGroup("Run files", files.filter((file) => typeof file === "string"));
909
- }
910
- function formatNamedArtifacts(artifacts) {
911
- if (!artifacts || typeof artifacts !== "object" || Array.isArray(artifacts))
912
- return "";
913
- return formatPathGroup("Artifacts", Object.values(artifacts).filter((path) => typeof path === "string"));
914
- }
915
940
  function getAttentionDataField(event, key) {
916
941
  return event.data &&
917
942
  typeof event.data === "object" &&
@@ -919,19 +944,96 @@ function getAttentionDataField(event, key) {
919
944
  ? event.data[key]
920
945
  : undefined;
921
946
  }
922
- function formatBodyPreview(body) {
923
- if (body === undefined)
924
- return "";
925
- const rendered = typeof body === "string" ? body : JSON.stringify(body);
926
- const compact = rendered.replaceAll(/\s+/g, " ").trim();
927
- if (!compact)
928
- return "";
929
- return `\nBody: ${compact.length > 500 ? `${compact.slice(0, 500)}…` : compact}`;
947
+ /** Only selected shallow compiler fields enter YAML; the selected body stays opaque and unindented. */
948
+ function renderAttention({ metadata, body }) {
949
+ const header = new Document(metadata).toString({ lineWidth: 0, defaultStringType: "QUOTE_DOUBLE", defaultKeyType: "PLAIN" }).slice(0, -1);
950
+ return body === undefined ? header : `---\n${header}\n---\n${body}`;
930
951
  }
952
+ /** Ordinary selection has no urgent-steer or compact-output byte/depth allowance. */
931
953
  export function formatRunAttentionMessage(event) {
954
+ const checkpoint = formatAXAttentionCheckpoint(event);
932
955
  if (event.kind === "command.done")
933
- return `Run ${event.run}: ${event.summary}`;
934
- return `Run ${event.run}: ${event.summary}${formatBodyPreview(event.body)}${formatNamedArtifacts(getAttentionDataField(event, "artifacts"))}${formatRunFileList(getAttentionDataField(event, "run_files"))}`;
956
+ return renderAttention(checkpoint);
957
+ for (const section of attentionPathSections(event)) {
958
+ checkpoint.metadata[section.key] = formatAXAttentionPaths(section.paths.slice(0, 8).filter(path => Buffer.from(path).toString("utf8") === path), section.paths.length);
959
+ }
960
+ return renderAttention(checkpoint);
961
+ }
962
+ function formatAXAttentionCheckpoint(event) {
963
+ const run = event.run.startsWith("run:") ? event.run.slice(4) : event.run;
964
+ const metadata = { run, checkpoint: event.summary };
965
+ const checkpoint = { metadata };
966
+ if (event.kind === "command.done" || event.body === undefined)
967
+ return checkpoint;
968
+ const rendered = typeof event.body === "string" ? event.body : JSON.stringify(event.body);
969
+ if (!rendered.trim())
970
+ return checkpoint;
971
+ let end = Math.min(rendered.length, 500);
972
+ const last = rendered.charCodeAt(end - 1), next = rendered.charCodeAt(end);
973
+ if (last >= 0xd800 && last <= 0xdbff && next >= 0xdc00 && next <= 0xdfff)
974
+ end--;
975
+ const preview = rendered.slice(0, end), clipped = end < rendered.length;
976
+ if (Buffer.from(preview).toString("utf8") !== preview) {
977
+ metadata.body = "unavailable";
978
+ metadata.body_reason = "malformed_legacy_preview";
979
+ metadata.body_omission = "[omitted: unknown]";
980
+ }
981
+ else {
982
+ // Retain the old final body observation and structured-preview ellipsis placement, not its compiler fence.
983
+ checkpoint.body = typeof event.body === "string" ? preview + (clipped ? "…" : "") : preview + (clipped ? "\n…" : "");
984
+ if (clipped)
985
+ metadata.body_omission = "[omitted: unknown]";
986
+ }
987
+ metadata.body_basis = "Legacy record; original completeness/privacy unproven.";
988
+ metadata.body_upstream = "[omitted: unknown]";
989
+ return checkpoint;
990
+ }
991
+ function attentionPathSections(event) {
992
+ const artifacts = getAttentionDataField(event, "artifacts"), files = getAttentionDataField(event, "run_files");
993
+ if (event.kind === "command.done")
994
+ return [];
995
+ return [
996
+ { key: "artifacts", values: artifacts && typeof artifacts === "object" && !Array.isArray(artifacts) ? Object.values(artifacts) : [] },
997
+ { key: "run_files", values: Array.isArray(files) ? files : [] },
998
+ ].map(({ key, values }) => ({ key,
999
+ paths: [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))],
1000
+ })).filter(section => section.paths.length > 0);
1001
+ }
1002
+ function formatAXAttentionPaths(paths, total) {
1003
+ return { shown: paths.length, omitted: total - paths.length, total, ...(paths.length ? { paths } : {}) };
1004
+ }
1005
+ /** Saved envelope.content is replayed before this formatter; only fresh complete candidates are fitted. */
1006
+ export function formatRunSteerMessage(event) {
1007
+ const limit = Limits.RUN_DELIVERY_STEER_MAX_BYTES;
1008
+ const checkpoint = formatAXAttentionCheckpoint(event);
1009
+ // YAML can escape malformed UTF-16: validate essential source values before serialization or reference reads.
1010
+ if ([checkpoint.metadata.run, checkpoint.metadata.checkpoint].some(value => Buffer.from(value).toString("utf8") !== value)
1011
+ || Buffer.byteLength(renderAttention(checkpoint)) > limit) {
1012
+ throw new Error("Urgent steer checkpoint text exceeds its Unicode/byte delivery limits");
1013
+ }
1014
+ const sections = attentionPathSections(event);
1015
+ if (event.kind === "command.done")
1016
+ return renderAttention(checkpoint);
1017
+ const selected = { ...checkpoint.metadata };
1018
+ for (const [index, section] of sections.entries()) {
1019
+ const reserve = {};
1020
+ for (const next of sections.slice(index + 1))
1021
+ reserve[next.key] = formatAXAttentionPaths([], next.paths.length);
1022
+ const candidates = section.paths.slice(0, 8).filter(path => Buffer.from(path).toString("utf8") === path);
1023
+ let fitted = false;
1024
+ for (let shown = candidates.length; shown >= 0; shown -= 1) {
1025
+ const group = formatAXAttentionPaths(candidates.slice(0, shown), section.paths.length);
1026
+ const candidate = { ...selected, [section.key]: group, ...reserve };
1027
+ if (Buffer.byteLength(renderAttention({ ...checkpoint, metadata: candidate })) <= limit) {
1028
+ selected[section.key] = group;
1029
+ fitted = true;
1030
+ break;
1031
+ }
1032
+ }
1033
+ if (!fitted)
1034
+ throw new Error("Urgent steer checkpoint and reference coverage exceed the delivery byte limit");
1035
+ }
1036
+ return renderAttention({ ...checkpoint, metadata: selected });
935
1037
  }
936
1038
  export function getRunTransitionNotificationType(transition) {
937
1039
  if (transition.to === "done" || transition.to === "cancelled")
@@ -975,68 +1077,83 @@ function deliveryTreePosition(input, runsByIdentity) {
975
1077
  }
976
1078
  return { depth, rootIdentity };
977
1079
  }
978
- /** Build exact immutable members only after each containing root Run is terminal. */
979
- export function collectRunCompletionBatchMembers(transitions, deliveryRuns = []) {
980
- const runsByIdentity = new Map(deliveryRuns.flatMap((run) => {
1080
+ /** Eligibility is metadata-only; ambient candidate checks must not read full primary bodies. */
1081
+ function readyRunCompletions(transitions, deliveryRuns) {
1082
+ const runsByIdentity = new Map(deliveryRuns.flatMap(run => {
981
1083
  const identity = deliveryGenerationIdentity(run);
982
1084
  return identity ? [[identity, run]] : [];
983
1085
  }));
984
- const incompleteRoots = new Set(deliveryRuns.flatMap((run) => {
1086
+ const incompleteRoots = new Set(deliveryRuns.flatMap(run => {
985
1087
  if (TERMINAL.has(run.status))
986
1088
  return [];
987
1089
  const position = deliveryTreePosition(run, runsByIdentity);
988
1090
  return position ? [position.rootIdentity] : [];
989
1091
  }));
990
- const admitted = transitions.flatMap((transition) => {
991
- if (!shouldNotifyRunTransition(transition) ||
992
- !transition.stateDir ||
993
- !transition.runInstanceId ||
994
- !transition.terminalAt ||
995
- Number.isNaN(Date.parse(transition.terminalAt)))
1092
+ return transitions.flatMap(transition => {
1093
+ if (!shouldNotifyRunTransition(transition) || !transition.stateDir || !transition.runInstanceId ||
1094
+ !transition.terminalAt || Number.isNaN(Date.parse(transition.terminalAt)))
996
1095
  return [];
997
1096
  const position = deliveryTreePosition(transition, runsByIdentity);
998
1097
  if (!position || incompleteRoots.has(position.rootIdentity))
999
1098
  return [];
1000
- const { depth } = position;
1001
- const artifactEntries = Object.entries(transition.artifacts ?? {})
1002
- .filter((entry) => typeof entry[1] === "string" && Boolean(entry[1]))
1003
- .slice(0, 4);
1004
- const rawSummary = transition.semanticResult?.summary.trim() ||
1005
- `Run ${transition.to}.`;
1006
- return [{
1007
- ...(artifactEntries.length > 0
1008
- ? { artifacts: Object.fromEntries(artifactEntries) }
1009
- : {}),
1010
- ...(transition.semanticResult?.body?.trim()
1011
- ? {
1012
- output: transition.semanticResult.body.trim().length > 4_000
1013
- ? `${transition.semanticResult.body.trim().slice(0, 3_999)}…`
1014
- : transition.semanticResult.body.trim(),
1015
- }
1016
- : {}),
1017
- ...(transition.deliveryParent
1018
- ? {
1019
- parent_run: transition.deliveryParent.run,
1020
- parent_run_instance_id: transition.deliveryParent.run_instance_id,
1021
- parent_state_dir: transition.deliveryParent.state_dir,
1022
- }
1023
- : {}),
1024
- run: transition.run,
1025
- run_instance_id: transition.runInstanceId,
1026
- state_dir: transition.stateDir,
1027
- status: transition.to,
1028
- summary: rawSummary.length > 1_000
1029
- ? `${rawSummary.slice(0, 999)}…`
1030
- : rawSummary,
1031
- terminal_at: transition.terminalAt,
1032
- tree_depth: depth,
1033
- }];
1099
+ return [{ transition: { ...transition, stateDir: transition.stateDir, runInstanceId: transition.runInstanceId,
1100
+ terminalAt: transition.terminalAt }, depth: position.depth }];
1101
+ });
1102
+ }
1103
+ export function hasRunCompletionBatchMembers(transitions, deliveryRuns = []) {
1104
+ return readyRunCompletions(transitions, deliveryRuns).length > 0;
1105
+ }
1106
+ /** Freeze primary/artifact source observations only for eligible members entering packet admission. */
1107
+ export function collectRunCompletionBatchMembers(transitions, deliveryRuns = []) {
1108
+ const admitted = readyRunCompletions(transitions, deliveryRuns).map(({ transition, depth }) => {
1109
+ const hasPrimary = transition.semanticResult?.hasPrimaryText === true;
1110
+ const hasDiagnostics = transition.semanticResult?.hasResultDiagnostics === true;
1111
+ const diagnosticReading = hasDiagnostics ? readRunResultDiagnosticOutcome(transition.stateDir, { run: transition.run,
1112
+ run_instance_id: transition.runInstanceId, ...(transition.ownerId === undefined ? {} : { ownerId: transition.ownerId }) }, transition.to) : undefined;
1113
+ const diagnostics = diagnosticReading?.state === "available" && diagnosticReading.terminal_status === transition.to && diagnosticReading.unchanged()
1114
+ ? { ...diagnosticReading.binding, diagnostic_excerpt: diagnosticReading.excerpt } : diagnosticReading
1115
+ ? { result_diagnostics: null, result_diagnostics_unavailable: diagnosticReading.state === "unavailable"
1116
+ ? diagnosticReading.result_diagnostics_unavailable ?? diagnosticReading.reason : diagnosticReading.terminal_status !== transition.to ? "outcome_mismatch" : "observation_changed" } : undefined;
1117
+ const reading = hasPrimary ? readRunCompletionTextSource(transition.stateDir, { run: transition.run,
1118
+ run_instance_id: transition.runInstanceId, ...(transition.ownerId === undefined ? {} : { ownerId: transition.ownerId }) }) : undefined;
1119
+ const primary = !reading ? undefined
1120
+ : reading.state === "available" && reading.result_acceptance === "natural" && transition.to === "done" && reading.unchanged()
1121
+ ? Object.freeze({ output: reading.original_output ?? reading.output })
1122
+ : Object.freeze({ output: null, output_unavailable: reading.state === "unavailable"
1123
+ ? reading.output_unavailable ?? reading.reason : reading.result_acceptance !== "natural" ? "nonterminal_capture" : "observation_changed" });
1124
+ // Existing unsupported session/composition/failure displays stay diagnostics, never substitute for a lost source.
1125
+ const legacyDisplay = !hasPrimary || (reading?.state === "unavailable" && reading.reason === "output_unavailable" &&
1126
+ ["not_selected", "selection_not_supported", "primary_not_selected_for_failure"].includes(reading.output_unavailable ?? ""));
1127
+ const body = !hasDiagnostics && legacyDisplay ? transition.semanticResult?.body ??
1128
+ (hasPrimary && transition.to === "done" ? semanticBodyFromStdout(transition.stateDir) : undefined) : undefined;
1129
+ const hasInventory = transition.semanticResult?.hasArtifactInventory === true;
1130
+ const inventoryReading = hasInventory ? readRunResultArtifactInventory(transition.stateDir, { run: transition.run,
1131
+ run_instance_id: transition.runInstanceId, ...(transition.ownerId === undefined ? {} : { ownerId: transition.ownerId }) }) : undefined;
1132
+ const inventory = inventoryReading?.state === "available" && inventoryReading.terminal_status === transition.to && inventoryReading.unchanged() ? inventoryReading.binding : inventoryReading
1133
+ ? { artifact_inventory: null, artifact_inventory_unavailable: inventoryReading.state === "unavailable"
1134
+ ? inventoryReading.artifact_inventory_unavailable ?? inventoryReading.reason : inventoryReading.terminal_status !== transition.to ? "outcome_mismatch" : "observation_changed" } : undefined;
1135
+ const artifactEntries = hasInventory ? [] : Object.entries(transition.artifacts ?? {})
1136
+ .filter((entry) => typeof entry[1] === "string" && Boolean(entry[1])).slice(0, 4);
1137
+ const rawSummary = transition.semanticResult?.summary.trim() || `Run ${transition.to}.`;
1138
+ return {
1139
+ ...(artifactEntries.length > 0 ? { artifacts: Object.fromEntries(artifactEntries) } : {}),
1140
+ ...(primary ? { primary_text: primary } : {}),
1141
+ ...(primary?.output && reading?.state === "available" ? { completion_text: Object.freeze({ text: reading.output.preview,
1142
+ shown_bytes: reading.output.shown_bytes }) } : {}),
1143
+ ...inventory,
1144
+ ...diagnostics,
1145
+ ...(body?.trim() ? { output: body.trim().length > 4_000 ? `${body.trim().slice(0, 3_999)}…` : body.trim() } : {}),
1146
+ ...(transition.deliveryParent ? { parent_run: transition.deliveryParent.run,
1147
+ parent_run_instance_id: transition.deliveryParent.run_instance_id, parent_state_dir: transition.deliveryParent.state_dir } : {}),
1148
+ run: transition.run, run_instance_id: transition.runInstanceId, state_dir: transition.stateDir,
1149
+ status: transition.to,
1150
+ summary: rawSummary.length > 1_000 ? `${rawSummary.slice(0, 999)}…` : rawSummary,
1151
+ terminal_at: transition.terminalAt, tree_depth: depth,
1152
+ };
1034
1153
  });
1035
1154
  return admitted.sort((left, right) => left.tree_depth - right.tree_depth ||
1036
- left.terminal_at.localeCompare(right.terminal_at) ||
1037
- left.run.localeCompare(right.run) ||
1038
- left.run_instance_id.localeCompare(right.run_instance_id) ||
1039
- left.state_dir.localeCompare(right.state_dir)).map(({ tree_depth: _depth, ...member }) => member);
1155
+ left.terminal_at.localeCompare(right.terminal_at) || left.run.localeCompare(right.run) ||
1156
+ left.run_instance_id.localeCompare(right.run_instance_id) || left.state_dir.localeCompare(right.state_dir)).map(({ tree_depth: _depth, ...member }) => member);
1040
1157
  }
1041
1158
  const TERMINAL_FOLLOW_UP_ARTIFACT_LIMIT = 4;
1042
1159
  const TERMINAL_FOLLOW_UP_IDENTIFIER_CHARS = 120;
package/dist/lib/pi.d.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  * Zones: pi agent sdk boundary, extension host adapters
4
4
  * Owns direct pi SDK imports and exposes narrow pi-actors-facing helpers/types for the composition root.
5
5
  */
6
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
6
+ import type { ExtensionAPI, ExtensionContext, SessionStartEvent, CustomMessageEntryDraft, TurnEndEvent, TurnEndEventResult } from "@earendil-works/pi-coding-agent";
7
7
  import * as SessionEvidence from "./session-evidence.ts";
8
- export type { ExtensionAPI, ExtensionContext };
8
+ export type { ExtensionAPI, ExtensionContext, SessionStartEvent, TurnEndEvent, TurnEndEventResult };
9
+ export { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
9
10
  export declare const RUN_COMPLETION_BATCH_CUSTOM_TYPE = "pi-actors-run-batch";
10
11
  export declare const RUN_STEER_CUSTOM_TYPE = "pi-actors-run-steer";
11
12
  export interface PiNotificationSink {
@@ -19,7 +20,8 @@ export interface PiNotificationSink {
19
20
  }
20
21
  export declare function getSessionId(ctx: ExtensionContext): string;
21
22
  export declare function createNotificationSink(pi: ExtensionAPI, ctx: ExtensionContext): PiNotificationSink;
22
- export declare function sendRunCompletionBatch(pi: ExtensionAPI, batchId: string, content: string): void;
23
+ export declare function createRunCompletionBatchEntry(batchId: string, content: string, presentation?: unknown): CustomMessageEntryDraft;
24
+ export declare function sendRunCompletionBatch(pi: ExtensionAPI, batchId: string, content: string, presentation?: unknown): void;
23
25
  export declare function sendRunSteer(pi: ExtensionAPI, input: {
24
26
  content: string;
25
27
  eventId: string;
@@ -41,6 +43,10 @@ export declare function dedupeRunSteerContext(messages: unknown[]): {
41
43
  }>;
42
44
  };
43
45
  export declare function removeRunSteerFromContext(messages: unknown[], steerId: string): unknown[];
46
+ export declare function inspectRunCompletionBatchSessionEvidence(ctx: ExtensionContext, input: {
47
+ batchId: string;
48
+ content: string;
49
+ }): SessionEvidence.ActiveSessionEntryEvidence;
44
50
  export declare function inspectRunSteerSessionEvidence(ctx: ExtensionContext, input: {
45
51
  content: string;
46
52
  eventId: string;