@llblab/pi-kit 0.6.0 → 0.7.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 (123) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +4 -4
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
  4. package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
  5. package/node_modules/@llblab/pi-actors/README.md +1 -1
  6. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
  7. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
  10. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
  11. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
  13. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
  17. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
  18. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
  19. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
  20. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  21. package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
  22. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
  23. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
  24. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
  25. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
  26. package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
  27. package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
  28. package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
  29. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
  30. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
  31. package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
  32. package/node_modules/@llblab/pi-actors/package.json +1 -1
  33. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  34. package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
  35. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
  36. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
  37. package/node_modules/@llblab/pi-state-flow/README.md +203 -107
  38. package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
  39. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
  41. package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
  42. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
  43. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
  47. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
  48. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
  50. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
  51. package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
  52. package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
  53. package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
  54. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
  55. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
  57. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
  58. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
  59. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
  60. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
  61. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
  62. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
  63. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
  64. package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
  65. package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
  66. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
  68. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
  69. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
  70. package/node_modules/@llblab/pi-state-flow/package.json +8 -2
  71. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
  72. package/node_modules/@llblab/pi-telegram/AGENTS.md +13 -8
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +20 -3
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +12 -8
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +220 -18
  78. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  79. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
  80. package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
  81. package/node_modules/@llblab/pi-telegram/docs/public-api.md +12 -5
  82. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
  83. package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
  84. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
  85. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +28 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  87. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  88. package/node_modules/@llblab/pi-telegram/lib/bus.ts +350 -26
  89. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +544 -0
  90. package/node_modules/@llblab/pi-telegram/lib/commands.ts +234 -11
  91. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -24
  92. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1792 -0
  93. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  94. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  95. package/node_modules/@llblab/pi-telegram/lib/locks.ts +38 -1
  96. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  97. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +51 -10
  98. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  99. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  100. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +5 -2
  101. package/node_modules/@llblab/pi-telegram/lib/queue.ts +52 -18
  102. package/node_modules/@llblab/pi-telegram/lib/replies.ts +2 -2
  103. package/node_modules/@llblab/pi-telegram/lib/routing.ts +305 -112
  104. package/node_modules/@llblab/pi-telegram/lib/status.ts +10 -0
  105. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  106. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +279 -4
  107. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  108. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  109. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  110. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  111. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  112. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  113. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  114. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  115. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  116. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  117. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  118. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  119. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  120. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  121. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  122. package/node_modules/@llblab/skills/package.json +2 -3
  123. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to `@llblab/pi-kit` are documented here.
4
4
 
5
+ ## 0.7.0 - 2026-09-10
6
+
7
+ - `Current Distribution`: Advances Actors to `0.53.0`, State Flow to `0.6.0`, Telegram to `0.45.0`, and Skills to `1.15.0`. The kit now includes durable Workspace-bound Telegram Threads, expanded run orchestration, current opt-in state handoffs, and the latest portable workflows while preserving the package set, resource inventory, and explicit load order.
8
+
5
9
  ## 0.6.0 - 2026-09-06
6
10
 
7
11
  - `Current Distribution`: Advances Telegram to `0.44.0`, Grow Loop to `0.7.5`, and Skills to `1.14.1`. The kit now includes reliable default-on Telegram draft previews and prompt anchors, unambiguous `grow-loop` runtime status, and ABCd contiguous-list validation while preserving the existing package set, resource inventory, and load order.
package/README.md CHANGED
@@ -10,13 +10,13 @@ Package links lead to the owning repositories for usage, documentation, issues,
10
10
 
11
11
  | Package | Version | Purpose |
12
12
  | --- | ---: | --- |
