@timurproko/a1 0.2.7-dev.1276 → 0.2.7-dev.1285

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 (180) hide show
  1. package/README.md +5 -1
  2. package/dist/app/session-shell/session-shell-root.d.ts +4 -68
  3. package/dist/app/session-shell/session-shell.js +5 -11
  4. package/dist/app/session-shell/shell-host.js +2 -2
  5. package/dist/app/session-shell/shell-options.d.ts +65 -0
  6. package/dist/app/session-shell/shell-options.js +1 -0
  7. package/dist/app/session-shell/workflow-router.d.ts +3 -1
  8. package/dist/app/session-shell/workflow-router.js +12 -0
  9. package/dist/cli/dispatch.js +27 -1
  10. package/dist/cli/packages.js +18 -2
  11. package/dist/cli/session-context.js +6 -2
  12. package/dist/composition/owned-ui.js +20 -8
  13. package/dist/composition/session-naming.js +1 -1
  14. package/dist/composition/session-runtime-registry.d.ts +2 -0
  15. package/dist/composition/session-runtime-registry.js +49 -9
  16. package/dist/contracts/agent-engine/package-ports.d.ts +10 -14
  17. package/dist/contracts/agent-engine/package-ports.js +5 -9
  18. package/dist/contracts/owned-ui/session-workflows.d.ts +6 -0
  19. package/dist/contracts/presentation/index.d.ts +1 -0
  20. package/dist/contracts/presentation/progress-palette.generated.d.ts +1 -1
  21. package/dist/contracts/presentation/progress-palette.generated.js +1 -1
  22. package/dist/contracts/presentation/progress-spinner.d.ts +7 -0
  23. package/dist/contracts/presentation/progress-spinner.js +1 -0
  24. package/dist/features/launch/session-context.d.ts +8 -0
  25. package/dist/features/launch/session-context.js +19 -10
  26. package/dist/features/owned-ui/reference-screen-app.js +9 -5
  27. package/dist/features/owned-ui/session-fork-prompt.d.ts +2 -1
  28. package/dist/features/owned-ui/session-fork-prompt.js +11 -5
  29. package/dist/features/owned-ui/settings-app.d.ts +1 -1
  30. package/dist/features/owned-ui/settings-app.js +14 -7
  31. package/dist/features/owned-ui/settings-spinner-preview.d.ts +9 -0
  32. package/dist/features/owned-ui/settings-spinner-preview.js +29 -0
  33. package/dist/features/session-naming/context.d.ts +17 -0
  34. package/dist/features/session-naming/context.js +39 -0
  35. package/dist/features/session-naming/controller.d.ts +1 -5
  36. package/dist/features/session-naming/controller.js +7 -4
  37. package/dist/features/session-naming/generate.d.ts +2 -1
  38. package/dist/features/session-naming/generate.js +14 -10
  39. package/dist/features/session-naming/index.d.ts +1 -1
  40. package/dist/foundation/lifecycle/index.d.ts +3 -1
  41. package/dist/foundation/lifecycle/index.js +3 -1
  42. package/dist/foundation/lifecycle/session-claim-lock.d.ts +2 -2
  43. package/dist/foundation/lifecycle/session-claim-lock.js +36 -10
  44. package/dist/foundation/lifecycle/session-context-retry.d.ts +2 -0
  45. package/dist/foundation/lifecycle/session-context-retry.js +17 -0
  46. package/dist/foundation/lifecycle/session-repository-context.d.ts +7 -1
  47. package/dist/foundation/lifecycle/session-repository-context.js +105 -39
  48. package/dist/foundation/lifecycle/session-worktree-entry.d.ts +3 -0
  49. package/dist/foundation/lifecycle/session-worktree-entry.js +87 -0
  50. package/dist/foundation/release/automatic-update.js +2 -2
  51. package/dist/foundation/release/bootstrap.js +19 -3
  52. package/dist/foundation/release/release-gc.js +20 -5
  53. package/dist/foundation/release/release-state.d.ts +12 -3
  54. package/dist/foundation/release/release-state.js +55 -24
  55. package/dist/foundation/release/release-use.d.ts +10 -0
  56. package/dist/foundation/release/release-use.js +70 -0
  57. package/dist/foundation/release/update-activation.js +2 -2
  58. package/dist/foundation/release/update-recovery.js +2 -2
  59. package/dist/foundation/release/update.js +1 -1
  60. package/dist/foundation/release/warmup.js +8 -9
  61. package/dist/foundation/session-writer-lock/index.d.ts +2 -2
  62. package/dist/foundation/session-writer-lock/index.js +82 -12
  63. package/dist/foundation/startup/startup-descriptor.js +2 -2
  64. package/dist/foundation/terminal-cleanup/fatal-diagnostic.d.ts +2 -0
  65. package/dist/foundation/terminal-cleanup/fatal-diagnostic.js +59 -0
  66. package/dist/foundation/terminal-cleanup/fatal-exit.js +1 -29
  67. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  68. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  69. package/dist/integrations/pi/components/share-operation-dialog.d.ts +2 -1
  70. package/dist/integrations/pi/components/share-operation-dialog.js +13 -5
  71. package/dist/integrations/pi/components/shell-footer-status.d.ts +2 -1
  72. package/dist/integrations/pi/components/shell-footer-status.js +15 -8
  73. package/dist/integrations/pi/components/shell-session-info-reference.js +11 -8
  74. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  75. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
  76. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  77. package/dist/integrations/pi/components/upstream/components/auth-url.d.ts +1 -1
  78. package/dist/integrations/pi/components/upstream/components/auth-url.js +1 -1
  79. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  80. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  81. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  82. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  83. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  84. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  85. package/dist/integrations/pi/components/upstream/components/login-dialog.js +1 -1
  86. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  87. package/dist/integrations/pi/components/upstream/components/oauth-selector.d.ts +1 -1
  88. package/dist/integrations/pi/components/upstream/components/oauth-selector.js +1 -1
  89. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  90. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  91. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  92. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  93. package/dist/integrations/pi/components/upstream/components/session-footer.js +14 -8
  94. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  95. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  96. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  97. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  98. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  99. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  100. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  101. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  102. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  103. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  104. package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
  105. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  106. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  107. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  108. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  109. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
  110. package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
  111. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  112. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  113. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  114. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  115. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  116. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  117. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  118. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  119. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  120. package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
  121. package/dist/integrations/pi/engine/adapter.d.ts +3 -1
  122. package/dist/integrations/pi/engine/adapter.js +24 -6
  123. package/dist/integrations/pi/engine/changelog.js +5 -2
  124. package/dist/integrations/pi/engine/delivery-retention.d.ts +7 -0
  125. package/dist/integrations/pi/engine/delivery-retention.js +66 -0
  126. package/dist/integrations/pi/engine/event-delivery.d.ts +17 -1
  127. package/dist/integrations/pi/engine/event-delivery.js +25 -3
  128. package/dist/integrations/pi/engine/extension-ui-binding.d.ts +2 -1
  129. package/dist/integrations/pi/engine/extension-ui-binding.js +45 -0
  130. package/dist/integrations/pi/engine/host-ports.d.ts +2 -0
  131. package/dist/integrations/pi/engine/host.d.ts +3 -0
  132. package/dist/integrations/pi/engine/host.js +2 -0
  133. package/dist/integrations/pi/engine/package-integration.js +19 -4
  134. package/dist/integrations/pi/engine/pending-delivery.d.ts +4 -1
  135. package/dist/integrations/pi/engine/pending-delivery.js +51 -18
  136. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  137. package/dist/integrations/pi/engine/runtime-integration.d.ts +8 -0
  138. package/dist/integrations/pi/engine/runtime-integration.js +58 -9
  139. package/dist/integrations/pi/engine/session-cwd-recovery.d.ts +11 -0
  140. package/dist/integrations/pi/engine/session-cwd-recovery.js +18 -0
  141. package/dist/integrations/pi/engine/session-runtime.d.ts +3 -0
  142. package/dist/integrations/pi/engine/session-runtime.js +2 -0
  143. package/dist/integrations/pi/engine/session-selection.d.ts +3 -2
  144. package/dist/integrations/pi/engine/session-selection.js +25 -5
  145. package/dist/integrations/pi/engine/transcript-image-assets.d.ts +3 -0
  146. package/dist/integrations/pi/engine/transcript-image-assets.js +65 -14
  147. package/dist/integrations/pi/engine/view-state.d.ts +14 -0
  148. package/dist/integrations/pi/engine/view-state.js +17 -0
  149. package/dist/integrations/pi/engine/workflow-runner.d.ts +2 -0
  150. package/dist/integrations/pi/engine/workflow-runner.js +47 -26
  151. package/dist/integrations/pi/engine/worktree-tools.d.ts +4 -0
  152. package/dist/integrations/pi/engine/worktree-tools.js +26 -0
  153. package/dist/integrations/pi/session-presenters/components.d.ts +2 -1
  154. package/dist/integrations/pi/session-presenters/components.js +5 -3
  155. package/dist/integrations/pi/session-presenters/presenters.d.ts +3 -1
  156. package/dist/integrations/pi/session-presenters/presenters.js +1 -1
  157. package/dist/integrations/pi/startup-public.d.ts +1 -1
  158. package/dist/integrations/pi/startup-public.js +16 -20
  159. package/dist/integrations/pi/startup-public.manifest.json +29 -29
  160. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  161. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  162. package/dist/product-identity.json +1 -1
  163. package/dist/runtime-payload-inventory.json +5027 -5027
  164. package/dist/ui/components/index.d.ts +1 -1
  165. package/dist/ui/components/index.js +1 -1
  166. package/dist/ui/components/progress-status.d.ts +7 -0
  167. package/dist/ui/components/progress-status.js +21 -0
  168. package/dist/ui/components/value-menu.d.ts +1 -1
  169. package/dist/ui/components/value-menu.js +4 -2
  170. package/dist/ui/settings/declarations.d.ts +16 -4
  171. package/dist/ui/settings/declarations.js +13 -4
  172. package/docs/architecture/internal-naming.md +1 -1
  173. package/docs/architecture/project-structure.md +7 -11
  174. package/docs/features/session-exports.md +1 -1
  175. package/docs/features/session-naming.md +8 -6
  176. package/docs/features/worktrees.md +38 -0
  177. package/docs/local-worktree-cleanup.md +27 -4
  178. package/docs/openspec-archive-automation.md +30 -4
  179. package/docs/worktree-link-recovery.md +2 -2
  180. package/package.json +5 -5
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-tui 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
2
+ * Provenance: @earendil-works/pi-tui 1.5.2 (MIT), commit b4afdddf44e059c67ccf5097e5317d913521b470,
3
3
  * packages/tui/src/word-navigation.ts.
