@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
@@ -2,8 +2,11 @@ import { createAgentSessionServices, SettingsManager, type AgentSession, type Ag
2
2
  import { resolvePiProjectTrustPreflight, type PiProjectTrustPreflightPrompt } from "./project-trust-preflight.js";
3
3
  import { type PiSessionForkPrompt, type PiSessionSelection } from "./session-selection.js";
4
4
  import { type StartupPhase } from "../../../foundation/startup/startup-runtime.js";
5
+ import { type PiWorktreeCwdReader } from "./worktree-tools.js";
5
6
  export interface PiRuntimeIntegrationOptions {
6
7
  readonly cwd: string;
8
+ /** Only the bare claim-owning product supplies this; comparison tools keep their ordinary cwd. */
9
+ readonly worktreeCwdReader?: PiWorktreeCwdReader;
7
10
  readonly agentDir: string;
8
11
  readonly sessionDir?: string;
9
12
  readonly sessionPath?: string;
@@ -51,6 +54,11 @@ export declare function createPiRuntimeServicesAfterTrust(options: Pick<PiRuntim
51
54
  readonly trust: Awaited<ReturnType<typeof resolvePiProjectTrustPreflight>>;
52
55
  }>;
53
56
  export declare function createPiRuntimeIntegration(options: PiRuntimeIntegrationOptions): Promise<AgentSessionRuntime>;
57
+ /** Switch to an independent copy; an extension veto or failed switch must not leave a stray fork. */
58
+ export declare function switchToRecoveredFork(runtime: AgentSessionRuntime, sourcePath: string, cwd: string, options?: Parameters<AgentSessionRuntime["switchSession"]>[1]): Promise<{
59
+ readonly cancelled: boolean;
60
+ readonly message: string;
61
+ }>;
54
62
  export declare function bindPiRuntimeSession(runtime: AgentSessionRuntime, rebind: (session: AgentSession) => Promise<void>): () => void;
55
63
  export declare function replacePiRuntimeSession(runtime: AgentSessionRuntime, replacement: PiSessionReplacement): Promise<{
56
64
  readonly cancelled: boolean;
@@ -1,10 +1,11 @@
1
- import { closeSync, mkdirSync, openSync, writeFileSync } from "node:fs";
1
+ import { closeSync, mkdirSync, openSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, resolveModelScopeWithDiagnostics, SessionManager, SettingsManager, } from "../startup-public.js";
4
4
  import { resolvePiProjectTrustPreflight, } from "./project-trust-preflight.js";
5
5
  import { openSelectedPiSession, resolveSessionArgumentPath, takeSelectedSessionWriter } from "./session-selection.js";
6
6
  import { markStartupPhase } from "../../../foundation/startup/startup-runtime.js";
7
7
  import { createWindowsNulCleanupExtension } from "./windows-filesystem-hygiene.js";
8
+ import { createSessionWorktreeTools } from "./worktree-tools.js";
8
9
  import { acquireSessionWriter, } from "../../../foundation/session-writer-lock/index.js";
9
10
  class SessionWriterCoordinator {
10
11
  #current;
@@ -140,13 +141,22 @@ export async function createPiRuntimeIntegration(options) {
140
141
  const created = await createAgentSessionFromServices({
141
142
  services,
142
143
  sessionManager: targetSessionManager,
144
+ ...(options.worktreeCwdReader === undefined ? {} : {
145
+ customTools: createSessionWorktreeTools(cwd, signal => options.worktreeCwdReader(targetSessionManager.getSessionId(), targetSessionManager.getSessionFile(), signal ?? options.signal ?? new AbortController().signal)).filter(tool => !services.resourceLoader.getExtensions().extensions.some(extension => extension.tools.has(tool.name))),
146
+ }),
143
147
  ...(sessionStartEvent ? { sessionStartEvent } : {}),
144
148
  ...(modelScope.model && !hasExistingSession ? { model: modelScope.model } : {}),
145
149
  ...(modelScope.thinkingLevel && !hasExistingSession ? { thinkingLevel: modelScope.thinkingLevel } : {}),
146
150
  ...(modelScope.scopedModels.length > 0 ? { scopedModels: [...modelScope.scopedModels] } : {}),
147
151
  });
152
+ try {
153
+ await (options.markStartupPhase ?? defaultStartupPhaseMarker)("session-created");
154
+ }
155
+ catch (error) {
156
+ created.session.dispose();
157
+ throw error;
158
+ }
148
159
  sessionWriters.activate(pendingWriter);
149
- await (options.markStartupPhase ?? defaultStartupPhaseMarker)("session-created");
150
160
  return {
151
161
  ...created,
152
162
  services,
@@ -161,17 +171,56 @@ export async function createPiRuntimeIntegration(options) {
161
171
  throw error;
162
172
  }
163
173
  };
164
- const runtime = await createAgentSessionRuntime(createRuntime, {
165
- cwd: sessionManager.getCwd(),
166
- agentDir: options.agentDir,
167
- sessionManager,
168
- });
169
- runtimeSessionWriters.set(runtime, sessionWriters);
170
- return runtime;
174
+ try {
175
+ const runtime = await createAgentSessionRuntime(createRuntime, {
176
+ cwd: sessionManager.getCwd(),
177
+ agentDir: options.agentDir,
178
+ sessionManager,
179
+ });
180
+ runtimeSessionWriters.set(runtime, sessionWriters);
181
+ const dispose = runtime.dispose.bind(runtime);
182
+ let disposal;
183
+ // Invariant: every public runtime consumer uses the writer-owning teardown, including the adapter.
184
+ runtime.dispose = () => disposal ??= (async () => {
185
+ try {
186
+ await dispose();
187
+ }
188
+ finally {
189
+ sessionWriters.release();
190
+ runtimeSessionWriters.delete(runtime);
191
+ }
192
+ })();
193
+ return runtime;
194
+ }
195
+ catch (error) {
196
+ takeSelectedSessionWriter(sessionManager)?.release();
197
+ sessionWriters.release();
198
+ throw error;
199
+ }
171
200
  }
172
201
  function defaultStartupPhaseMarker(phase) {
173
202
  return markStartupPhase(process.env, phase);
174
203
  }
204
+ /** Switch to an independent copy; an extension veto or failed switch must not leave a stray fork. */
205
+ export async function switchToRecoveredFork(runtime, sourcePath, cwd, options) {
206
+ const source = resolveSessionArgumentPath(sourcePath, process.cwd());
207
+ const fork = SessionManager.forkFrom(source, cwd, runtime.session.sessionManager.getSessionDir());
208
+ const path = fork.getSessionFile();
209
+ if (path === undefined)
210
+ throw new Error("Fork did not create a session file");
211
+ try {
212
+ const result = await runtime.switchSession(path, options);
213
+ if (result.cancelled)
214
+ unlinkSync(path);
215
+ return { cancelled: result.cancelled, message: `Forked into new session ${fork.getSessionId()} in ${cwd}` };
216
+ }
217
+ catch (error) {
218
+ // Security: a replacement callback can throw after the fork became active; never delete that active file.
219
+ if (runtime.session.sessionManager.getSessionFile() !== path)
220
+ unlinkSync(path);
221
+ throw error;
222
+ }
223
+ }
175
224
  export function bindPiRuntimeSession(runtime, rebind) {
176
225
  runtime.setRebindSession(rebind);
177
226
  return () => runtime.setRebindSession(undefined);
@@ -0,0 +1,11 @@
1
+ import type { OwnedUiWorkflowResult } from "../../../contracts/owned-ui/index.js";
2
+ /** Pinned session-cwd prompt policy, adapted without importing Pi's private modules. */
3
+ export declare function missingSessionCwdRecovery(error: unknown): (Pick<OwnedUiWorkflowResult, "message"> & {
4
+ readonly options: readonly {
5
+ readonly id: "continue" | "fork" | "cancel";
6
+ readonly label: string;
7
+ }[];
8
+ readonly cwdRecovery: {
9
+ readonly fallbackCwd: string;
10
+ };
11
+ }) | undefined;
@@ -0,0 +1,18 @@
1
+ import { isRecord, stringProperty } from "./message-values.js";
2
+ /** Pinned session-cwd prompt policy, adapted without importing Pi's private modules. */
3
+ export function missingSessionCwdRecovery(error) {
4
+ const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
5
+ const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
6
+ const sessionCwd = issue === undefined ? undefined : stringProperty(issue, "sessionCwd");
7
+ if (fallbackCwd === undefined || sessionCwd === undefined)
8
+ return undefined;
9
+ return {
10
+ message: `Session cwd does not exist: ${sessionCwd}`,
11
+ cwdRecovery: { fallbackCwd },
12
+ options: [
13
+ { id: "continue", label: `Continue in ${fallbackCwd} (asks again next time)` },
14
+ { id: "fork", label: `Fork into ${fallbackCwd} (new session ID, original kept)` },
15
+ { id: "cancel", label: "Cancel" },
16
+ ],
17
+ };
18
+ }
@@ -7,8 +7,10 @@ import type { OwnedUiDiagnostics } from "../../../contracts/owned-ui/index.js";
7
7
  import type { PiSessionResumeMetadata, PiWorkflowHost } from "./workflows.js";
8
8
  import type { PiPullRequestProbe } from "./repository-pr.js";
9
9
  import { RepositoryMetadataPoller, type PiRepositoryContextReader } from "./repository-metadata.js";
10
+ import type { PiWorktreeCwdReader } from "./worktree-tools.js";
10
11
  export interface PiEngineRuntimeFactoryInput {
11
12
  readonly cwd: string;
13
+ readonly worktreeCwdReader?: PiWorktreeCwdReader;
12
14
  readonly agentDir: string;
13
15
  readonly sessionId: string;
14
16
  readonly sessionPath?: string;
@@ -39,6 +41,7 @@ interface PiEngineRuntimeOptions {
39
41
  readonly pullRequestRefreshMs?: number;
40
42
  readonly repositoryContextPollMs?: number;
41
43
  readonly repositoryContextReader?: PiRepositoryContextReader;
44
+ readonly worktreeCwdReader?: PiWorktreeCwdReader;
42
45
  readonly gitBranchReader?: (cwd: string, signal: AbortSignal) => Promise<string | null>;
43
46
  readonly host: PiWorkflowHost;
44
47
  }
@@ -84,6 +84,7 @@ export class PiEngineRuntime {
84
84
  sessionId: this.#options.sessionId,
85
85
  signal: this.#options.signal,
86
86
  markStartupPhase: this.#options.markStartupPhase,
87
+ ...(this.#options.worktreeCwdReader === undefined ? {} : { worktreeCwdReader: this.#options.worktreeCwdReader }),
87
88
  ...(this.#options.sessionPath === undefined ? {} : { sessionPath: this.#options.sessionPath }),
88
89
  ...(this.#options.sessionSelection === undefined ? {} : { sessionSelection: this.#options.sessionSelection }),
89
90
  ...(this.#options.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: this.#options.sessionForkPrompt }),
@@ -272,6 +273,7 @@ async function createDefaultPiRuntime(input) {
272
273
  agentDir: input.agentDir,
273
274
  signal: input.signal,
274
275
  markStartupPhase: input.markStartupPhase,
276
+ ...(input.worktreeCwdReader === undefined ? {} : { worktreeCwdReader: input.worktreeCwdReader }),
275
277
  ...(input.sessionPath === undefined ? {} : { sessionPath: input.sessionPath }),
276
278
  ...(input.sessionSelection === undefined ? {} : { sessionSelection: input.sessionSelection }),
277
279
  ...(input.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: input.sessionForkPrompt }),
@@ -6,7 +6,8 @@ export interface PiSessionSelection {
6
6
  }
7
7
  export type PiSessionForkPrompt = (request: {
8
8
  readonly sourceCwd: string;
9
- }) => Promise<boolean>;
9
+ readonly fallbackCwd?: string;
10
+ }) => Promise<boolean | "continue" | "fork" | "cancel">;
10
11
  /** Expected resume outcomes are concise CLI diagnostics, not engine crashes. */
11
12
  export declare class PiSessionSelectionError extends Error {
12
13
  readonly exitCode: number;
@@ -24,7 +25,7 @@ interface SessionSelectionOptions {
24
25
  };
25
26
  }
26
27
  export declare function takeSelectedSessionWriter(manager: SessionManager): SessionWriterHandle | undefined;
27
- /** Pinned Pi 0.84.2 main.ts policy, using SDK APIs rather than importing the CLI. */
28
+ /** A1 CLI selection policy over pinned SDK APIs, including missing-folder recovery. */
28
29
  export declare function openSelectedPiSession(options: SessionSelectionOptions): Promise<SessionManager>;
29
30
  /** Resolve before switching effective cwd, including quoted home-relative paths. */
30
31
  export declare function resolveSessionArgumentPath(path: string, cwd: string): string;
@@ -22,7 +22,7 @@ function holdSelectedWriter(manager, writer) {
22
22
  selectedSessionWriters.set(manager, writer);
23
23
  return manager;
24
24
  }
25
- /** Pinned Pi 0.84.2 main.ts policy, using SDK APIs rather than importing the CLI. */
25
+ /** A1 CLI selection policy over pinned SDK APIs, including missing-folder recovery. */
26
26
  export async function openSelectedPiSession(options) {
27
27
  const sessions = options.sessions ?? SessionManager;
28
28
  const cwd = resolve(options.cwd);
@@ -48,8 +48,9 @@ export async function openSelectedPiSession(options) {
48
48
  path = matched.path;
49
49
  }
50
50
  assertResumeFile(path);
51
- if (global) {
52
- if (!options.forkPrompt || !await options.forkPrompt({ sourceCwd: global.cwd })) {
51
+ if (global && existsSync(global.cwd)) {
52
+ const choice = await options.forkPrompt?.({ sourceCwd: global.cwd });
53
+ if (choice !== true && choice !== "fork") {
53
54
  throw new PiSessionSelectionError("Session resume cancelled.", 0);
54
55
  }
55
56
  assertResumeFile(path);
@@ -66,8 +67,26 @@ export async function openSelectedPiSession(options) {
66
67
  // can otherwise produce a new in-memory header instead of an existing session.
67
68
  const writer = options.guardWriter ? acquireExisting(path) : undefined;
68
69
  try {
69
- const manager = sessions.open(path, sessionDir);
70
+ let manager = sessions.open(path, sessionDir);
70
71
  assertResumeFile(path);
72
+ if (!existsSync(manager.getCwd()) && options.forkPrompt !== undefined) {
73
+ const choice = await options.forkPrompt({ sourceCwd: manager.getCwd(), fallbackCwd: cwd });
74
+ if (choice !== "continue" && choice !== "fork" && choice !== true)
75
+ throw new PiSessionSelectionError("Session resume cancelled.", 0);
76
+ assertResumeFile(path);
77
+ assertCwd(cwd);
78
+ if (choice === "fork" || choice === true) {
79
+ const fork = sessions.forkFrom(path, cwd, sessionDir);
80
+ writer?.release();
81
+ if (!options.guardWriter)
82
+ return fork;
83
+ const target = fork.getSessionFile();
84
+ if (target === undefined)
85
+ throw new PiSessionSelectionError("Fork did not create a session file");
86
+ return holdSelectedWriter(fork, acquireSessionWriter(target));
87
+ }
88
+ manager = sessions.open(path, sessionDir, cwd);
89
+ }
71
90
  assertCwd(manager.getCwd());
72
91
  return writer === undefined ? manager : holdSelectedWriter(manager, writer);
73
92
  }
@@ -80,7 +99,8 @@ export async function openSelectedPiSession(options) {
80
99
  if (error instanceof PiSessionSelectionError)
81
100
  throw error;
82
101
  if (error instanceof SessionWriterConflict) {
83
- if (options.forkPrompt && await options.forkPrompt({ sourceCwd: cwd })) {
102
+ const choice = await options.forkPrompt?.({ sourceCwd: cwd });
103
+ if (choice === true || choice === "fork") {
84
104
  const manager = sessions.forkFrom(error.path, cwd, sessionDir);
85
105
  if (!options.guardWriter)
86
106
  return manager;
@@ -1,6 +1,9 @@
1
1
  import type { OwnedUiEvent, OwnedUiImageAttachment, OwnedUiTranscriptBlock, OwnedUiTranscriptImageReference } from "../../../contracts/owned-ui/index.js";
2
2
  export declare class TranscriptImageAssets {
3
3
  #private;
4
+ /** Encoded assets held only by delivery, plus shared pending lease metadata. */
5
+ pendingBytesExcluding(event?: OwnedUiEvent): number;
6
+ get pendingBytes(): number;
4
7
  resolve(id: string): OwnedUiImageAttachment | null;
5
8
  clear(): void;
6
9
  reference(value: unknown, source: OwnedUiTranscriptImageReference["source"]): OwnedUiTranscriptImageReference | undefined;
@@ -3,6 +3,22 @@ const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
3
3
  // Security: current blocks and admitted delivery snapshots own assets independently; superseded state owns none.
4
4
  export class TranscriptImageAssets {
5
5
  #assets = new Map();
6
+ #events = new WeakMap();
7
+ #captureBytes = 0;
8
+ /** Encoded assets held only by delivery, plus shared pending lease metadata. */
9
+ pendingBytesExcluding(event) {
10
+ const key = event === undefined ? undefined : event.type === "transcript-block" ? event.block : event.type === "session-view" ? event.view.transcript : undefined;
11
+ const shared = key === undefined ? undefined : this.#events.get(key);
12
+ return this.pendingBytes - (shared?.owners === 1 ? shared.exclusiveBytes() : 0);
13
+ }
14
+ get pendingBytes() {
15
+ let bytes = this.#captureBytes;
16
+ for (const entry of this.#assets.values()) {
17
+ if (entry.pending > 0 && entry.owners === entry.pending)
18
+ bytes += 64 + entry.asset.data.length * 2;
19
+ }
20
+ return bytes;
21
+ }
6
22
  #byData = new Map();
7
23
  #unowned = new Set();
8
24
  resolve(id) { return this.#assets.get(id)?.asset ?? null; }
@@ -24,7 +40,7 @@ export class TranscriptImageAssets {
24
40
  return;
25
41
  const assetId = `image-${createHash("sha256").update(value.mimeType).update("\0").update(value.data).digest("hex").slice(0, 24)}`;
26
42
  if (!this.#assets.has(assetId)) {
27
- this.#assets.set(assetId, { asset: { type: "image", data: value.data, mimeType: value.mimeType }, byteLength, owners: 0 });
43
+ this.#assets.set(assetId, { asset: { type: "image", data: value.data, mimeType: value.mimeType }, byteLength, owners: 0, pending: 0 });
28
44
  let byData = this.#byData.get(value.mimeType);
29
45
  if (byData === undefined)
30
46
  this.#byData.set(value.mimeType, byData = new Map());
@@ -50,21 +66,56 @@ export class TranscriptImageAssets {
50
66
  }
51
67
  }
52
68
  retainEvent(event) {
53
- const blocks = event.type === "transcript-block" ? [event.block] : event.type === "session-view" ? event.view.transcript : [];
54
- // Concurrency: capture entries, not ids; an obsolete generation's release cannot decrement a replacement asset.
55
- const entries = blocks.flatMap(block => (block.imageReferences ?? []).map(ref => [ref.assetId, this.#assets.get(ref.assetId)]));
56
- for (const [id, entry] of entries) {
57
- if (entry === undefined)
58
- continue;
59
- entry.owners++;
60
- this.#unowned.delete(id);
61
- }
62
- return () => {
69
+ const key = event.type === "transcript-block" ? event.block : event.type === "session-view" ? event.view.transcript : undefined;
70
+ if (key === undefined)
71
+ return () => { };
72
+ let shared = this.#events.get(key);
73
+ if (shared === undefined) {
74
+ const blocks = event.type === "transcript-block" ? [event.block] : event.type === "session-view" ? event.view.transcript : [];
75
+ // Concurrency: capture entries, not ids: obsolete generations cannot decrement replacement assets.
76
+ const entries = blocks.flatMap(block => (block.imageReferences ?? []).map(ref => [ref.assetId, this.#assets.get(ref.assetId)]));
77
+ this.#captureBytes += entries.length * 32;
63
78
  for (const [id, entry] of entries) {
64
- if (entry !== undefined && this.#assets.get(id) === entry && --entry.owners === 0)
65
- this.#delete(id);
79
+ if (entry === undefined)
80
+ continue;
81
+ entry.owners++;
82
+ entry.pending++;
83
+ this.#unowned.delete(id);
66
84
  }
67
- entries.length = 0;
85
+ shared = { owners: 0, exclusiveBytes: () => {
86
+ let bytes = entries.length * 32;
87
+ const counts = new Map();
88
+ for (const [id, entry] of entries)
89
+ if (entry !== undefined && this.#assets.get(id) === entry)
90
+ counts.set(id, (counts.get(id) ?? 0) + 1);
91
+ for (const [id, count] of counts) {
92
+ const entry = this.#assets.get(id);
93
+ if (entry.owners === count && entry.pending === count)
94
+ bytes += 64 + entry.asset.data.length * 2;
95
+ }
96
+ return bytes;
97
+ }, release: () => {
98
+ this.#captureBytes -= entries.length * 32;
99
+ for (const [id, entry] of entries) {
100
+ if (entry !== undefined && this.#assets.get(id) === entry) {
101
+ entry.pending--;
102
+ if (--entry.owners === 0)
103
+ this.#delete(id);
104
+ }
105
+ }
106
+ entries.length = 0;
107
+ this.#events.delete(key);
108
+ } };
109
+ this.#events.set(key, shared);
110
+ }
111
+ shared.owners++;
112
+ let released = false;
113
+ return () => {
114
+ if (released)
115
+ return;
116
+ released = true;
117
+ if (--shared.owners === 0)
118
+ shared.release();
68
119
  };
69
120
  }
70
121
  discardUnowned() {
@@ -14,6 +14,20 @@ declare class EngineDiagnostics {
14
14
  * touches more than one field is one named mutator, so the adapter and its collaborators never
15
15
  * rebuild these objects inline.
16
16
  */
17
+ export declare class OverloadRecovery {
18
+ #private;
19
+ readonly ports: {
20
+ generation(): number;
21
+ busy(): boolean;
22
+ canResume(): boolean;
23
+ resume(): void;
24
+ };
25
+ constructor(ports: OverloadRecovery["ports"]);
26
+ begin(): void;
27
+ defer(generation: number | undefined): void;
28
+ settled(): void;
29
+ resume(): void;
30
+ }
17
31
  export declare class EngineViewState {
18
32
  #private;
19
33
  lifecycle: OwnedUiSessionViewModel["lifecycle"];
@@ -45,6 +45,23 @@ const DEFAULT_SURFACE = {
45
45
  * touches more than one field is one named mutator, so the adapter and its collaborators never
46
46
  * rebuild these objects inline.
47
47
  */
48
+ export class OverloadRecovery {
49
+ #generation;
50
+ #settled = false;
51
+ ports;
52
+ constructor(ports) { this.ports = ports; }
53
+ begin() { this.#settled = false; }
54
+ defer(generation) { this.#generation = generation; }
55
+ settled() { this.#settled = true; this.resume(); }
56
+ resume() {
57
+ if (this.#generation === undefined || !this.#settled || this.ports.busy() || !this.ports.canResume())
58
+ return;
59
+ const generation = this.#generation;
60
+ this.#generation = undefined;
61
+ if (generation === this.ports.generation())
62
+ this.ports.resume();
63
+ }
64
+ }
48
65
  export class EngineViewState {
49
66
  #revision = 0;
50
67
  #editor = {
@@ -48,6 +48,8 @@ export declare class PiWorkflowRunner {
48
48
  get pendingCount(): number;
49
49
  /** Resolve every admitted workflow as cancelled, except the named command. */
50
50
  cancelPending(except?: PiWorkflowRequest["command"]): void;
51
+ /** Presentation pressure must not cancel an independently applied state mutation. */
52
+ cancelForDelivery(): void;
51
53
  cycleModelWorkflow(direction: "forward" | "backward"): Promise<PiWorkflowResult>;
52
54
  /** Bind the owned UI's clipboard lifecycle without changing the comparison host. True means acknowledged delivery. */
53
55
  bindClipboardWriter(writer: (text: string) => Promise<boolean>): () => void;
@@ -2,7 +2,8 @@ import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { isRecord, readModel, readThinkingLevel, requireCapability, stringProperty } from "./message-values.js";
4
4
  import { AUTH_REFRESH_TIMEOUT_MS, errorMessage, findExactWorkflowModel, pathArgument, pinnedHotkeySummary, pinnedSessionInfoPresentation, workflowConfirmation, workflowResult, } from "./workflow-support.js";
5
- import { PiProviderAuthentication } from "./provider-authentication.js";
5
+ import { missingSessionCwdRecovery } from "./session-cwd-recovery.js";
6
+ import { switchToRecoveredFork } from "./runtime-integration.js";
6
7
  /** How many workflows and engine commands may be admitted together before new work is rejected. */
7
8
  const MAX_PENDING_WORK = 32;
8
9
  /**
@@ -25,7 +26,8 @@ export class PiWorkflowRunner {
25
26
  this.#bare = options.productMode === "bare";
26
27
  this.#host = options.host;
27
28
  this.#ports = ports;
28
- this.#authentication = new PiProviderAuthentication({ agentDir: options.agentDir, contexts: options.contexts }, {
29
+ let authentication;
30
+ this.#authentication = () => authentication ??= import("./provider-authentication.js").then(({ PiProviderAuthentication }) => new PiProviderAuthentication({ agentDir: options.agentDir, contexts: options.contexts }, {
29
31
  runtime: () => ports.runtime(),
30
32
  requireSession: () => this.#requireSession(),
31
33
  disposed: () => ports.disposed(),
@@ -34,7 +36,7 @@ export class PiWorkflowRunner {
34
36
  setActiveModel: model => { ports.setActiveModel(model); },
35
37
  reconcileActiveModelAvailability: () => { ports.reconcileActiveModelAvailability(); },
36
38
  emitView: () => { ports.emitView(); },
37
- });
39
+ }));
38
40
  }
39
41
  /** Workflows admitted and not yet settled. */
40
42
  get pendingCount() {
@@ -46,6 +48,13 @@ export class PiWorkflowRunner {
46
48
  if (pending.command !== except)
47
49
  pending.cancel();
48
50
  }
51
+ /** Presentation pressure must not cancel an independently applied state mutation. */
52
+ cancelForDelivery() {
53
+ const mutations = new Set(["model", "models", "thinking", "scoped-models", "name"]);
54
+ for (const pending of this.#pending)
55
+ if (!mutations.has(pending.command))
56
+ pending.cancel();
57
+ }
49
58
  #requireSession() {
50
59
  const session = this.#ports.session();
51
60
  if (this.#ports.disposed() || !this.#ports.runtime() || !session)
@@ -114,7 +123,7 @@ export class PiWorkflowRunner {
114
123
  }
115
124
  async executeWorkflow(request) {
116
125
  const cancelled = workflowResult(request.command, "cancelled", "", undefined, "silent");
117
- if (this.#ports.admissionStopped() || this.#ports.pendingCommandCount() + this.#pending.size >= MAX_PENDING_WORK)
126
+ if (request.command !== "quit" && (this.#ports.admissionStopped() || this.#ports.pendingCommandCount() + this.#pending.size >= MAX_PENDING_WORK))
118
127
  return cancelled;
119
128
  let cancel;
120
129
  const cancellation = new Promise(resolve => { cancel = () => resolve(cancelled); });
@@ -216,12 +225,14 @@ export class PiWorkflowRunner {
216
225
  const failure = { kind: "error", message: error instanceof Error ? error.message : String(error) };
217
226
  return workflowResult(request.command, "failed", failure.message, undefined, "error", messages.length === 0 ? undefined : [...messages, failure]);
218
227
  }
219
- if (!current())
220
- return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
221
228
  const providerId = stringProperty(model, "provider") ?? "unknown";
222
229
  const modelId = stringProperty(model, "id") ?? reference;
223
- this.#ports.setActiveModel({ providerId, modelId, displayName: stringProperty(model, "name") ?? modelId });
224
- this.#ports.emitView();
230
+ // Invariant: selection already applied. A delivery-generation change cannot undo it; a replaced
231
+ // session must not receive its view, but the operation result remains truthful.
232
+ if (!this.#ports.disposed() && session === this.#ports.session()) {
233
+ this.#ports.setActiveModel({ providerId, modelId, displayName: stringProperty(model, "name") ?? modelId });
234
+ this.#ports.emitView();
235
+ }
225
236
  const resultMessage = { kind: "status", message: options.persist ? `Default model: ${providerId}/${modelId}` : `Model: ${modelId}` };
226
237
  const result = messages.length === 0
227
238
  ? workflowResult(request.command, "completed", resultMessage.message)
@@ -255,8 +266,15 @@ export class PiWorkflowRunner {
255
266
  case "clone": return cloneWorkflow(call);
256
267
  case "tree": return treeWorkflow(call);
257
268
  case "trust": return trustWorkflow(call);
258
- case "login": return await this.#authentication.login(request, session, runtime, argument, selection);
259
- case "logout": return selection ? await this.#authentication.logout(request, runtime, selection) : workflowResult(command, "failed", "Logout requires the owned authentication controller");
269
+ case "login":
270
+ case "logout": {
271
+ const generation = this.#ports.sessionGeneration();
272
+ const authentication = await this.#authentication();
273
+ if (this.#ports.disposed() || generation !== this.#ports.sessionGeneration() || this.#ports.admissionStopped())
274
+ return workflowResult(command, "cancelled", "", undefined, "silent");
275
+ return command === "login" ? authentication.login(request, session, runtime, argument, selection)
276
+ : selection ? authentication.logout(request, runtime, selection) : workflowResult(command, "failed", "Logout requires the owned authentication controller");
277
+ }
260
278
  case "new": return newSessionWorkflow(call);
261
279
  case "compact": return compactWorkflow(call);
262
280
  case "resume": return resumeWorkflow(call);
@@ -350,7 +368,7 @@ async function importWorkflow({ request, runtime, argument }) {
350
368
  if (request.confirmed === undefined) {
351
369
  return workflowConfirmation(request.command, `Replace current session with ${path}?`);
352
370
  }
353
- if (!request.confirmed)
371
+ if (!request.confirmed || request.cwdRecoveryChoice === "cancel")
354
372
  return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
355
373
  try {
356
374
  const result = await requireCapability(runtime.importFromJsonl, "importFromJsonl").call(runtime, path, request.cwdOverride);
@@ -359,13 +377,14 @@ async function importWorkflow({ request, runtime, argument }) {
359
377
  return workflowResult(request.command, "completed", `Session imported from: ${path}`);
360
378
  }
361
379
  catch (error) {
362
- const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
363
- const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
364
- if (fallbackCwd !== undefined && request.cwdOverride === undefined) {
365
- const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
366
- return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`, fallbackCwd);
380
+ const recovery = missingSessionCwdRecovery(error);
381
+ if (recovery === undefined || request.cwdOverride !== undefined)
382
+ throw error;
383
+ if (request.cwdRecoveryChoice === "fork") {
384
+ const result = await switchToRecoveredFork(runtime, path, recovery.cwdRecovery.fallbackCwd);
385
+ return workflowResult(request.command, result.cancelled ? "cancelled" : "completed", result.cancelled ? "Import cancelled" : result.message, undefined, "status");
367
386
  }
368
- throw error;
387
+ return { command: request.command, outcome: "requires-selection", ...recovery };
369
388
  }
370
389
  }
371
390
  async function copyWorkflow({ request, session }, copy) {
@@ -459,6 +478,8 @@ async function resumeWorkflow({ request, runtime, argument, selection }) {
459
478
  if (!selection && !argument)
460
479
  return workflowResult(request.command, "failed", "Resume requires the owned session controller");
461
480
  const sessionPath = selection ?? argument;
481
+ if (request.cwdRecoveryChoice === "cancel")
482
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "status");
462
483
  try {
463
484
  const result = await runtime.switchSession(sessionPath);
464
485
  if (isRecord(result) && result.cancelled === true)
@@ -466,20 +487,20 @@ async function resumeWorkflow({ request, runtime, argument, selection }) {
466
487
  return workflowResult(request.command, "completed", "Resumed session");
467
488
  }
468
489
  catch (error) {
469
- const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
470
- const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
471
- if (fallbackCwd === undefined)
490
+ const recovery = missingSessionCwdRecovery(error);
491
+ if (recovery === undefined)
472
492
  throw error;
473
- if (request.confirmed === undefined) {
474
- const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
475
- return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`);
493
+ if (request.cwdRecoveryChoice === undefined)
494
+ return { command: request.command, outcome: "requires-selection", ...recovery };
495
+ const fallbackCwd = recovery.cwdRecovery.fallbackCwd;
496
+ if (request.cwdRecoveryChoice === "fork") {
497
+ const result = await switchToRecoveredFork(runtime, sessionPath, fallbackCwd);
498
+ return workflowResult(request.command, result.cancelled ? "cancelled" : "completed", result.cancelled ? "Resume cancelled" : result.message, undefined, "status");
476
499
  }
477
- if (!request.confirmed)
478
- return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "status");
479
500
  const result = await runtime.switchSession(sessionPath, { cwdOverride: fallbackCwd });
480
501
  if (isRecord(result) && result.cancelled === true)
481
502
  return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
482
- return workflowResult(request.command, "completed", "Resumed session in current cwd");
503
+ return workflowResult(request.command, "completed", `Resumed session in ${fallbackCwd}`, undefined, "status");
483
504
  }
484
505
  }
485
506
  /**
@@ -0,0 +1,4 @@
1
+ import { type ToolDefinition } from "../startup-public.js";
2
+ export type PiWorktreeCwdReader = (sessionId: string, sessionFile: string, signal: AbortSignal) => Promise<string>;
3
+ /** Built-in tool definitions borrow the current attachment per call, without process-global chdir. */
4
+ export declare function createSessionWorktreeTools(startupCwd: string, resolveCwd: (signal?: AbortSignal) => Promise<string>): ToolDefinition[];
@@ -0,0 +1,26 @@
1
+ import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, defineTool, } from "../startup-public.js";
2
+ /** Built-in tool definitions borrow the current attachment per call, without process-global chdir. */
3
+ export function createSessionWorktreeTools(startupCwd, resolveCwd) {
4
+ function wrap(factory) {
5
+ const definition = factory(startupCwd);
6
+ return defineTool({
7
+ ...definition,
8
+ ...(definition.name === "bash" ? { promptGuidelines: [
9
+ ...(definition.promptGuidelines ?? []),
10
+ "For task worktrees, use a1 worktree enter <branch> or a1 worktree attach <absolute-path>; success confirms attachment and subsequent built-in tools use that root. a1 worktree detach preserves files. Coordination and recovery are internal; no follow-up inventory check is required.",
11
+ ] } : {}),
12
+ defaultActive: false,
13
+ async execute(...args) {
14
+ const [id, params, signal, onUpdate, context] = args;
15
+ const cwd = await resolveCwd(signal);
16
+ signal?.throwIfAborted();
17
+ return await factory(cwd).execute(id, params, signal, onUpdate, { ...context, cwd });
18
+ },
19
+ });
20
+ }
21
+ // Compatibility: defineTool preserves schema inference through the SDK's heterogeneous collection.
22
+ return [
23
+ wrap(createReadToolDefinition), wrap(createBashToolDefinition), wrap(createEditToolDefinition), wrap(createWriteToolDefinition),
24
+ wrap(createGrepToolDefinition), wrap(createFindToolDefinition), wrap(createLsToolDefinition),
25
+ ];
26
+ }