@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
package/README.md CHANGED
@@ -73,7 +73,11 @@ a1 --session <id>
73
73
 
74
74
  When a session exits, A1 prints the `a1 --session <id>` command that resumes it; a custom session store is included in the printed command. Closing the terminal ends the agent, as it does for Pi.
75
75
 
76
- Bare A1 automatically names unnamed sessions from the first prompt and regenerates automatic names on the next prompt after a worktree-context change. Manual `/name` renames are preserved. Names appear after footer stats, with a secondary-accent reveal for automatic changes. Agent settings offers **Session name format** with Kebab case (default), Sentence case, and Title Case for future generated names, or **Automatic session name** to disable naming; see [session names](docs/features/session-naming.md) for details.
76
+ If a saved session's folder was moved or deleted, recovery offers **Continue**, **Fork**, or **Cancel**. Continue uses the current folder for this run and asks again on the next resume. Fork keeps the original and creates a new session ID in the current folder, so later resumes no longer ask. This also applies to session import and extension-triggered switching.
77
+
78
+ Bare A1 automatically names unnamed sessions from the current task and relevant discussion, using up to three descriptive words and 32 characters, and regenerates on the next prompt after a worktree-context change. Manual `/name` renames are preserved. Names appear after footer stats, with a secondary-accent reveal for automatic changes; narrow footers reduce stats before shortening the name. Agent settings offers **Session name format** with Sentence case (default, first choice), Kebab case, and Title Case for future generated names, or **Automatic session name** to disable naming. Valid saved formats are preserved; see [session names](docs/features/session-naming.md) for details.
79
+
80
+ Inside an A1 agent session, `a1 worktree enter <branch>` creates or resumes a task checkout and attaches it. `a1 worktree attach <path>` attaches an existing checkout; `a1 worktree detach` releases it without deleting work. A1 handles coordination and ordinary recovery internally; successful attachment needs no second inventory check. See [worktree commands](docs/features/worktrees.md).
77
81
 
78
82
  Show all commands:
79
83
 
@@ -1,8 +1,7 @@
1
1
  import type { OwnedUiShellComponents, OwnedUiShellHotkeysPresentation, OwnedUiShellClipboardContent, OwnedUiShellComponentPort, OwnedUiShellEditorPort, OwnedUiShellHeaderOptions, OwnedUiShellHeaderPort, OwnedUiShellHistoryEditor, OwnedUiShellImageAssetResolver, OwnedUiShellKeybindingsHost, OwnedUiShellLoadedResourcesPort, OwnedUiShellViewComponentPort } from "../../contracts/owned-ui/index.js";
2
- import type { TerminalComponentPort, TerminalInputCoordinationScheduler, TerminalInputDiagnosticsEvent, TerminalInputSurfaceKind, TerminalLayoutNode, TerminalPort } from "../../contracts/presentation/index.js";
2
+ import type { TerminalComponentPort, TerminalInputSurfaceKind, TerminalLayoutNode } from "../../contracts/presentation/index.js";
3
3
  import type { ShellPresentationPorts, ThemeSource } from "../../contracts/presentation/index.js";
4
- import type { OwnedUiBackgroundSettingsPort, OwnedUiPromptSuggestionGeneratorPort, OwnedUiSessionBackend, OwnedUiSessionPresenters, OwnedUiSessionViewModel, OwnedUiTranscriptRendererPort, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, OwnedUiQuitOutroSettingsPort, SuggestionDecision, SuggestionDiagnosticObserver } from "../../contracts/owned-ui/index.js";
5
- import type { UiRouteHost, UiRouteInput } from "../../ui/apps/contracts.js";
4
+ import type { OwnedUiPromptSuggestionGeneratorPort, OwnedUiSessionPresenters, OwnedUiSessionViewModel, OwnedUiTranscriptRendererPort, OwnedUiViewportSettings, SuggestionDecision, SuggestionDiagnosticObserver } from "../../contracts/owned-ui/index.js";
6
5
  import type { TranscriptViewportFrame, TranscriptViewportFrameDescriptor } from "../../ui/components/transcript-viewport.js";
7
6
  import { PromptChipStore, type PreparedPrompt } from "./prompt-chips.js";
8
7
  import type { PastePreparationClientOptions } from "./paste-preparation-client.js";
@@ -15,70 +14,8 @@ import type { PasteEvent, PasteSource } from "./paste-protocol.js";
15
14
  import { DockChrome } from "./dock-chrome.js";
16
15
  import { WorkflowOutput } from "./workflow-output.js";
17
16
  import { TranscriptDocument } from "./transcript-document.js";