13
- | [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.52.1` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
13
+ | [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.53.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
14
14
  | [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.1.1` | Isolated nested Pi TUI with explicitly selected extensions |
15
15
  | [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.9.4` | Compact Codex/Spark subscription-limit status |
16
16
  | [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.7.5` | Visible continuation scheduling and bounded worker Skills |
17
- | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.3.0` | Opt-in validated state handoffs between agent runs |
18
- | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.44.0` | Telegram companion, queues, files, voice, controls, and Generative Apps guidance |
19
- | [`@llblab/skills`](https://github.com/llblab/skills) | `1.14.1` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
17
+ | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.6.0` | Opt-in validated state handoffs between agent runs |
18
+ | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.45.0` | Telegram companion, queues, files, voice, controls, and Generative Apps guidance |
19
+ | [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
20
20
 
21
21
  Versions are exact by design. An upstream release does not change an installed kit until this repository explicitly advances the dependency and publishes a new kit version. Runtime defects and package-specific feature requests belong in the linked repository; package selection and kit installation issues belong here.
22
22
 
@@ -136,7 +136,7 @@ Tool result/error text contributes exactly one leading line break. Keep model-fa
136
136
 
137
137
  File watchers accelerate reconciliation; a bounded interval recovers missed terminal and retained-attention events. Canonical attention observation uses stable retained ids and bounds seen memory to the current suffix across compaction; only allowlisted legacy outbox fallback uses line offsets. Ordinary `notify` and `followup` hints prime at startup without replay and require durable state or artifacts for recovery. Explicit `steer` instead re-enters through the owner journal until exact presentation records its newer generation-fenced Trace marker.
138
138
 
139
- Ordinary root terminals remain authoritative in Run state until the owner-scoped completion scheduler snapshots an immutable batch at a settled or idle boundary. Batch delivery is generation-fenced and bounded at 256 durable members, 64 model-facing rows, 16 KiB of context, and a 1 MiB journal. `sendMessage()` acceptance advances only to queued; only the exact batch id and content appearing in model-bound `context` advances to presented and authorizes exact-generation handled markers. Restart recovery walks the bounded active Pi session parent chain: exact queued evidence waits without resend, proven absence returns the envelope to pending, and incomplete or conflicting evidence remains queued with diagnostics. Retry duplicates collapse before presentation, malformed envelopes fail closed, and automatic review waits behind an active completion batch.
139
+ Ordinary terminals retain inherited delivery lineage from each nested Actor process to the top-level coordinator. Only that root coordinator may schedule completion follow-ups; descendant sessions stay quiet, descendants wait for their containing top-level Run to terminate, and the complete ready forest is flattened into one immutable settled/idle batch with parent-child rows and bounded semantic outputs. The same batch renders as one visible operator card and one model prompt. Batch delivery is generation-fenced and bounded at 256 durable members, 64 model-facing rows, 16 KiB of context, and a 1 MiB journal. Successful `sendMessage()` acceptance durably finalizes the completion batch and authorizes exact-generation handled markers, so a missed context callback cannot block later completion epochs. Send failure retains the pending batch for retry; queued recovery remains only for the narrow crash boundary between transport acceptance and finalization. Restart recovery walks the bounded active Pi session parent chain: exact queued evidence waits without resend, proven absence returns the envelope to pending, and incomplete or conflicting evidence remains queued with diagnostics. Retry duplicates collapse before presentation, malformed envelopes fail closed, and automatic review waits behind an active completion batch.
140
140
 
141
141
  When deferred Run results gate the next step, wait for their completion batch. Inspect early only for operator request, meaningful attention, or diagnosis of an overdue Run.
142
142
 
@@ -2,6 +2,12 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
+ ## 0.53.0: Tree-Compressed Completion Delivery
6
+
7
+ - `Quiet Draft Reloads`: Keeps automatic Recipe reload active for draft-directory changes while showing “Recipe tools refreshed” only for active non-draft Recipe changes or registry-root replacement, so ordinary spawn draft capture no longer produces misleading tool-refresh noise.
8
+ - `Tree-Compressed Completion Delivery`: Propagates exact root-owner and parent-generation lineage through nested Actor processes. Descendants stay quiet and wait for the complete top-level tree; the root batches ready trees and concurrent roots into one visible gray card plus one parent-child model prompt with bounded outputs. Completion forests render in stable root/descendant preorder. Successful Pi acceptance finalizes completion members immediately, preventing missed callbacks or long branches from blocking later epochs; settled/context delivery accepts fresh Pi context wrappers only for the exact active session owner, while urgent steer retains exact presentation acknowledgment.
9
+ - `Command Timeout Trees`: Terminates the full command-template descendant tree on timeout, preventing inherited output streams from leaving an Actor stuck after its command deadline while preserving outer Run process-group cancellation.
10
+
5
11
  ## 0.52.1: Unified Playback Control Hotfix
6
12
 
7
13
  - `Unified Playback CLI`: Consolidates standalone playback and external control into `playback.mjs`; standalone commands remain runtime-independent, while Actor-owned commands retain exact terminal Control evidence. The maintained Music Player Generative App uses the same explicit control entrypoint. Post-stop status remains readable despite leftover Run metadata; inactive controls fail before journal admission. External callers of the removed `playback-client.mjs` use `playback.mjs control` instead.
@@ -153,7 +153,7 @@ Trace fields are exact: `id`, `ts`, `kind`, and optional `summary`, `data`, `lev
153
153
 
154
154
  Generic command lifecycle is Trace-only: runner-owned `command.done` records preserve completion and execution evidence but never request or project attention, including malformed legacy records carrying `steer`. There is no Recipe-level command-completion delivery switch. Semantic attention is explicit: `notify` shows visible status without a model turn, `followup` supplies ordinary checkpoint context, and `steer` requests urgent delivery at Pi's next safe assistant/tool boundary. `notify` and `followup` remain retained wake hints that may compact away, so persist recovery state or an artifact first. `steer` is admitted to the bounded owner delivery journal before `deliverAs: "steer"`, acknowledged only in exact model-bound context, and never suppresses the eventual root-terminal batch. Store large evidence in artifacts or bounded execution captures.
155
155
 
156
- Ordinary finite Runs project root terminal results through one owner-fenced completion scheduler. Completions that accumulate while Pi is active or inside the idle debounce window enter one immutable bounded batch and cause one automatic agent turn after Pi settles; terminal generations are acknowledged only when that exact batch reaches model-bound context. Sequence, parallel, repeat, and imported branches remain internal and never create branch-level turns, while separately launched Runs retain exact independent generations. Silent Runs and synchronous stop acknowledgments remain non-projecting.
156
+ Ordinary finite Runs project terminal results through one root-coordinator completion scheduler. Nested actors inherit durable parent-generation lineage: their own Pi sessions never emit completion turns, their results wait until the containing top-level Run terminates, and the ready trees join other top-level completions accumulated during the same active/idle epoch. One immutable bounded forest causes one automatic agent turn after the root Pi settles. The operator sees one gray completion card, while the agent receives the same parent-child tree with bounded actor outputs as one prompt; terminal generations are acknowledged after Pi durably accepts the exact batch, preventing one missed context callback from blocking later completion epochs. Sequence, parallel, repeat, and imported branches remain internal, while separately launched Runs retain exact independent generations. Silent Runs and synchronous stop acknowledgments remain non-projecting.
157
157
 
158
158
  ## Control
159
159
 
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { CommandTemplateFailureScope, CommandTemplateValue } from "./command-templates.ts";
6
6
  import { type CurrentPolicyProvenance } from "./model-context.ts";
7
+ import * as RunDeliveryLineage from "./run-delivery-lineage.ts";
7
8
  import * as RecipesReferences from "./recipes-references.ts";
8
9
  import { type RunArtifactDeclaration } from "./runs-artifacts.ts";
9
10
  import { type RunProcessIdentity } from "./runs-process.ts";
@@ -92,6 +93,8 @@ export interface AsyncRunMeta {
92
93
  artifacts?: Record<string, RunArtifactDeclaration>;
93
94
  control?: string[];
94
95
  control_endpoint?: AsyncRunControlEndpoint;
96
+ delivery_owner_id?: string;
97
+ delivery_parent?: RunDeliveryLineage.RunDeliveryParent;
95
98
  model_policy?: CurrentPolicyProvenance;
96
99
  notification_policy?: "normal" | "silent";
97
100
  process_identity?: RunProcessIdentity;
@@ -11,6 +11,7 @@ import { isDeepStrictEqual } from "node:util";
11
11
  import { writeJsonAtomic } from "./file-state.js";
12
12
  import { CURRENT_MODEL_VALUE_KEY, CURRENT_THINKING_VALUE_KEY, describeCurrentPolicyProvenance, } from "./model-context.js";
13
13
  import * as Paths from "./paths.js";
14
+ import * as RunDeliveryLineage from "./run-delivery-lineage.js";
14
15
  import * as RecipesReferences from "./recipes-references.js";
15
16
  import * as RecipesUsage from "./recipes-usage.js";
16
17
  import * as Schema from "./schema.js";
@@ -369,6 +370,8 @@ export function startRun(params, cwd, options = {}) {
369
370
  };
370
371
  const transportContext = normalizeRunTransportContext(startParams.transport_context);
371
372
  const artifacts = resolveArtifactPaths(startParams.artifacts, outputValues);
373
+ const runInstanceId = randomUUID();
374
+ const deliveryLineage = RunDeliveryLineage.inheritedRunDeliveryLineage(startParams.ownerId);
372
375
  const meta = {
373
376
  argv: [process.execPath, ...argv],
374
377
  createdAt: new Date().toISOString(),
@@ -386,7 +389,7 @@ export function startRun(params, cwd, options = {}) {
386
389
  ...(recipe ? { recipe } : {}),
387
390
  ...(recipeFile ? { recipe_file: recipeFile } : {}),
388
391
  run,
389
- run_instance_id: randomUUID(),
392
+ run_instance_id: runInstanceId,
390
393
  state_dir: stateDir,
391
394
  state_schema: RuntimeIdentity.RUN_STATE_SCHEMA,
392
395
  status: "running",
@@ -399,6 +402,7 @@ export function startRun(params, cwd, options = {}) {
399
402
  ...(startParams.control_endpoint
400
403
  ? { control_endpoint: startParams.control_endpoint }
401
404
  : {}),
405
+ ...(deliveryLineage ?? {}),
402
406
  ...(startParams.notification_policy === "silent"
403
407
  ? { notification_policy: "silent" }
404
408
  : {}),
@@ -429,6 +433,15 @@ export function startRun(params, cwd, options = {}) {
429
433
  const child = spawn(process.execPath, argv, {
430
434
  cwd,
431
435
  detached: true,
436
+ ...(deliveryLineage
437
+ ? {
438
+ env: RunDeliveryLineage.runDeliveryChildEnv(deliveryLineage, {
439
+ run,
440
+ run_instance_id: runInstanceId,
441
+ state_dir: stateDir,
442
+ }),
443
+ }
444
+ : {}),
432
445
  stdio: ["ignore", outFd, errFd],
433
446
  });
434
447
  closeSync(outFd);
@@ -3,7 +3,7 @@
3
3
  * Zones: shell-free command parsing, placeholder expansion, local process execution, composition semantics
4
4
  * Owns portable command-template parsing, expansion, risk checks, retries, timeouts, and direct execution.
5
5
  */
6
- import { spawn } from "node:child_process";
6
+ import { spawn, spawnSync } from "node:child_process";
7
7
  import { accessSync, appendFileSync, constants, mkdirSync, mkdtempSync, statSync, writeFileSync } from "node:fs";
8
8
  import { homedir, tmpdir } from "node:os";
9
9
  import { basename, delimiter, extname, isAbsolute, join, resolve as resolvePath } from "node:path";
@@ -714,14 +714,56 @@ function execCommandTemplateOnce(command, args, options = {}) {
714
714
  let settled = false;
715
715
  let timeoutId;
716
716
  let killTimeoutId;
717
+ const signalProcessTree = (signal) => {
718
+ if (proc.pid === undefined)
719
+ return;
720
+ if (process.platform === "win32") {
721
+ spawnSync("taskkill", [
722
+ "/PID",
723
+ String(proc.pid),
724
+ "/T",
725
+ ...(signal === "SIGKILL" ? ["/F"] : []),
726
+ ]);
727
+ return;
728
+ }
729
+ const snapshot = spawnSync("ps", ["-eo", "pid=,ppid="], { encoding: "utf8" });
730
+ const children = new Map();
731
+ for (const line of snapshot.stdout?.split(/\r?\n/u) ?? []) {
732
+ const [pidText, parentText] = line.trim().split(/\s+/u);
733
+ const pid = Number(pidText);
734
+ const parent = Number(parentText);
735
+ if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(parent))
736
+ continue;
737
+ const siblings = children.get(parent) ?? [];
738
+ siblings.push(pid);
739
+ children.set(parent, siblings);
740
+ }
741
+ const descendants = [];
742
+ const collect = (parent) => {
743
+ for (const child of children.get(parent) ?? []) {
744
+ collect(child);
745
+ descendants.push(child);
746
+ }
747
+ };
748
+ collect(proc.pid);
749
+ for (const pid of [...descendants, proc.pid]) {
750
+ try {
751
+ process.kill(pid, signal);
752
+ }
753
+ catch (error) {
754
+ if (error.code !== "ESRCH")
755
+ throw error;
756
+ }
757
+ }
758
+ };
717
759
  const killProcess = () => {
718
760
  if (killed)
719
761
  return;
720
762
  killed = true;
721
- proc.kill("SIGTERM");
763
+ signalProcessTree("SIGTERM");
722
764
  killTimeoutId = setTimeout(() => {
723
765
  if (!settled)
724
- proc.kill("SIGKILL");
766
+ signalProcessTree("SIGKILL");
725
767
  }, options.killGrace ?? 5000);
726
768
  };
727
769
  const settle = (code) => {
@@ -113,7 +113,7 @@ export function createActorExtensionRuntime(pi) {
113
113
  },
114
114
  getRunOwnerId,
115
115
  onAgentSettled(ctx) {
116
- if (activeRunContext !== ctx)
116
+ if (!activeRunOwnerId || getRunOwnerId(ctx) !== activeRunOwnerId)
117
117
  return;
118
118
  if (!runUiRuntime.flushCompletionBatch(ctx))
119
119
  automaticReview.schedule();
@@ -13,6 +13,12 @@ export interface RunObservation {
13
13
  activeSubagents?: number;
14
14
  completed?: number;
15
15
  descendantSubagents?: number;
16
+ deliveryOwnerId?: string;
17
+ deliveryParent?: {
18
+ run: string;
19
+ run_instance_id: string;
20
+ state_dir: string;
21
+ };
16
22
  failures?: number;
17
23
  ownerId?: string;
18
24
  artifacts?: Record<string, string>;
@@ -69,6 +75,8 @@ export declare function readRunUiSnapshot(state: RunUiObservationState, ownerId:
69
75
  includeAttention?: boolean;
70
76
  stateRoot?: string;
71
77
  }): RunUiSnapshot;
78
+ /** Observe every Run generation routed to one root coordinator. */
79
+ export declare function readRunDeliverySnapshot(state: RunUiObservationState, deliveryOwnerId: string, stateRoot?: string): RunUiSnapshot;
72
80
  export declare function pruneRunUiObservationState(state: RunUiObservationState, snapshot: Pick<RunUiSnapshot, "summary" | "transitions">): void;
73
81
  export declare function deliverRunAttentionNotifications(events: RunAttentionEvent[], sink: RunUiNotificationSink): void;
74
82
  export interface RunRetirementCandidate {
@@ -124,6 +132,11 @@ export interface RunRetirementExecutorOptions {
124
132
  sendStop: (candidate: RunRetirementCandidate) => Promise<unknown>;
125
133
  }
126
134
  export interface RunTransition {
135
+ deliveryParent?: {
136
+ run: string;
137
+ run_instance_id: string;
138
+ state_dir: string;
139
+ };
127
140
  from: RunObservedStatus;
128
141
  run: string;
129
142
  runInstanceId?: string;
@@ -162,7 +175,7 @@ export interface RunAttentionEvent {
162
175
  ts: string;
163
176
  }
164
177
  export type RunTransitionNotificationType = "info" | "warning" | "error";
165
- export declare function summarizeRuns(stateRoot?: string, ownerId?: string): RunSummary;
178
+ export declare function summarizeRuns(stateRoot?: string, ownerId?: string, deliveryOwnerId?: string): RunSummary;
166
179
  export declare function countRunningSubagentsByRun(stateRoot?: string, ownerId?: string): Map<string, number>;
167
180
  export declare function countRunningSubagents(stateRoot?: string, ownerId?: string): number;
168
181
  export declare function renderSubagentStatus(count: number, frame?: number): string | undefined;
@@ -180,6 +193,6 @@ export declare function shouldSendRunAttentionFollowUp(event: RunAttentionEvent)
180
193
  export declare function formatRunAttentionMessage(event: RunAttentionEvent): string;
181
194
  export declare function getRunTransitionNotificationType(transition: RunTransition): RunTransitionNotificationType;
182
195
  export declare function shouldNotifyRunTransition(transition: RunTransition): boolean;
183
- /** Build exact immutable generation members for owner-journal admission. */
184
- export declare function collectRunCompletionBatchMembers(transitions: RunTransition[]): RunCompletionBatchMember[];
196
+ /** Build exact immutable members only after each containing root Run is terminal. */
197
+ export declare function collectRunCompletionBatchMembers(transitions: RunTransition[], deliveryRuns?: RunObservation[]): RunCompletionBatchMember[];
185
198
  export declare function formatRunTransitionMessage(transition: RunTransition): string;
@@ -32,6 +32,16 @@ export function readRunUiSnapshot(state, ownerId, options = {}) {
32
32
  transitions: detectRunTransitions(state.observed, summary),
33
33
  };
34
34
  }
35
+ /** Observe every Run generation routed to one root coordinator. */
36
+ export function readRunDeliverySnapshot(state, deliveryOwnerId, stateRoot) {
37
+ const summary = summarizeRuns(stateRoot, undefined, deliveryOwnerId);
38
+ return {
39
+ attentionEvents: [],
40
+ status: undefined,
41
+ summary,
42
+ transitions: detectRunTransitions(state.observed, summary),
43
+ };
44
+ }
35
45
  export function pruneRunUiObservationState(state, snapshot) {
36
46
  pruneRunObservationState(state.observed, state.legacyEventLines, snapshot.summary, snapshot.transitions.map((transition) => transition.stateDir ?? transition.run), state.attentionEventIds);
37
47
  }
@@ -348,6 +358,19 @@ function observeRun(stateDir) {
348
358
  failures: Array.isArray(progress.failures)
349
359
  ? progress.failures.length
350
360
  : undefined,
361
+ ...(typeof status.delivery_owner_id === "string"
362
+ ? { deliveryOwnerId: status.delivery_owner_id }
363
+ : {}),
364
+ ...(status.delivery_parent &&
365
+ typeof status.delivery_parent === "object" &&
366
+ !Array.isArray(status.delivery_parent) &&
367
+ typeof status.delivery_parent.run === "string" &&
368
+ typeof status.delivery_parent.run_instance_id === "string" &&
369
+ typeof status.delivery_parent.state_dir === "string"
370
+ ? {
371
+ deliveryParent: status.delivery_parent,
372
+ }
373
+ : {}),
351
374
  ...(typeof status.ownerId === "string"
352
375
  ? { ownerId: status.ownerId }
353
376
  : {}),
@@ -391,7 +414,7 @@ function observeRun(stateDir) {
391
414
  return undefined;
392
415
  }
393
416
  }
394
- export function summarizeRuns(stateRoot = Paths.getRunStateRoot(), ownerId) {
417
+ export function summarizeRuns(stateRoot = Paths.getRunStateRoot(), ownerId, deliveryOwnerId) {
395
418
  if (!existsSync(stateRoot)) {
396
419
  return {
397
420
  cancelled: 0,
@@ -409,7 +432,10 @@ export function summarizeRuns(stateRoot = Paths.getRunStateRoot(), ownerId) {
409
432
  scanRunStateDirs(stateRoot))
410
433
  .map((stateDir) => observeRun(stateDir))
411
434
  .filter((run) => Boolean(run))
412
- .filter((run) => ownerId === undefined || run.ownerId === ownerId)
435
+ .filter((run) => deliveryOwnerId !== undefined
436
+ ? run.deliveryOwnerId === deliveryOwnerId ||
437
+ (run.deliveryOwnerId === undefined && run.ownerId === deliveryOwnerId)
438
+ : ownerId === undefined || run.ownerId === ownerId)
413
439
  .sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""));
414
440
  const processSubagentsByRun = countRunningSubagentsByRun(stateRoot, ownerId);
415
441
  const runsWithDescendants = runs.map((run) => {
@@ -683,6 +709,7 @@ export function detectRunTransitions(previous, summary) {
683
709
  transitions.push({
684
710
  from: old ?? "running",
685
711
  run: run.run,
712
+ ...(run.deliveryParent ? { deliveryParent: run.deliveryParent } : {}),
686
713
  ...(run.stateDir ? { stateDir: run.stateDir } : {}),
687
714
  ...(run.artifacts ? { artifacts: run.artifacts } : {}),
688
715
  ...(run.launchCorrelation ? { launchCorrelation: run.launchCorrelation } : {}),
@@ -921,15 +948,56 @@ export function shouldNotifyRunTransition(transition) {
921
948
  transition.to === "killed" ||
922
949
  transition.to === "exited");
923
950
  }
924
- /** Build exact immutable generation members for owner-journal admission. */
925
- export function collectRunCompletionBatchMembers(transitions) {
926
- return transitions.flatMap((transition) => {
951
+ function deliveryGenerationIdentity(input) {
952
+ return input.stateDir && input.runInstanceId
953
+ ? `${input.stateDir}\0${input.runInstanceId}`
954
+ : undefined;
955
+ }
956
+ function deliveryTreePosition(input, runsByIdentity) {
957
+ const ownIdentity = deliveryGenerationIdentity(input);
958
+ if (!ownIdentity)
959
+ return undefined;
960
+ let parent = input.deliveryParent;
961
+ let depth = 0;
962
+ let rootIdentity = ownIdentity;
963
+ const visited = new Set([ownIdentity]);
964
+ while (parent) {
965
+ const identity = `${parent.state_dir}\0${parent.run_instance_id}`;
966
+ if (visited.has(identity))
967
+ return undefined;
968
+ visited.add(identity);
969
+ const ancestor = runsByIdentity.get(identity);
970
+ if (!ancestor || ancestor.notificationPolicy === "silent")
971
+ return undefined;
972
+ depth += 1;
973
+ rootIdentity = identity;
974
+ parent = ancestor.deliveryParent;
975
+ }
976
+ return { depth, rootIdentity };
977
+ }
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) => {
981
+ const identity = deliveryGenerationIdentity(run);
982
+ return identity ? [[identity, run]] : [];
983
+ }));
984
+ const incompleteRoots = new Set(deliveryRuns.flatMap((run) => {
985
+ if (TERMINAL.has(run.status))
986
+ return [];
987
+ const position = deliveryTreePosition(run, runsByIdentity);
988
+ return position ? [position.rootIdentity] : [];
989
+ }));
990
+ const admitted = transitions.flatMap((transition) => {
927
991
  if (!shouldNotifyRunTransition(transition) ||
928
992
  !transition.stateDir ||
929
993
  !transition.runInstanceId ||
930
994
  !transition.terminalAt ||
931
995
  Number.isNaN(Date.parse(transition.terminalAt)))
932
996
  return [];
997
+ const position = deliveryTreePosition(transition, runsByIdentity);
998
+ if (!position || incompleteRoots.has(position.rootIdentity))
999
+ return [];
1000
+ const { depth } = position;
933
1001
  const artifactEntries = Object.entries(transition.artifacts ?? {})
934
1002
  .filter((entry) => typeof entry[1] === "string" && Boolean(entry[1]))
935
1003
  .slice(0, 4);
@@ -939,6 +1007,20 @@ export function collectRunCompletionBatchMembers(transitions) {
939
1007
  ...(artifactEntries.length > 0
940
1008
  ? { artifacts: Object.fromEntries(artifactEntries) }
941
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
+ : {}),
942
1024
  run: transition.run,
943
1025
  run_instance_id: transition.runInstanceId,
944
1026
  state_dir: transition.stateDir,
@@ -947,11 +1029,14 @@ export function collectRunCompletionBatchMembers(transitions) {
947
1029
  ? `${rawSummary.slice(0, 999)}…`
948
1030
  : rawSummary,
949
1031
  terminal_at: transition.terminalAt,
1032
+ tree_depth: depth,
950
1033
  }];
951
- }).sort((left, right) => left.terminal_at.localeCompare(right.terminal_at) ||
1034
+ });
1035
+ return admitted.sort((left, right) => left.tree_depth - right.tree_depth ||
1036
+ left.terminal_at.localeCompare(right.terminal_at) ||
952
1037
  left.run.localeCompare(right.run) ||
953
1038
  left.run_instance_id.localeCompare(right.run_instance_id) ||
954
- left.state_dir.localeCompare(right.state_dir));
1039
+ left.state_dir.localeCompare(right.state_dir)).map(({ tree_depth: _depth, ...member }) => member);
955
1040
  }
956
1041
  const TERMINAL_FOLLOW_UP_ARTIFACT_LIMIT = 4;
957
1042
  const TERMINAL_FOLLOW_UP_IDENTIFIER_CHARS = 120;
@@ -46,5 +46,4 @@ export declare function inspectRunSteerSessionEvidence(ctx: ExtensionContext, in
46
46
  eventId: string;
47
47
  steerId: string;
48
48
  }): SessionEvidence.ActiveSessionEntryEvidence;
49
- export declare function inspectRunCompletionBatchSessionEvidence(ctx: ExtensionContext, batchId: string, content: string): SessionEvidence.ActiveSessionEntryEvidence;
50
49
  export declare function registerToolDefinitions(pi: ExtensionAPI, definitions: Iterable<unknown>): void;
@@ -22,7 +22,7 @@ export function sendRunCompletionBatch(pi, batchId, content) {
22
22
  pi.sendMessage({
23
23
  customType: RUN_COMPLETION_BATCH_CUSTOM_TYPE,
24
24
  content,
25
- display: false,
25
+ display: true,
26
26
  details: {
27
27
  pi_actors_delivery: {
28
28
  batch_id: batchId,
@@ -59,18 +59,21 @@ function completionBatchMessage(message) {
59
59
  typeof record.content !== "string")
60
60
  return undefined;
61
61
  const details = record.details;
62
- if (!details || typeof details !== "object" || Array.isArray(details))
63
- return undefined;
64
- const delivery = details.pi_actors_delivery;
65
- if (!delivery || typeof delivery !== "object" || Array.isArray(delivery))
66
- return undefined;
67
- const envelope = delivery;
68
- if (envelope.kind !== "completion_batch" ||
69
- typeof envelope.batch_id !== "string" ||
70
- !envelope.batch_id ||
71
- envelope.batch_id.length > 128)
62
+ const delivery = details && typeof details === "object" && !Array.isArray(details)
63
+ ? details.pi_actors_delivery
64
+ : undefined;
65
+ const envelope = delivery && typeof delivery === "object" && !Array.isArray(delivery)
66
+ ? delivery
67
+ : undefined;
68
+ const detailedId = envelope?.kind === "completion_batch" &&
69
+ typeof envelope.batch_id === "string"
70
+ ? envelope.batch_id
71
+ : undefined;
72
+ const contentId = /^Batch: `([^`]{1,128})`$/mu.exec(record.content)?.[1];
73
+ const batchId = detailedId ?? contentId;
74
+ if (!batchId || batchId.length > 128)
72
75
  return undefined;
73
- return { batchId: envelope.batch_id, content: record.content };
76
+ return { batchId, content: record.content };
74
77
  }
75
78
  /** Collapse exact retry duplicates and remove conflicting delivery envelopes. */
76
79
  export function dedupeRunCompletionBatchContext(messages) {
@@ -183,18 +186,6 @@ export function inspectRunSteerSessionEvidence(ctx, input) {
183
186
  },
184
187
  });
185
188
  }
186
- export function inspectRunCompletionBatchSessionEvidence(ctx, batchId, content) {
187
- return SessionEvidence.inspectBoundedActiveSessionEntries({
188
- getEntry: (id) => ctx.sessionManager.getEntry(id),
189
- leaf: ctx.sessionManager.getLeafEntry(),
190
- match: (entry) => {
191
- const batch = completionBatchMessage(entry);
192
- if (!batch || batch.batchId !== batchId)
193
- return undefined;
194
- return batch.content === content ? "present" : "conflict";
195
- },
196
- });
197
- }
198
189
  export function registerToolDefinitions(pi, definitions) {
199
190
  for (const definition of definitions)
200
191
  pi.registerTool(definition);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Run delivery lineage.
3
+ * Zones: inherited coordinator root, parent Run generation, delivery-tree filtering
4
+ * Owns durable completion ancestry; excludes delivery persistence and Pi scheduling.
5
+ */
6
+ export interface RunDeliveryParent {
7
+ run: string;
8
+ run_instance_id: string;
9
+ state_dir: string;
10
+ }
11
+ export interface RunDeliveryLineage {
12
+ delivery_owner_id: string;
13
+ delivery_parent?: RunDeliveryParent;
14
+ }
15
+ export declare function inheritedRunDeliveryLineage(ownerId: string | undefined, env?: NodeJS.ProcessEnv): RunDeliveryLineage | undefined;
16
+ export declare function runDeliveryChildEnv(lineage: RunDeliveryLineage, parent: RunDeliveryParent, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
17
+ export declare function getProcessDeliveryOwnerId(env?: NodeJS.ProcessEnv): string | undefined;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Run delivery lineage.
3
+ * Zones: inherited coordinator root, parent Run generation, delivery-tree filtering
4
+ * Owns durable completion ancestry; excludes delivery persistence and Pi scheduling.
5
+ */
6
+ const DELIVERY_OWNER_ENV = "PI_ACTORS_DELIVERY_OWNER_ID";
7
+ const PARENT_RUN_ENV = "PI_ACTORS_DELIVERY_PARENT_RUN";
8
+ const PARENT_INSTANCE_ENV = "PI_ACTORS_DELIVERY_PARENT_INSTANCE";
9
+ const PARENT_STATE_DIR_ENV = "PI_ACTORS_DELIVERY_PARENT_STATE_DIR";
10
+ function nonEmpty(value) {
11
+ const text = value?.trim();
12
+ return text || undefined;
13
+ }
14
+ export function inheritedRunDeliveryLineage(ownerId, env = process.env) {
15
+ const localOwner = nonEmpty(ownerId);
16
+ if (!localOwner)
17
+ return undefined;
18
+ const deliveryOwner = nonEmpty(env[DELIVERY_OWNER_ENV]) ?? localOwner;
19
+ const run = nonEmpty(env[PARENT_RUN_ENV]);
20
+ const runInstanceId = nonEmpty(env[PARENT_INSTANCE_ENV]);
21
+ const stateDir = nonEmpty(env[PARENT_STATE_DIR_ENV]);
22
+ const completeParent = run && runInstanceId && stateDir
23
+ ? { run, run_instance_id: runInstanceId, state_dir: stateDir }
24
+ : undefined;
25
+ if ((run || runInstanceId || stateDir) && !completeParent) {
26
+ throw new Error("Inherited Run delivery parent lineage is incomplete");
27
+ }
28
+ return {
29
+ delivery_owner_id: deliveryOwner,
30
+ ...(completeParent ? { delivery_parent: completeParent } : {}),
31
+ };
32
+ }
33
+ export function runDeliveryChildEnv(lineage, parent, env = process.env) {
34
+ return {
35
+ ...env,
36
+ [DELIVERY_OWNER_ENV]: lineage.delivery_owner_id,
37
+ [PARENT_RUN_ENV]: parent.run,
38
+ [PARENT_INSTANCE_ENV]: parent.run_instance_id,
39
+ [PARENT_STATE_DIR_ENV]: parent.state_dir,
40
+ };
41
+ }
42
+ export function getProcessDeliveryOwnerId(env = process.env) {
43
+ return nonEmpty(env[DELIVERY_OWNER_ENV]);
44
+ }
@@ -7,6 +7,10 @@ export type RunCompletionDeliveryStatus = "done" | "failed" | "killed" | "exited
7
7
  export type RunDeliveryPhase = "pending" | "queued" | "presented";
8
8
  export interface RunCompletionBatchMember {
9
9
  artifacts?: Record<string, string>;
10
+ parent_run?: string;
11
+ parent_run_instance_id?: string;
12
+ parent_state_dir?: string;
13
+ output?: string;
10
14
  run: string;
11
15
  run_instance_id: string;
12
16
  state_dir: string;