4
4
  * Modifications: Owned editor core or minimal editor-local helper subset; public imports, strict
5
5
  * types, typed persistent-history hooks, and semantic border state. Public terminal runtime/exports
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.2 (MIT), commit b4afdddf44e059c67ccf5097e5317d913521b470,
3
3
  * packages/coding-agent/src/modes/interactive/model-search.ts.
4
4
  * Modifications: Mechanical source-synchronized port preserving pinned model search terms for stateful
5
5
  * scoped-model filtering.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.2 (MIT), commit b4afdddf44e059c67ccf5097e5317d913521b470,
3
3
  * packages/coding-agent/src/modes/interactive/program-status-reporter.ts.
4
4
  * Modifications: Remap Pi session events to A1's neutral run events, inject the terminal reporter, use
5
5
  * A1's application identity, and clear dialog state when the owned session changes.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.2 (MIT), commit b4afdddf44e059c67ccf5097e5317d913521b470,
3
3
  * packages/coding-agent/src/modes/interactive/theme/theme.ts.
4
4
  * Modifications: Source-synchronized theme port: retain pinned theme schema, variable/color
5
5
  * resolution, built-in and custom loading, terminal detection, and layout defaults while constructing
@@ -15,7 +15,8 @@ export type { OwnedPiVisualExtensionSupport } from "./extension-ui-binding.js";
15
15
  export type { PiSessionPresentationSource } from "./ports.js";