18
- import type { StreamPresentationScheduler } from "./stream-presentation-coalescer.js";
19
- type OwnedUiTerminalPort = TerminalPort;
20
- type OwnedUiStartupOptions = OwnedUiShellHeaderOptions;
21
- /** The engine session the shell presents and the composition's decisions about it. */
22
- export interface OwnedUiShellEngineOptions {
23
- readonly backend: OwnedUiSessionBackend;
24
- readonly cwd: string;
25
- /**
26
- * Declared A1-owned routes. A route this host claims resolves to its app;
27
- * every other route continues to the pinned workflow table unchanged.
28
- */
29
- readonly routeHost?: UiRouteHost;
30
- /** One product-owned route presented after the first usable frame and any startup modal. */
31
- readonly startupRoute?: {
32
- readonly route: string;
33
- readonly input?: UiRouteInput;
34
- readonly onClosed?: () => void | Promise<void>;
35
- };
36
- /** Bare A1 selects the owned bounded viewport; comparison profiles stay pinned. */
37
- readonly sessionLayout?: "pinned" | "custom-viewport";
38
- }
39
- /** Terminal, startup, viewport, and presentation-cadence seams; production leaves most at their defaults. */
40
- export interface OwnedUiShellPresentationOptions {
41
- readonly terminal?: OwnedUiTerminalPort;
42
- readonly startup?: OwnedUiStartupOptions;
43
- /** Live profile-local settings, supplied only to the bare-A1 composition. */
44
- readonly viewportSettings?: OwnedUiViewportSettingsPort;
45
- /** Live fullscreen canvas choice, supplied only to the bare-A1 composition. */
46
- readonly backgroundSettings?: OwnedUiBackgroundSettingsPort;
47
- /** Deterministic scheduling seam for presentation-cadence tests. */
48
- readonly stream?: {
49
- readonly intervalMs?: number;
50
- readonly scheduler?: StreamPresentationScheduler;
51
- };
52
- /**
53
- * Minimum time the reload box stays visible so a fast reload still reads as one.
54
- * Production uses the default hold; tests inject `now`/`sleep` for determinism.
55
- */
56
- readonly reload?: {
57
- readonly minVisibleMs?: number;
58
- readonly now?: () => number;
59
- readonly sleep?: (ms: number) => Promise<void>;
60
- };
61
- /**
62
- * Quit outro settings and seams, supplied only to the bare-A1 composition.
63
- * `interactive` states whether the terminal can show the animation; production
64
- * passes stdout's TTY state, tests opt in explicitly.
65
- */
66
- readonly quitOutro?: OwnedUiQuitOutroSettingsPort & {
67
- readonly interactive: boolean;
68
- readonly now?: () => number;
69
- readonly sleep?: (ms: number) => Promise<void>;
70
- readonly seed?: number;
71
- };
72
- readonly input?: {
73
- readonly scheduler?: TerminalInputCoordinationScheduler;
74
- readonly onEvent?: (event: TerminalInputDiagnosticsEvent) => void;
75
- readonly now?: () => number;
76
- /** Evidence-only baseline seam; production leaves coordination enabled. */
77
- readonly coordination?: boolean;
78
- /** Evidence-only baseline seam; production leaves dock reuse enabled. */
79
- readonly viewportReuse?: boolean;
80
- };
81
- }
17
+ import type { OwnedUiShellEngineOptions, OwnedUiShellPresentationOptions } from "./shell-options.js";
18
+ export type { OwnedUiShellEngineOptions, OwnedUiShellPresentationOptions } from "./shell-options.js";
82
19
  /** Persistent prompt history: its store, its bound, the editor that recalls it, and the image sidecar. */
83
20
  export interface OwnedUiShellHistoryOptions {
84
21
  readonly store: import("../../contracts/owned-ui/index.js").PromptHistoryPort;
@@ -286,4 +223,3 @@ export type PinnedLayoutParts = Readonly<Record<PinnedLayoutPart, TerminalCompon
286
223
  * so its single `transcript` scroll view is shared by whichever session the parts currently resolve to.
287
224
  */
288
225
  export declare function pinnedLayoutRoot(parts: PinnedLayoutParts, theme: ThemeSource): TerminalLayoutNode;
289
- export {};
@@ -7,7 +7,7 @@ import { LoginInteraction } from "./login-interaction.js";
7
7
  import { PromptSubmission } from "./prompt-submission.js";
8
8
  import { SessionConcerns } from "./session-concern.js";
9
9
  import { composeExitText, SessionStopLatch } from "./session-lifecycle.js";
10
- import { openWorkflowSelector } from "./workflow-router.js";
10
+ import { openWorkflowSelector, selectSessionCwdRecovery } from "./workflow-router.js";
11
11
  import { SessionSelectors } from "./session-selectors.js";
12
12
  import { bindSessionSettings, bindShellSettings } from "./settings-bindings.js";
13
13
  import { promptSuggestionSurface, suggestionPresentationBlockReason, } from "./suggestion-surface.js";
@@ -606,18 +606,12 @@ export class OwnedUiSessionPresenter {
606
606
  this.selectors.showModelSelector((result.detail ?? request.argument.trim()) || undefined);
607
607
  return { outcome: "completed", diagnostic: null };
608
608
  }
609
- if (result.outcome === "requires-confirmation" && request.command === "resume") {
610
- const confirmed = await this.#extensionBridge.context.confirm("Session cwd not found", result.message);
611
- return this.runWorkflow({ ...request, confirmed: confirmed === true });
609
+ if (result.outcome === "requires-selection" && result.cwdRecovery !== undefined && (request.command === "resume" || request.command === "import")) {
610
+ return this.runWorkflow(await selectSessionCwdRecovery(request, result, this.#extensionBridge.context));
612
611
  }
613
612
  if (result.outcome === "requires-confirmation" && request.command === "import") {
614
- const recoveringCwd = result.detail !== undefined;
615
- const confirmed = await this.#extensionBridge.context.confirm(recoveringCwd ? "Session cwd not found" : "Import session", result.message);
616
- return this.runWorkflow({
617
- ...request,
618
- confirmed: confirmed === true,
619
- ...(confirmed === true && result.detail ? { cwdOverride: result.detail } : {}),
620
- });
613
+ const confirmed = await this.#extensionBridge.context.confirm("Import session", result.message);
614
+ return this.runWorkflow({ ...request, confirmed: confirmed === true });
621
615
  }
622
616
  if (result.outcome === "requires-selection" || result.outcome === "requires-confirmation") {
623
617
  this.root.workflow.appendResult({ command: request.command, outcome: "failed", message: `Owned controller missing for ${request.command}` });
@@ -270,8 +270,8 @@ export class OwnedUiTerminalHost {
270
270
  requestHyperlinkCleanup: rows => { if (attached())
271
271
  this.#damageTerminal?.requestHyperlinkCleanup(rows); },
272
272
  openRoute: (surface, callbacks) => this.#openRoute(presenter, surface, callbacks),
273
- // Invariant: an inactive presenter must never hold the active presenter's frames.
274
- beginPresentationHold: () => attached() ? runtime.beginPresentationHold() : () => { },
273
+ // Invariant: only an attached presenter on a running runtime can hold frames.
274
+ beginPresentationHold: () => attached() && runtime.active ? runtime.beginPresentationHold() : () => { },
275
275
  switchMode: mode => runtime.switchMode(mode),
276
276
  setTitle: title => { if (attached())
277
277
  runtime.setTitle(title); },
@@ -0,0 +1,65 @@
1
+ import type { OwnedUiBackgroundSettingsPort, OwnedUiSessionBackend, OwnedUiShellHeaderOptions, OwnedUiViewportSettingsPort, OwnedUiQuitOutroSettingsPort } from "../../contracts/owned-ui/index.js";
2
+ import type { TerminalInputCoordinationScheduler, TerminalInputDiagnosticsEvent, TerminalPort } from "../../contracts/presentation/index.js";
3
+ import type { StreamPresentationScheduler } from "./stream-presentation-coalescer.js";
4
+ import type { UiRouteHost, UiRouteInput } from "../../ui/apps/index.js";
5
+ /** The engine session the shell presents and the composition's decisions about it. */
6
+ export interface OwnedUiShellEngineOptions {
7
+ readonly backend: OwnedUiSessionBackend;
8
+ readonly cwd: string;
9
+ /**
10
+ * Declared A1-owned routes. A route this host claims resolves to its app;
11
+ * every other route continues to the pinned workflow table unchanged.
12
+ */
13
+ readonly routeHost?: UiRouteHost;
14
+ /** One product-owned route presented after the first usable frame and any startup modal. */
15
+ readonly startupRoute?: {
16
+ readonly route: string;
17
+ readonly input?: UiRouteInput;
18
+ readonly onClosed?: () => void | Promise<void>;
19
+ };
20
+ /** Bare A1 selects the owned bounded viewport; comparison profiles stay pinned. */
21
+ readonly sessionLayout?: "pinned" | "custom-viewport";
22
+ }
23
+ /** Terminal, startup, viewport, and presentation-cadence seams; production leaves most at their defaults. */
24
+ export interface OwnedUiShellPresentationOptions {
25
+ readonly terminal?: TerminalPort;
26
+ readonly startup?: OwnedUiShellHeaderOptions;
27
+ /** Live profile-local settings, supplied only to the bare-A1 composition. */
28
+ readonly viewportSettings?: OwnedUiViewportSettingsPort;
29
+ /** Live fullscreen canvas choice, supplied only to the bare-A1 composition. */
30
+ readonly backgroundSettings?: OwnedUiBackgroundSettingsPort;
31
+ /** Deterministic scheduling seam for presentation-cadence tests. */
32
+ readonly stream?: {
33
+ readonly intervalMs?: number;
34
+ readonly scheduler?: StreamPresentationScheduler;
35
+ };
36
+ /**
37
+ * Minimum time the reload box stays visible so a fast reload still reads as one.
38
+ * Production uses the default hold; tests inject `now`/`sleep` for determinism.
39
+ */
40
+ readonly reload?: {
41
+ readonly minVisibleMs?: number;
42
+ readonly now?: () => number;
43
+ readonly sleep?: (ms: number) => Promise<void>;
44
+ };
45
+ /**
46
+ * Quit outro settings and seams, supplied only to the bare-A1 composition.
47
+ * `interactive` states whether the terminal can show the animation; production
48
+ * passes stdout's TTY state, tests opt in explicitly.
49
+ */
50
+ readonly quitOutro?: OwnedUiQuitOutroSettingsPort & {
51
+ readonly interactive: boolean;
52
+ readonly now?: () => number;
53
+ readonly sleep?: (ms: number) => Promise<void>;
54
+ readonly seed?: number;
55
+ };
56
+ readonly input?: {
57
+ readonly scheduler?: TerminalInputCoordinationScheduler;
58
+ readonly onEvent?: (event: TerminalInputDiagnosticsEvent) => void;
59
+ readonly now?: () => number;
60
+ /** Evidence-only baseline seam; production leaves coordination enabled. */
61
+ readonly coordination?: boolean;
62
+ /** Evidence-only baseline seam; production leaves dock reuse enabled. */
63
+ readonly viewportReuse?: boolean;
64
+ };
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import type { OwnedUiCommandResult, OwnedUiWorkflowRequest } from "../../contracts/owned-ui/index.js";
1
+ import type { OwnedUiCommandResult, OwnedUiExtensionUiPort, OwnedUiWorkflowRequest, OwnedUiWorkflowResult } from "../../contracts/owned-ui/index.js";
2
2
  /** The owned selectors a workflow can open in place of asking the engine to choose. */
3
3
  export interface WorkflowSelectors {
4
4
  showScopedModelsSelector(): void;
@@ -14,6 +14,8 @@ export interface WorkflowSelectors {
14
14
  showLogoutSelector(): Promise<void>;
15
15
  showTreeSelector(): Promise<void>;
16
16
  }
17
+ /** Maps the pinned recovery labels back to explicit choices, including Escape as cancellation. */
18
+ export declare function selectSessionCwdRecovery(request: OwnedUiWorkflowRequest, result: OwnedUiWorkflowResult, ui: Pick<OwnedUiExtensionUiPort, "select">): Promise<OwnedUiWorkflowRequest>;
17
19
  /**
18
20
  * Opens the owned selector a workflow request asks for when the user has not chosen yet. Returns
19
21
  * the command result when a selector handled the request, settling once an asynchronous selector
@@ -1,3 +1,15 @@
1
+ /** Maps the pinned recovery labels back to explicit choices, including Escape as cancellation. */
2
+ export async function selectSessionCwdRecovery(request, result, ui) {
3
+ const options = result.options ?? [];
4
+ const selected = await ui.select(result.message, options.map(option => option.label));
5
+ const choice = options.find(option => option.label === selected)?.id;
6
+ const cwdRecoveryChoice = choice === "continue" || choice === "fork" ? choice : "cancel";
7
+ return {
8
+ ...request,
9
+ cwdRecoveryChoice,
10
+ ...(cwdRecoveryChoice === "continue" ? { cwdOverride: result.cwdRecovery.fallbackCwd } : {}),
11
+ };
12
+ }
1
13
  const unchosen = (request) => request.selection === undefined && request.confirmed === undefined;
2
14
  const unchosenBare = (request) => unchosen(request) && request.argument.trim().length === 0;
3
15
  // Invariant: the routes are checked in this order, matching the order the workflows were introduced.
@@ -20,6 +20,10 @@ export function cliUsage(capabilities) {
20
20
  "uninstall [--yes] [--purge]",
21
21
  "uninstall <source>",
22
22
  "list",
23
+ "worktree enter <branch> [--base <branch>]",
24
+ "worktree attach <path>",
25
+ "worktree detach",
26
+ "worktree list",
23
27
  "session worktrees",
24
28
  "session link-worktree <path>",
25
29
  "session unlink-worktree",
@@ -51,7 +55,13 @@ export function cliHelp(capabilities) {
51
55
  ` ${command} uninstall <source>`,
52
56
  ` ${command} list`,
53
57
  "",
54
- "Session context:",
58
+ "Worktrees (successful enter/attach is authoritative; detach preserves files):",
59
+ ` ${command} worktree enter <branch> [--base <branch>]`,
60
+ ` ${command} worktree attach <path>`,
61
+ ` ${command} worktree detach`,
62
+ ` ${command} worktree list`,
63
+ "",
64
+ "Session context (compatibility aliases):",
55
65
  ` ${command} session worktrees`,
56
66
  ` ${command} session link-worktree <path>`,
57
67
  ` ${command} session unlink-worktree`,
@@ -123,6 +133,8 @@ export function parseCliCommand(arguments_, capabilities) {
123
133
  }
124
134
  if (command === "update")
125
135
  return parseUpdate(rest, capabilities.runningChannel ?? "stable");
136
+ if (command === "worktree")
137
+ return parseWorktree(rest);
126
138
  if (command === "session")
127
139
  return parseSessionContext(rest);
128
140
  if (command?.startsWith("update:"))
@@ -131,6 +143,20 @@ export function parseCliCommand(arguments_, capabilities) {
131
143
  // explicit, and a typo must never start an interactive or maintenance path.
132
144
  return { kind: "noop" };
133
145
  }
146
+ function parseWorktree(rest) {
147
+ const [action, value, flag, base, extra] = rest;
148
+ if (action === "enter" && value !== undefined && !value.startsWith("-")
149
+ && (flag === undefined || (flag === "--base" && base !== undefined && !base.startsWith("-"))) && extra === undefined) {
150
+ return { kind: "session-context", request: { action: "enter-worktree", branch: value, ...(base === undefined ? {} : { base }) } };
151
+ }
152
+ if (action === "attach" && value !== undefined && flag === undefined) {
153
+ return { kind: "session-context", request: { action: "link-worktree", path: value } };
154
+ }
155
+ if ((action === "detach" || action === "list") && value === undefined) {
156
+ return { kind: "session-context", request: { action: action === "detach" ? "unlink-worktree" : "worktrees" } };
157
+ }
158
+ return { kind: "error", message: PRODUCT_TEXT.diagnostic("worktree accepts enter <branch> [--base <branch>], attach <path>, detach, or list.") };
159
+ }
134
160
  function parseSessionContext(rest) {
135
161
  const [action, value, extra] = rest;
136
162
  if (action === "link-worktree") {
@@ -21,7 +21,7 @@ export async function runPackageCommand(request, environment) {
21
21
  const port = environment.createPort({
22
22
  profileRoot,
23
23
  cwd,
24
- onProgress: progress => stdout(style.dim(`${progress.message}\n`)),
24
+ onProgress: progress => stdout((progress.severity === "warning" ? style.yellow : style.dim)(`${progress.message}\n`)),
25
25
  onDiagnostic: diagnostic => {
26
26
  stderr(`${style.yellow(diagnostic.message)}\n`);
27
27
  if (diagnostic.detail)
@@ -60,11 +60,27 @@ export function renderPackageOutcome(outcome, style = chalk) {
60
60
  case "remove":
61
61
  return `${style.green(`Removed ${outcome.source}`)}\n`;
62
62
  case "update":
63
- return `${style.green(outcome.source === null ? "Updated packages" : `Updated ${outcome.source}`)}\n`;
63
+ return renderPackageUpdate(outcome, style);
64
64
  case "list":
65
65
  return renderPackageList(outcome, style);
66
66
  }
67
67
  }
68
+ function renderPackageUpdate(outcome, style) {
69
+ const summary = outcome.updateSummary;
70
+ if (summary?.offline)
71
+ return `${style.yellow("Skipped package updates (offline mode)")}\n`;
72
+ const changes = summary?.changes ?? [];
73
+ if (changes.length > 0) {
74
+ const heading = `Updated ${changes.length} package${changes.length === 1 ? "" : "s"}:`;
75
+ return `${style.green(heading)}\n${changes.map(change => ` ${change}\n`).join("")}`;
76
+ }
77
+ if (summary?.warningCount) {
78
+ return `${style.yellow(outcome.source
79
+ ? `${outcome.source} was not updated, see the warnings above`
80
+ : "No packages were updated, see the warnings above")}\n`;
81
+ }
82
+ return `${style.green(outcome.source ? `${outcome.source} is already up to date` : "Packages are already up to date")}\n`;
83
+ }
68
84
  function renderPackageList(outcome, style) {
69
85
  if (outcome.packages.length === 0)
70
86
  return `${style.dim("No packages installed.")}\n`;
@@ -16,7 +16,7 @@ export async function runSessionContextCommand(request, options = {}) {
16
16
  if (outcome.kind === "unlinked")
17
17
  stdout("Session worktree context cleared.\n");
18
18
  if (outcome.kind === "linked")
19
- stdout(`Session worktree context linked: ${outcome.cwd}\n`);
19
+ stdout(`Session worktree context linked: ${outcome.cwd} (${outcome.branch})\n`);
20
20
  if (outcome.kind === "worktrees") {
21
21
  for (const entry of outcome.entries) {
22
22
  // Compatibility: the primary agent is the only agent of a single-session runtime, so it stays unnamed.
@@ -44,7 +44,7 @@ function readIdentity(environment) {
44
44
  /** Known coordination failures must not expose internal paths or another session's identity. */
45
45
  function recoveryDetail(code) {
46
46
  const details = {
47
- "worktree-claim-busy": "another mutation is still running; runtime registration retries automatically, so retry this link shortly",
47
+ "worktree-claim-busy": "coordination did not settle within the operation budget; no ownership was changed; retry this link shortly if the system remains busy",
48
48
  "worktree-legacy-recovery-required": "an older-format worktree lock requires safe recovery; stop all older A1 processes, move only session-worktree-claims/mutation.lock to a backup while stopped, then start the updated A1; keep all sessions and worktree claims",
49
49
  "worktree-coordination-unavailable": "worktree coordination is unavailable; repair or update this A1 installation, then retry",
50
50
  "worktree-coordination-io": "worktree coordination could not access its files; check A1 data-directory permissions and filesystem availability, then retry",
@@ -54,6 +54,10 @@ function recoveryDetail(code) {
54
54
  "session-runtime-stopped": "this shell belongs to a stopped runtime; resume in a running A1 session before linking",
55
55
  "worktree-active-in-another-session": "another live agent owns this worktree; use a different worktree or wait for its owner to release it",
56
56
  "worktree-owner-unverifiable": "worktree ownership cannot be verified; retain the claim and use another worktree rather than forcing recovery",
57
+ "worktree-primary-checkout": "attach a task worktree, not the primary checkout; use worktree enter to create one",
58
+ "worktree-git-locked": "Git has locked this worktree; retain it and ask its owner to release the lock",
59
+ "worktree-foreign-repository": "the selected worktree belongs to a different repository; the previous attachment was preserved",
60
+ "worktree-context-changed": "the attachment changed during the operation; no ownership was overridden",
57
61
  };
58
62
  return code === null ? undefined : details[code];
59
63
  }
@@ -17,22 +17,23 @@ import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentat
17
17
  import { createOwnedRouteHost } from "./settings-route-host.js";
18
18
  import { renderPiShellChangelogLines } from "../integrations/pi/components/shell-presenters-info.js";
19
19
  import { nativeHyperlinkStyle } from "../ui/components/spans.js";
20
- import { SessionRuntimeRegistry } from "./session-runtime-registry.js";
21
20
  import { bindAutomaticSessionNaming } from "./session-naming.js";
22
21
  import { deferStartupReleaseUpdateObservation, readPackageVersion, StartupReleaseNote } from "./startup-route.js";
23
22
  import { createOwnedSettingsManager, watchSharedSettings } from "./owned-settings.js";
24
23
  /** Production default: without an inspector the registry never registers; loaded lazily. */
25
24
  const inspectLiveProcessOnDemand = async (pid) => (await import("../foundation/lifecycle/process-liveness.js")).inspectLiveProcess(pid);
25
+ /** Load coordination only when composing a session, not while evaluating the startup facade. */
26
+ async function createSessionRuntimes(bare, inspectProcess) {
27
+ const { SessionRuntimeRegistry } = await import("./session-runtime-registry.js");
28
+ return new SessionRuntimeRegistry({ runtimeId: bare ? process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] : undefined, inspectProcess });
29
+ }
26
30
  export async function composeOwnedUiApplication(options = {}) {
27
31
  return (await composeOwnedUi(options)).application;
28
32
  }
29
33
  export async function composeOwnedUi(options = {}) {
30
34
  const cwd = options.cwd ?? process.cwd();
31
35
  const productMode = options.productMode ?? "bare", bare = productMode === "bare";
32
- const sessionRuntimes = new SessionRuntimeRegistry({
33
- runtimeId: bare ? process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] : undefined,
34
- inspectProcess: options.inspectProcess ?? inspectLiveProcessOnDemand,
35
- });
36
+ const sessionRuntimes = await createSessionRuntimes(bare, options.inspectProcess ?? inspectLiveProcessOnDemand);
36
37
  const productPaths = resolveProductPaths();
37
38
  // Rationale: the manager reads the engine's settings port lazily, after the session exists.
38
39
  let backend;
@@ -46,12 +47,14 @@ export async function composeOwnedUi(options = {}) {
46
47
  const bareSettings = bare ? settings : null;
47
48
  // Invariant: process-wide engine state has one owner. The host installs the HTTP dispatcher, applies
48
49
  // the theme singletons, and announces startup notices once; every session is created through it.
49
- const host = await (options.createEngineHost ?? createPiEngineHost)(engineHostOptions(options, productMode, bareSettings));
50
+ const host = await (options.createEngineHost ?? createPiEngineHost)(engineHostOptions(options, productMode, bareSettings, productPaths.runtimeDir));
50
51
  const sessionForkPrompt = options.sessionForkPrompt;
51
52
  try {
52
53
  backend = await host.create({
53
54
  cwd,
54
55
  ...(bare ? { repositoryContextReader: sessionRuntimes.readerFor(undefined) } : {}),
56
+ ...(bare && process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] !== undefined
57
+ ? { worktreeCwdReader: sessionRuntimes.toolReaderFor(undefined) } : {}),
55
58
  ...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
56
59
  ...(options.sessionSelection === undefined ? {} : { sessionSelection: options.sessionSelection }),
57
60
  ...(sessionForkPrompt === undefined ? {} : { sessionForkPrompt }),
@@ -127,7 +130,7 @@ export async function composeOwnedUi(options = {}) {
127
130
  let presenter;
128
131
  try {
129
132
  const presenterOptions = {
130
- presenters: createPiSessionPresenters(backend),
133
+ presenters: createPiSessionPresenters(backend, spinnerPresenterOptions(bareSettings)),
131
134
  engine: {
132
135
  backend,
133
136
  cwd: backend.identity.cwd,
@@ -201,6 +204,12 @@ export async function composeOwnedUi(options = {}) {
201
204
  };
202
205
  return { application, settings };
203
206
  }
207
+ function spinnerPresenterOptions(settings) {
208
+ return settings === null ? {} : { spinner: {
209
+ snapshot: () => settings.value("spinnerStyle"),
210
+ onChange: (listener) => settings.onChange(() => listener()),
211
+ } };
212
+ }
204
213
  /** The settings-driven ports only bare A1 receives: viewport, background, quit outro, suggestions, image limit, and skills. */
205
214
  function bareShellPorts(settings, generator, diagnostics) {
206
215
  const viewportSettings = {
@@ -235,10 +244,13 @@ function bareShellPorts(settings, generator, diagnostics) {
235
244
  },
236
245
  };
237
246
  }
238
- function engineHostOptions(options, productMode, settings) {
247
+ function engineHostOptions(options, productMode, settings, runtimeDir) {
239
248
  const bare = productMode === "bare";
240
249
  return {
241
250
  productMode,
251
+ deliveryIncident: failure => {
252
+ void import("../foundation/terminal-cleanup/index.js").then(module => module.writeFatalDiagnostic(`${runtimeDir}/crashes`, "delivery", Object.assign(new Error(), { deliveryFailure: failure }), options.packageVersion)).catch(() => undefined);
253
+ },
242
254
  availableThemes: () => getAvailablePiThemes().map(theme => theme.name),
243
255
  ...(bare ? { workflowHost: { reserveExport: async (id, format) => (await import("../foundation/lifecycle/user-exports.js")).reserveUserExport(id, format) } } : {}),
244
256
  announceStartupChangelog: !bare,
@@ -6,7 +6,7 @@ export function bindAutomaticSessionNaming(backend, settings, bare) {
6
6
  const naming = new SessionNamingController({
7
7
  backend, naming: backend.naming,
8
8
  enabled: () => settings?.value("sessionAutoName") ?? true,
9
- format: () => settings?.value("sessionNameFormat") ?? "kebab",
9
+ format: () => settings?.value("sessionNameFormat") ?? "sentence",
10
10
  withContext: async (consume, signal) => {
11
11
  const sessionId = backend.identity.currentHistorySessionId();
12
12
  const sessionFile = backend.identity.currentSessionFile();
@@ -16,6 +16,8 @@ export declare class SessionRuntimeRegistry {
16
16
  branch: string;
17
17
  revision: string;
18
18
  } | null>;
19
+ /** Strict tool cwd lookup: transient startup failure never redirects a write to primary. */
20
+ toolReaderFor(agentId: string | undefined): (sessionId: string, sessionFile: string, signal: AbortSignal) => Promise<string>;
19
21
  /** Drain publication before releasing claims; never republish after disposal. */
20
22
  dispose(): Promise<void>;
21
23
  }
@@ -2,6 +2,7 @@
2
2
  export class SessionRuntimeRegistry {
3
3
  #options;
4
4
  #identities = new Map();
5
+ #activated = new Map();
5
6
  #abort = new AbortController();
6
7
  #owner = null;
7
8
  #timer = null;
@@ -15,7 +16,9 @@ export class SessionRuntimeRegistry {
15
16
  const { runtimeId, inspectProcess } = this.#options;
16
17
  if (this.#disposed || runtimeId === undefined || inspectProcess === undefined)
17
18
  return null;
18
- const identity = { sessionId, sessionFile };
19
+ const previous = this.#identities.get(agentId);
20
+ const identity = previous?.sessionId === sessionId && previous.sessionFile === sessionFile
21
+ ? previous : { sessionId, sessionFile };
19
22
  this.#identities.set(agentId, identity);
20
23
  // Invariant: initial activation or inspection failure must not disable retries.
21
24
  this.#arm();
@@ -23,25 +26,62 @@ export class SessionRuntimeRegistry {
23
26
  const owner = await this.#ownerIdentity();
24
27
  if (owner === null || !this.#current(agentId, identity))
25
28
  return null;
26
- const { activateSessionRepositoryContext, withSessionRepositoryObservation } = await import("../foundation/lifecycle/session-repository-context.js");
29
+ const { activateSessionRepositoryContext, readActiveSessionRepositoryContext } = await import("../foundation/lifecycle/session-repository-context.js");
27
30
  if (!this.#current(agentId, identity))
28
31
  return null;
29
32
  const options = {
33
+ runtimeId,
30
34
  signal: AbortSignal.any([signal, this.#abort.signal]),
31
35
  inspectProcess,
32
36
  ...(agentId === undefined ? {} : { agentId }),
33
37
  };
34
- const selected = await activateSessionRepositoryContext(identity, runtimeId, owner, options);
35
- if (selected === null)
36
- return null;
37
- return await withSessionRepositoryObservation(identity, context => {
38
+ if (this.#activated.get(agentId) !== identity) {
39
+ await activateSessionRepositoryContext(identity, runtimeId, owner, options);
40
+ if (!this.#current(agentId, identity))
41
+ return null;
42
+ this.#activated.set(agentId, identity);
43
+ }
44
+ try {
45
+ const context = await readActiveSessionRepositoryContext(identity, options);
46
+ if (context === null)
47
+ return null;
38
48
  if (context.branch === null)
39
49
  throw new Error("associated branch unavailable");
40
50
  return { cwd: context.cwd, branch: context.branch, revision: context.revision };
41
- }, options);
51
+ }
52
+ catch (error) {
53
+ const code = error instanceof Error && "code" in error ? error.code : null;
54
+ if ((code === "session-runtime-unavailable" || code === "worktree-context-changed") && this.#current(agentId, identity)) {
55
+ this.#activated.delete(agentId);
56
+ }
57
+ throw error;
58
+ }
42
59
  });
43
60
  };
44
61
  }
62
+ /** Strict tool cwd lookup: transient startup failure never redirects a write to primary. */
63
+ toolReaderFor(agentId) {
64
+ const read = this.readerFor(agentId);
65
+ return async (sessionId, sessionFile, signal) => {
66
+ const { retrySessionCoordination } = await import("../foundation/lifecycle/session-context-retry.js");
67
+ return await retrySessionCoordination(async () => {
68
+ signal.throwIfAborted();
69
+ if (this.#disposed || this.#options.runtimeId === undefined)
70
+ throw new Error("worktree runtime is not active");
71
+ const context = await read(sessionId, sessionFile, signal);
72
+ if (this.#disposed)
73
+ throw new Error("worktree runtime is not active");
74
+ const active = this.#activated.get(agentId);
75
+ if (active?.sessionId !== sessionId || active.sessionFile !== sessionFile) {
76
+ throw Object.assign(new Error("worktree runtime registration is still recovering"), { code: "session-runtime-unavailable" });
77
+ }
78
+ if (context !== null)
79
+ return context.cwd;
80
+ const { readSessionStartupRoot } = await import("../foundation/lifecycle/session-repository-context.js");
81
+ return await readSessionStartupRoot({ sessionId, sessionFile });
82
+ }, 8_000, signal);
83
+ };
84
+ }
45
85
  /** Drain publication before releasing claims; never republish after disposal. */
46
86
  async dispose() {
47
87
  this.#disposed = true;
@@ -82,7 +122,7 @@ export class SessionRuntimeRegistry {
82
122
  #arm() {
83
123
  if (this.#timer !== null)
84
124
  return;
85
- this.#timer = setInterval(() => this.#queueRefresh(), this.#options.refreshMs ?? 30_000);
125
+ this.#timer = setInterval(() => this.#queueRefresh(), this.#options.refreshMs ?? 1_000);
86
126
  this.#timer.unref();
87
127
  }
88
128
  #queueRefresh() {
@@ -96,7 +136,7 @@ export class SessionRuntimeRegistry {
96
136
  return;
97
137
  const { registerSessionRepositoryRuntime } = await import("../foundation/lifecycle/session-repository-context.js");
98
138
  for (const [agentId, identity] of this.#identities) {
99
- if (!this.#current(agentId, identity))
139
+ if (!this.#current(agentId, identity) || this.#activated.get(agentId) === identity)
100
140
  continue;
101
141
  await registerSessionRepositoryRuntime(identity, runtimeId, owner, {
102
142
  inspectProcess,
@@ -1,17 +1,6 @@
1
- /**
2
- * Extension packages are managed outside any session: the command that installs
3
- * one runs before the runtime exists, against a profile root chosen by the caller
4
- * rather than by whatever session happens to be open. So this port stands apart
5
- * from the session-scoped service ports and names only what a package operation
6
- * needs — a root, a source, and an outcome a caller can render without knowing
7
- * which engine performed it.
8
- */
1
+ /** Non-interactive package operations use a caller-chosen profile root before any session exists. */
9
2
  export type AgentPackageOperation = "install" | "remove" | "update" | "refresh-models" | "list";
10
- /**
11
- * Why an operation ended, kept separate from the prose so callers can branch on
12
- * it. `not-found` is the one failure an engine can state structurally — the source
13
- * is not configured in this profile — rather than only in a message.
14
- */
3
+ /** Status is separate from prose; not-found means the source is unconfigured. */
15
4
  export type AgentPackageStatus = "completed" | "not-found" | "failed";
16
5
  export interface AgentPackageDescriptor {
17
6
  readonly source: string;
@@ -25,10 +14,17 @@ export interface AgentPackageOutcome {
25
14
  readonly source: string | null;
26
15
  readonly packages: readonly AgentPackageDescriptor[];
27
16
  readonly detail: string | null;
17
+ /** Update facts distinguish a checked result from skipped or warned work. */
18
+ readonly updateSummary?: {
19
+ readonly offline: boolean;
20
+ readonly warningCount: number;
21
+ readonly changes: readonly string[];
22
+ };
28
23
  }
29
24
  export interface AgentPackageProgress {
30
25
  readonly operation: AgentPackageOperation;
31
26
  readonly message: string;
27
+ readonly severity?: "warning";
32
28
  }
33
29
  export interface AgentPackagesPort {
34
30
  readonly capabilities: {
@@ -57,4 +53,4 @@ export interface AgentPackagesPortInput {
57
53
  readonly onDiagnostic?: (diagnostic: AgentPackageDiagnostic) => void;
58
54
  }
59
55
  export declare function assertAgentPackagesPort(port: AgentPackagesPort): void;
60
- export declare function agentPackageOutcome(operation: AgentPackageOperation, status: AgentPackageStatus, detail?: string | null, source?: string | null, packages?: readonly AgentPackageDescriptor[]): AgentPackageOutcome;
56
+ export declare function agentPackageOutcome(operation: AgentPackageOperation, status: AgentPackageStatus, detail?: string | null, source?: string | null, packages?: readonly AgentPackageDescriptor[], updateSummary?: AgentPackageOutcome["updateSummary"]): AgentPackageOutcome;