16
16
  /** Explicit flush failure when required delivery was interrupted rather than completed. */
17
17
  export declare class EngineDeliveryError extends Error {
18
- constructor();
18
+ readonly deliveryFailure: import("./event-delivery.js").DeliveryFailure | null;
19
+ constructor(failure?: import("./event-delivery.js").DeliveryFailure | null);
19
20
  }
20
21
  export interface PiEngineAdapterOptions {
21
22
  readonly cwd?: string;
@@ -46,6 +47,7 @@ export interface PiEngineAdapterOptions {
46
47
  */
47
48
  readonly checkPackageUpdates?: PiEnginePackageUpdateProbe;
48
49
  readonly repositoryContextReader?: PiRepositoryContextReader;
50
+ readonly worktreeCwdReader?: import("./worktree-tools.js").PiWorktreeCwdReader;
49
51
  }
50
52
  /** Owns the pinned Pi session lifecycle and serves it to the owned shell as the neutral session backend. */
51
53
  export declare class PiEngineAdapter implements OwnedUiSessionBackend, OwnedUiPromptSuggestionGeneratorPort {
@@ -19,7 +19,12 @@ import { EngineViewState, projectSessionView } from "./view-state.js";
19
19
  import { createEnginePorts } from "./ports.js";
20
20
  /** Explicit flush failure when required delivery was interrupted rather than completed. */
21
21
  export class EngineDeliveryError extends Error {
22
- constructor() { super("Engine delivery did not complete"); this.name = "EngineDeliveryError"; }
22
+ deliveryFailure;
23
+ constructor(failure = null) {
24
+ super("Engine delivery did not complete");
25
+ this.name = "EngineDeliveryError";
26
+ this.deliveryFailure = failure;
27
+ }
23
28
  }
24
29
  /** Owns the pinned Pi session lifecycle and serves it to the owned shell as the neutral session backend. */
25
30
  export class PiEngineAdapter {
@@ -43,12 +48,22 @@ export class PiEngineAdapter {
43
48
  #delivery = new PiEventDelivery({
44
49
  sessionId: () => this.#sessionId,
45
50
  sessionGeneration: () => this.#engine.generation,
51
+ authoritativeTranscript: () => this.#projection.snapshot(),
52
+ retainedAssetBytes: excluding => this.#projection.assets.pendingBytesExcluding(excluding),
46
53
  transcriptBlock: id => this.#projection.block(id),
47
54
  retainEvent: event => this.#projection.assets.retainEvent(event),
48
55
  isPendingCommand: correlationId => this.#commands.isPending(correlationId),
49
56
  cancelForOverload: () => this.#cancelForOverload(),
50
57
  reconcileOverload: cancelled => this.#reconcileOverload(cancelled),
51
58
  listenerFailed: message => { this.#recordDiagnostic("warning", "event-listener", message, true); },
59
+ canResume: () => !this.#disposed && this.#runningCommands === 0,
60
+ resume: () => {
61
+ this.#admissionStopped = false;
62
+ this.#state.settleOverload(true, false);
63
+ this.#engine.resume();
64
+ this.#emitView();
65
+ },
66
+ incident: failure => { this.#engineHost.deliveryIncident?.(failure); },
52
67
  });
53
68
  #admissionStopped = false;
54
69
  #runningCommands = 0;
@@ -87,6 +102,7 @@ export class PiEngineAdapter {
87
102
  });
88
103
  #extensions = new PiExtensionUiBinding({
89
104
  session: () => this.#engine.session,
105
+ runtime: () => this.#engine.runtime,
90
106
  diagnostic: (severity, code, message, recoverable) => { this.#addDiagnostic(severity, code, message, recoverable); },
91
107
  emitView: () => { this.#emitView(); },
92
108
  });
@@ -113,6 +129,7 @@ export class PiEngineAdapter {
113
129
  signal: this.#engineHost.signal,
114
130
  markStartupPhase: phase => this.#engineHost.markStartupPhase(phase),
115
131
  ...(options.repositoryContextReader === undefined ? {} : { repositoryContextReader: options.repositoryContextReader }),
132
+ ...(options.worktreeCwdReader === undefined ? {} : { worktreeCwdReader: options.worktreeCwdReader }),
116
133
  host: this.#workflowHost,
117
134
  }, {
118
135
  disposed: () => this.#disposed,
@@ -148,7 +165,7 @@ export class PiEngineAdapter {
148
165
  admissionStopped: () => this.#delivery.overloaded || this.#admissionStopped || this.#disposed,
149
166
  pendingCommandCount: () => this.#commands.pendingCount,
150
167
  beginRunning: () => { this.#runningCommands++; },
151
- endRunning: () => { this.#runningCommands--; },
168
+ endRunning: () => { this.#runningCommands--; this.#delivery.recovery.resume(); },
152
169
  activeModel: () => this.#state.activeModel,
153
170
  setActiveModel: model => { this.#state.activeModel = model; },
154
171
  thinkingLevel: () => this.#state.thinkingLevel,
@@ -167,7 +184,7 @@ export class PiEngineAdapter {
167
184
  pendingWorkflowCount: () => this.#workflows.pendingCount,
168
185
  sessionGeneration: () => this.#engine.generation,
169
186
  beginRunning: () => { this.#runningCommands++; },
170
- endRunning: () => { this.#runningCommands--; },
187
+ endRunning: () => { this.#runningCommands--; this.#delivery.recovery.resume(); },
171
188
  perform: command => performEngineCommand(command, this.#commandContext()),
172
189
  emit: value => { this.#emitEvent(value); },
173
190
  emitView: () => { this.#emitView(); },
@@ -250,7 +267,7 @@ export class PiEngineAdapter {
250
267
  await this.#delivery.settle();
251
268
  if (failed || this.#delivery.failed || this.#disposed && this.#state.lifecycle !== "stopped") {
252
269
  this.#delivery.clearFailure();
253
- throw new EngineDeliveryError();
270
+ throw new EngineDeliveryError(this.#delivery.failure);
254
271
  }
255
272
  }
256
273
  async executeWorkflow(request) {
@@ -438,8 +455,8 @@ export class PiEngineAdapter {
438
455
  }
439
456
  // Concurrency: cancellation runs synchronously so the overload reservation already guards reentrant outcomes.
440
457
  #cancelForOverload() {
441
- this.#commands.cancelPending();
442
- this.#workflows.cancelPending();
458
+ this.#commands.cancelPending(["set-model", "set-thinking-level", "resize-surface"]);
459
+ this.#workflows.cancelForDelivery();
443
460
  const session = this.#engine.session;
444
461
  return Promise.resolve().then(() => session?.abort()).then(() => undefined);
445
462
  }
@@ -447,6 +464,7 @@ export class PiEngineAdapter {
447
464
  const canResume = cancelled === true && this.#runningCommands === 0;
448
465
  this.#admissionStopped = !canResume;
449
466
  this.#engine.suspend();
467
+ this.#delivery.recovery.defer(canResume ? undefined : this.#engine.generation);
450
468
  this.#events.abandonRun();
451
469
  this.#state.settleOverload(canResume, this.#disposed);
452
470
  // Invariant: overload publishes one authoritative view; settle tool phases before finalizing other live blocks.
@@ -20,14 +20,17 @@ export async function readPinnedCommandChangelog(sinceVersion, path = new URL(".
20
20
  }
21
21
  }
22
22
  function formatPinnedCommandChangelog(content, sinceVersion) {
23
- // Provenance: behavior follows Pi 0.84.2 utils/changelog.ts at 914cf1472e715297caa30db4b9535d534a9eb718.
23
+ // Provenance: behavior follows pinned Pi's utils/changelog.ts, including leading-version comparison.
24
24
  // Runtime reads only the owned data resource; maintenance tooling verifies its pinned provenance.
25
25
  let releases = content.split(/(?=^## )/m).flatMap((section) => {
26
26
  const version = /^##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/.exec(section);
27
27
  return version ? [{ version: version.slice(1).map(Number), markdown: section.trim() }] : [];
28
28
  });
29
29
  if (sinceVersion !== undefined) {
30
- const previous = sinceVersion.split(".").map(value => Number(value) || 0);
30
+ const matched = sinceVersion.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
31
+ if (matched === null)
32
+ return "";
33
+ const previous = matched.slice(1).map(Number);
31
34
  releases = releases.filter(release => {
32
35
  for (let index = 0; index < 3; index++) {
33
36
  const difference = release.version[index] - (previous[index] ?? 0);
@@ -0,0 +1,7 @@
1
+ /** Accounts shared immutable graphs once; only delivery-exclusive storage consumes backlog capacity. */
2
+ export declare class DeliveryRetention {
3
+ #private;
4
+ get bytes(): number;
5
+ baseline(value: unknown): void;
6
+ retain(value: unknown): () => void;
7
+ }
@@ -0,0 +1,66 @@
1
+ /** Accounts shared immutable graphs once; only delivery-exclusive storage consumes backlog capacity. */
2
+ export class DeliveryRetention {
3
+ #objects = new WeakMap();
4
+ #current;
5
+ #baseline;
6
+ #bytes = 0;
7
+ get bytes() { return this.#bytes; }
8
+ baseline(value) {
9
+ if (value === this.#current)
10
+ return;
11
+ const next = this.#allocation(value);
12
+ // Invariant: retain first so common blocks never become transient backlog during a revision change.
13
+ this.#change(next, "authoritative", 1);
14
+ if (this.#baseline)
15
+ this.#change(this.#baseline, "authoritative", -1);
16
+ this.#current = value;
17
+ this.#baseline = next;
18
+ }
19
+ retain(value) {
20
+ const allocation = this.#allocation(value);
21
+ this.#change(allocation, "pending", 1);
22
+ let released = false;
23
+ return () => {
24
+ if (released)
25
+ return;
26
+ released = true;
27
+ this.#change(allocation, "pending", -1);
28
+ };
29
+ }
30
+ #allocation(value) {
31
+ if (value === null || typeof value !== "object") {
32
+ return { bytes: scalarBytes(value), children: [], pending: 0, authoritative: 0 };
33
+ }
34
+ const cached = this.#objects.get(value);
35
+ if (cached)
36
+ return cached;
37
+ let bytes = 32;
38
+ const children = [];
39
+ for (const [key, item] of Object.entries(value)) {
40
+ bytes += 16 + key.length * 2;
41
+ if (item !== null && typeof item === "object")
42
+ children.push(this.#allocation(item));
43
+ else
44
+ bytes += scalarBytes(item);
45
+ }
46
+ const result = { bytes, children, pending: 0, authoritative: 0 };
47
+ this.#objects.set(value, result);
48
+ return result;
49
+ }
50
+ #change(allocation, owner, delta) {
51
+ const before = allocation[owner];
52
+ const charged = allocation.pending > 0 && allocation.authoritative === 0;
53
+ allocation[owner] += delta;
54
+ const charge = allocation.pending > 0 && allocation.authoritative === 0;
55
+ if (charged !== charge)
56
+ this.#bytes += charge ? allocation.bytes : -allocation.bytes;
57
+ // Invariant: a shared parent's second owner does not retain every descendant again.
58
+ if (before === 0 || allocation[owner] === 0) {
59
+ for (const child of allocation.children)
60
+ this.#change(child, owner, delta);
61
+ }
62
+ }
63
+ }
64
+ function scalarBytes(value) {
65
+ return typeof value === "string" ? 16 + value.length * 2 : 8;
66
+ }
@@ -1,4 +1,5 @@
1
1
  import type { OwnedUiEvent, OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
2
+ import { OverloadRecovery } from "./view-state.js";
2
3
  /** An event the adapter emits before delivery stamps it with the session id and sequence. */
3
4
  export type PiEmittedEvent = Omit<Extract<OwnedUiEvent, {
4
5
  type: "session-lifecycle";
@@ -32,9 +33,19 @@ interface ReservedCommandOutcome {
32
33
  type: "command-outcome";
33
34
  }>["diagnostic"];
34
35
  }
36
+ export interface DeliveryFailure {
37
+ readonly reason: "event-count" | "retained-bytes" | "listener";
38
+ readonly event: OwnedUiEvent["type"] | "snapshot";
39
+ readonly generation: number;
40
+ readonly pending: number;
41
+ readonly bytes: number;
42
+ }
35
43
  export interface PiEventDeliveryPorts {
36
44
  sessionId(): string;
37
45
  sessionGeneration(): number;
46
+ /** Immutable history already owned by the engine, not additional pending payload. */
47
+ authoritativeTranscript?(): readonly OwnedUiTranscriptBlock[];
48
+ retainedAssetBytes?(excluding?: OwnedUiEvent): number;
38
49
  /** The current block for a lazily materialized transcript event. */
39
50
  transcriptBlock(id: string): OwnedUiTranscriptBlock | undefined;
40
51
  /** Retain the image assets an event references until the returned release runs. */
@@ -47,6 +58,9 @@ export interface PiEventDeliveryPorts {
47
58
  reconcileOverload(cancelled: boolean): Promise<void>;
48
59
  /** A listener threw; the adapter records the warning. */
49
60
  listenerFailed(message: string): void;
61
+ canResume?(): boolean;
62
+ resume?(): void;
63
+ incident?(failure: DeliveryFailure): void;
50
64
  }
51
65
  /**
52
66
  * Bounded delivery of owned UI events: stamps sequence numbers, keeps subscribers, queues events
@@ -56,6 +70,8 @@ export interface PiEventDeliveryPorts {
56
70
  */
57
71
  export declare class PiEventDelivery {
58
72
  #private;
73
+ readonly recovery: OverloadRecovery;
74
+ get failure(): DeliveryFailure | null;
59
75
  constructor(ports: PiEventDeliveryPorts);
60
76
  /** The sequence number of the most recently stamped event. */
61
77
  get sequence(): number;
@@ -90,7 +106,7 @@ export declare class PiEventDelivery {
90
106
  /** Drop queued events from earlier session generations. */
91
107
  discardObsolete(generation: number): void;
92
108
  /** Enter the bounded overload transition; a no-op while one is already in progress. */
93
- beginOverload(): void;
109
+ beginOverload(event?: DeliveryFailure["event"]): void;
94
110
  /** Deliver out of band, bypassing the queue; used for reconciliation after an overload. */
95
111
  deliverNow(event: OwnedUiEvent): void;
96
112
  /** The command outcomes reserved during an overload, in arrival order; clears the reservation. */
@@ -1,3 +1,4 @@
1
+ import { OverloadRecovery } from "./view-state.js";
1
2
  import { PendingEngineDelivery } from "./pending-delivery.js";
2
3
  /**
3
4
  * Engine events delivered before the queue hands the event loop a turn. Small enough
@@ -26,9 +27,15 @@ export class PiEventDelivery {
26
27
  #overloads = 0;
27
28
  #deliveryFailed = false;
28
29
  #invalidatedEvents = 0;
30
+ #failure = null;
31
+ #reported = 0;
32
+ recovery;
33
+ get failure() { return this.#failure; }
29
34
  constructor(ports) {
30
35
  this.#ports = ports;
31
- this.#queue = new PendingEngineDelivery(event => ports.retainEvent(event));
36
+ this.recovery = new OverloadRecovery({ generation: () => ports.sessionGeneration(), busy: () => this.overloaded,
37
+ canResume: () => ports.canResume?.() ?? false, resume: () => ports.resume?.() });
38
+ this.#queue = new PendingEngineDelivery(event => ports.retainEvent(event), ports.authoritativeTranscript, ports.retainedAssetBytes);
32
39
  }
33
40
  /** The sequence number of the most recently stamped event. */
34
41
  get sequence() {
@@ -77,7 +84,7 @@ export class PiEventDelivery {
77
84
  }
78
85
  if (!this.#queue.push(event, this.#ports.sessionGeneration(), event.type === "transcript-block"
79
86
  ? this.#blockReconciliation(event.block.id, event.sequence) : undefined)) {
80
- this.beginOverload();
87
+ this.beginOverload(event.type);
81
88
  if (event.type === "command-outcome" && event.outcome !== "accepted" && this.#ports.isPendingCommand(event.correlationId)
82
89
  && !this.#reservedOutcomes.has(event.correlationId)) {
83
90
  this.#reservedOutcomes.set(event.correlationId, { outcome: event.outcome, diagnostic: event.diagnostic });
@@ -98,19 +105,22 @@ export class PiEventDelivery {
98
105
  this.#invalidatedEvents += this.#queue.discardObsolete(generation);
99
106
  }
100
107
  /** Enter the bounded overload transition; a no-op while one is already in progress. */
101
- beginOverload() {
108
+ beginOverload(event = "snapshot") {
102
109
  if (this.#overload !== undefined)
103
110
  return;
111
+ this.#recordFailure(this.#queue.rejection ?? "retained-bytes", event);
104
112
  this.#overloads = Math.min(Number.MAX_SAFE_INTEGER, this.#overloads + 1);
105
113
  this.#deliveryFailed = true;
106
114
  // Concurrency: reserve before cancellation; outcomes produced reentrantly must not enter the saturated queue.
107
115
  this.#overload = Promise.resolve(false);
116
+ this.recovery.begin();
108
117
  const cancellation = this.#ports.cancelForOverload();
109
118
  this.#overload = new Promise(resolve => {
110
119
  const timer = setTimeout(() => resolve(false), OVERLOAD_CANCELLATION_TIMEOUT_MS);
111
120
  cancellation.then(() => {
112
121
  clearTimeout(timer);
113
122
  resolve(true);
123
+ this.recovery.settled();
114
124
  }, () => { clearTimeout(timer); resolve(false); });
115
125
  });
116
126
  }
@@ -124,10 +134,21 @@ export class PiEventDelivery {
124
134
  }
125
135
  catch (error) {
126
136
  this.#deliveryFailed = true;
137
+ this.#recordFailure("listener", event.type);
127
138
  this.#ports.listenerFailed(error instanceof Error ? error.message : String(error));
128
139
  }
129
140
  }
130
141
  }
142
+ #recordFailure(reason, event) {
143
+ const { pending, bytes } = this.#queue.diagnostics();
144
+ this.#failure = { reason, event, generation: this.#ports.sessionGeneration(), pending, bytes };
145
+ if (this.#reported++ < 10) {
146
+ try {
147
+ this.#ports.incident?.(this.#failure);
148
+ }
149
+ catch { /* Invariant: diagnostics never own recovery. */ }
150
+ }
151
+ }
131
152
  /** The command outcomes reserved during an overload, in arrival order; clears the reservation. */
132
153
  takeReservedOutcomes() {
133
154
  const reserved = [...this.#reservedOutcomes];
@@ -172,6 +193,7 @@ export class PiEventDelivery {
172
193
  // Invariant: an outcome reserved while reconciliation replayed the earlier ones is dropped with the overload.
173
194
  this.#reservedOutcomes.clear();
174
195
  this.#overload = undefined;
196
+ this.recovery.resume();
175
197
  }
176
198
  }
177
199
  finally {
@@ -1,8 +1,9 @@
1
- import type { AgentSession } from "../startup-public.js";
1
+ import type { AgentSession, AgentSessionRuntime } from "../startup-public.js";
2
2
  import { type OwnedUiDiagnostics, type OwnedUiVisualExtensionSupport } from "../../../contracts/owned-ui/index.js";
3
3
  export type OwnedPiVisualExtensionSupport = OwnedUiVisualExtensionSupport;
4
4
  interface PiExtensionUiBindingPorts {
5
5
  session(): AgentSession | undefined;
6
+ runtime(): AgentSessionRuntime | undefined;
6
7
  diagnostic(severity: OwnedUiDiagnostics["severity"], code: string, message: string, recoverable: boolean): void;
7
8
  emitView(): void;
8
9
  }
@@ -1,5 +1,7 @@
1
1
  import { PRODUCT_IDENTITY } from "../../../product-identity.js";
2
2
  import { isRecord } from "./message-values.js";
3
+ import { missingSessionCwdRecovery } from "./session-cwd-recovery.js";
4
+ import { switchToRecoveredFork } from "./runtime-integration.js";
3
5
  import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiExtensionUiPort, } from "../../../contracts/owned-ui/index.js";
4
6
  /**
5
7
  * The owned extension UI bridge's attachment to pinned Pi sessions: the bridge the shell hands
@@ -54,8 +56,51 @@ export class PiExtensionUiBinding {
54
56
  return;
55
57
  }
56
58
  try {
59
+ const runtime = this.#ports.runtime();
57
60
  await session.bindExtensions({
58
61
  uiContext: ui,
62
+ ...(runtime === undefined ? {} : {
63
+ commandContextActions: {
64
+ waitForIdle: () => session.waitForIdle(),
65
+ newSession: options => runtime.newSession(options),
66
+ fork: (entryId, options) => runtime.fork(entryId, options),
67
+ navigateTree: async (targetId, options) => {
68
+ const result = await session.navigateTree(targetId, options);
69
+ return { cancelled: result.cancelled === true };
70
+ },
71
+ reload: async () => { await session.reload(); await this.rebind(); },
72
+ switchSession: async (path, options) => {
73
+ try {
74
+ const result = await runtime.switchSession(path, options);
75
+ if (!result.cancelled)
76
+ ui.notify("Resumed session", "info");
77
+ return result;
78
+ }
79
+ catch (error) {
80
+ const recovery = missingSessionCwdRecovery(error);
81
+ if (recovery === undefined)
82
+ throw error;
83
+ const selected = await ui.select(recovery.message, recovery.options.map(option => option.label));
84
+ const choice = recovery.options.find(option => option.label === selected)?.id;
85
+ const cwd = recovery.cwdRecovery.fallbackCwd;
86
+ if (choice === "fork") {
87
+ const result = await switchToRecoveredFork(runtime, path, cwd, options);
88
+ if (!result.cancelled)
89
+ ui.notify(result.message, "info");
90
+ return { cancelled: result.cancelled };
91
+ }
92
+ if (choice === "continue") {
93
+ const result = await runtime.switchSession(path, { ...options, cwdOverride: cwd });
94
+ if (!result.cancelled)
95
+ ui.notify(`Resumed session in ${cwd}`, "info");
96
+ return result;
97
+ }
98
+ ui.notify("Resume cancelled", "info");
99
+ return { cancelled: true };
100
+ }
101
+ },
102
+ },
103
+ }),
59
104
  mode: "tui",
60
105
  shutdownHandler: () => this.#extensionShutdown?.(),
61
106
  onError: error => {
@@ -1,3 +1,4 @@
1
+ import type { DeliveryFailure } from "./event-delivery.js";
1
2
  import { type StartupPhase } from "../../../foundation/startup/startup-runtime.js";
2
3
  /**
3
4
  * What one engine session may ask of the process-level host that created it. The host owns
@@ -6,6 +7,7 @@ import { type StartupPhase } from "../../../foundation/startup/startup-runtime.j
6
7
  export interface PiEngineHostSessionPorts {
7
8
  /** Aborted when the host is disposed; host-level timers and probes stop with it. */
8
9
  readonly signal: AbortSignal;
10
+ deliveryIncident?(failure: DeliveryFailure): void;
9
11
  /** Record a startup trace phase; the host records each phase once per process. */
10
12
  markStartupPhase(phase: StartupPhase): Promise<void>;
11
13
  /** A session's runtime loaded the profile's HTTP idle timeout; the host installs the dispatcher once. */
@@ -1,3 +1,4 @@
1
+ import type { DeliveryFailure } from "./event-delivery.js";
1
2
  import { PiEngineAdapter } from "./adapter.js";
2
3
  import { type StartupPhase } from "../../../foundation/startup/startup-runtime.js";
3
4
  import type { PiEnginePackageUpdateProbe, PiEngineRuntimeFactory, PiRepositoryContextReader } from "./session-runtime.js";
@@ -21,6 +22,7 @@ export interface PiEngineHostTheme {
21
22
  }
22
23
  export interface PiEngineHostOptions {
23
24
  readonly productMode: "bare" | "comparison";
25
+ readonly deliveryIncident?: (failure: DeliveryFailure) => void;
24
26
  readonly theme: PiEngineHostTheme;
25
27
  /** Themes installed on this machine, listed by the component adapter that owns the theme unit. */
26
28
  readonly availableThemes?: () => readonly string[];
@@ -41,6 +43,7 @@ export interface PiEngineSessionRequest extends OwnedUiSessionRequest {
41
43
  readonly sessionSelection?: PiSessionSelection;
42
44
  readonly sessionForkPrompt?: PiSessionForkPrompt;
43
45
  readonly repositoryContextReader?: PiRepositoryContextReader;
46
+ readonly worktreeCwdReader?: import("./worktree-tools.js").PiWorktreeCwdReader;
44
47
  }
45
48
  /** The dispatcher policy in force for the process and the session that put it there. */
46
49
  export interface PiEngineHttpPolicy {
@@ -26,6 +26,7 @@ class ProcessPiEngineHost {
26
26
  this.#markStartupPhase = options.markStartupPhase ?? (phase => markStartupPhase(process.env, phase));
27
27
  this.#sessionPorts = {
28
28
  signal: this.#abort.signal,
29
+ deliveryIncident: failure => options.deliveryIncident?.(failure),
29
30
  markStartupPhase: phase => this.#tracePhase(phase),
30
31
  httpPolicyLoaded: (timeoutMs, sessionId) => { this.#applyHttpPolicy(timeoutMs, sessionId, "startup"); },
31
32
  httpPolicyChanged: (timeoutMs, sessionId) => { this.#applyHttpPolicy(timeoutMs, sessionId, "setting"); },
@@ -68,6 +69,7 @@ class ProcessPiEngineHost {
68
69
  ...(request.sessionSelection === undefined ? {} : { sessionSelection: request.sessionSelection }),
69
70
  ...(request.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: request.sessionForkPrompt }),
70
71
  ...(request.repositoryContextReader === undefined ? {} : { repositoryContextReader: request.repositoryContextReader }),
72
+ ...(request.worktreeCwdReader === undefined ? {} : { worktreeCwdReader: request.worktreeCwdReader }),
71
73
  });
72
74
  this.#sessions.set(sessionId, adapter);
73
75
  try {
@@ -17,6 +17,8 @@ export function createPiPackagesPort(input) {
17
17
  const { profileRoot, cwd } = input;
18
18
  // Compatibility: model-only refresh bypasses package settings and their diagnostics, as Pi does.
19
19
  let packageManager;
20
+ let updateChanges = [];
21
+ let updateWarningCount = 0;
20
22
  const manager = () => {
21
23
  if (packageManager)
22
24
  return packageManager;
@@ -29,9 +31,16 @@ export function createPiPackagesPort(input) {
29
31
  }
30
32
  packageManager = new DefaultPackageManager({ cwd, agentDir: profileRoot, settingsManager });
31
33
  packageManager.setProgressCallback(event => {
32
- if (event.type !== "start")
33
- return;
34
- input.onProgress?.({ operation: operationFor(event.action), message: String(event.message) });
34
+ if (event.type === "result" && event.action === "update" && event.message) {
35
+ updateChanges.push(event.message);
36
+ }
37
+ else if (event.type === "start") {
38
+ input.onProgress?.({ operation: operationFor(event.action), message: String(event.message) });
39
+ }
40
+ else if (event.type === "progress" && event.message) {
41
+ updateWarningCount++;
42
+ input.onProgress?.({ operation: operationFor(event.action), message: event.message, severity: "warning" });
43
+ }
35
44
  });
36
45
  return packageManager;
37
46
  };
@@ -65,8 +74,14 @@ export function createPiPackagesPort(input) {
65
74
  async update(source) {
66
75
  return await attempt("update", source ?? null, async () => {
67
76
  // Compatibility: Pi owns source identity and its contextual no-match suggestions.
77
+ updateChanges = [];
78
+ updateWarningCount = 0;
68
79
  await manager().update(source);
69
- return agentPackageOutcome("update", "completed", null, source ?? null, configured());
80
+ // Compatibility: Pi's offline predicate is not part of its package-root public API.
81
+ const offline = ["1", "true", "yes"].includes(process.env.PI_OFFLINE?.toLowerCase() ?? "");
82
+ return agentPackageOutcome("update", "completed", null, source ?? null, configured(), {
83
+ offline, warningCount: updateWarningCount, changes: updateChanges,
84
+ });
70
85
  });
71
86
  },
72
87
  async refreshModels() {
@@ -2,8 +2,11 @@ import type { OwnedUiEvent } from "../../../contracts/owned-ui/index.js";
2
2
  /** Intrusive FIFO + segment index: replacement unlinks in O(1), never changes source ordering. */
3
3
  export declare class PendingEngineDelivery {
4
4
  #private;
5
+ rejection: "event-count" | "retained-bytes" | null;
5
6
  private readonly retain;
6
- constructor(retain?: (event: OwnedUiEvent) => () => void);
7
+ private readonly current;
8
+ private readonly externalBytes;
9
+ constructor(retain?: (event: OwnedUiEvent) => () => void, current?: () => unknown, externalBytes?: (excluding?: OwnedUiEvent) => number);
7
10
  get size(): number;
8
11
  diagnostics(): {
9
12
  pending: number;