@timurproko/a1 0.2.7-dev.746 → 0.2.7-dev.767

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 (118) hide show
  1. package/bin/tab-launch-context.d.ts +6 -1
  2. package/bin/tab-launch-context.js +11 -2
  3. package/bin/ui.js +7 -3
  4. package/dist/app/session-shell/session-shell-root.d.ts +23 -0
  5. package/dist/app/session-shell/session-shell.js +112 -6
  6. package/dist/composition/owned-ui.d.ts +4 -1
  7. package/dist/composition/owned-ui.js +54 -8
  8. package/dist/contracts/owned-ui/session-backend.d.ts +1 -0
  9. package/dist/features/owned-ui/settings-app.js +8 -35
  10. package/dist/foundation/release/bootstrap.d.ts +1 -1
  11. package/dist/foundation/release/bootstrap.js +15 -4
  12. package/dist/foundation/release/dependency-certification.js +54 -19
  13. package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
  14. package/dist/foundation/release/progress-palette.generated.js +2 -2
  15. package/dist/foundation/resident-tabs/bridge-client.d.ts +7 -2
  16. package/dist/foundation/resident-tabs/bridge-client.js +58 -16
  17. package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +27 -0
  18. package/dist/foundation/resident-tabs/draft-checkpoint.js +145 -0
  19. package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +2 -0
  20. package/dist/foundation/resident-tabs/ensure-terminal-host.js +7 -1
  21. package/dist/foundation/resident-tabs/fault-injection.d.ts +8 -0
  22. package/dist/foundation/resident-tabs/fault-injection.js +26 -0
  23. package/dist/foundation/resident-tabs/index.d.ts +2 -1
  24. package/dist/foundation/resident-tabs/index.js +1 -1
  25. package/dist/foundation/resident-tabs/prompt-journal.d.ts +65 -0
  26. package/dist/foundation/resident-tabs/prompt-journal.js +214 -0
  27. package/dist/foundation/resident-tabs/recovery.d.ts +26 -0
  28. package/dist/foundation/resident-tabs/recovery.js +205 -0
  29. package/dist/foundation/resident-tabs/session-sync.d.ts +14 -0
  30. package/dist/foundation/resident-tabs/session-sync.js +37 -0
  31. package/dist/foundation/resident-tabs/settings.d.ts +9 -2
  32. package/dist/foundation/resident-tabs/settings.js +55 -7
  33. package/dist/foundation/session-writer-lock/index.d.ts +27 -0
  34. package/dist/foundation/session-writer-lock/index.js +197 -0
  35. package/dist/foundation/startup/startup-descriptor.js +2 -2
  36. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +4 -4
  37. package/dist/integrations/pi/components/resources/builtin-themes.js +4 -4
  38. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  39. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +17 -4
  40. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  41. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  42. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  43. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  44. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  45. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  46. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  47. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  48. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  49. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  50. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  51. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  52. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  53. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  54. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  55. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  56. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  57. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +2 -2
  58. package/dist/integrations/pi/components/upstream/components/status-indicator.js +3 -3
  59. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  60. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  61. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  62. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  63. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +0 -2
  64. package/dist/integrations/pi/components/upstream/components/tool-execution.js +41 -20
  65. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  66. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  67. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  68. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  69. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +2 -2
  70. package/dist/integrations/pi/components/upstream/history/editor-core.js +5 -5
  71. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  72. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  73. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  74. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  75. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  76. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  77. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  78. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  79. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  80. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  81. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +3 -0
  82. package/dist/integrations/pi/components/upstream/theme/theme.js +24 -1
  83. package/dist/integrations/pi/engine/adapter.js +1 -0
  84. package/dist/integrations/pi/engine/pi-settings-metadata.json +2 -2
  85. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  86. package/dist/integrations/pi/engine/runtime-integration.js +102 -29
  87. package/dist/integrations/pi/engine/session-runtime.d.ts +0 -6
  88. package/dist/integrations/pi/engine/session-runtime.js +8 -3
  89. package/dist/integrations/pi/engine/session-selection.d.ts +3 -0
  90. package/dist/integrations/pi/engine/session-selection.js +45 -9
  91. package/dist/integrations/pi/engine/workflow-contexts.js +10 -1
  92. package/dist/integrations/pi/startup-public.js +152 -222
  93. package/dist/integrations/pi/startup-public.manifest.json +266 -177
  94. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  95. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  96. package/dist/native/darwin-arm64/manifest.json +1 -1
  97. package/dist/native/linux-x64/manifest.json +1 -1
  98. package/dist/native/win32-x64/manifest.json +2 -2
  99. package/dist/native/win32-x64/process-guardian.exe +0 -0
  100. package/dist/runtime-payload-inventory.json +5349 -6035
  101. package/dist/ui/components/line-input.d.ts +1 -1
  102. package/dist/ui/components/prompt-input.d.ts +7 -5
  103. package/dist/ui/components/prompt-input.js +7 -7
  104. package/dist/ui/settings/declarations.d.ts +38 -2
  105. package/dist/ui/settings/declarations.js +29 -2
  106. package/dist/ui/settings/index.d.ts +4 -2
  107. package/dist/ui/settings/index.js +2 -1
  108. package/dist/ui/settings/manager.d.ts +9 -3
  109. package/dist/ui/settings/manager.js +81 -29
  110. package/dist/ui/settings/migrations.d.ts +6 -0
  111. package/dist/ui/settings/migrations.js +25 -0
  112. package/dist/ui/settings/profile-document.d.ts +26 -0
  113. package/dist/ui/settings/profile-document.js +164 -0
  114. package/dist/ui/settings/sections.js +4 -0
  115. package/docs/architecture/resident-tabs-requirements.md +1 -234
  116. package/docs/architecture/resident-tabs.md +30 -10
  117. package/docs/openspec-archive-automation.md +7 -7
  118. package/package.json +6 -4
@@ -1,8 +1,63 @@
1
+ import { closeSync, mkdirSync, openSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
1
3
  import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, resolveModelScopeWithDiagnostics, SessionManager, SettingsManager, } from "../startup-public.js";
2
4
  import { resolvePiProjectTrustPreflight, } from "./project-trust-preflight.js";
3
- import { openSelectedPiSession, resolveSessionArgumentPath } from "./session-selection.js";
5
+ import { openSelectedPiSession, resolveSessionArgumentPath, takeSelectedSessionWriter } from "./session-selection.js";
4
6
  import { markStartupPhase } from "../../../foundation/startup/startup-runtime.js";
5
7
  import { createWindowsNulCleanupExtension } from "./windows-filesystem-hygiene.js";
8
+ import { acquireSessionWriter, } from "../../../foundation/session-writer-lock/index.js";
9
+ class SessionWriterCoordinator {
10
+ #current;
11
+ prepare(manager) {
12
+ const selected = takeSelectedSessionWriter(manager);
13
+ if (selected !== undefined)
14
+ return { handle: selected, alreadyCurrent: false };
15
+ const path = manager.getSessionFile();
16
+ if (path === undefined)
17
+ return undefined;
18
+ if (this.#current?.path === path)
19
+ return { handle: this.#current, alreadyCurrent: true };
20
+ const handle = acquireSessionWriter(path);
21
+ try {
22
+ if (!handle.bound) {
23
+ const header = manager.getHeader();
24
+ if (header === null)
25
+ throw new Error("new session is missing its generated header");
26
+ mkdirSync(dirname(path), { recursive: true });
27
+ const descriptor = openSync(path, "wx", 0o600);
28
+ try {
29
+ handle.bind(path);
30
+ writeFileSync(descriptor, `${JSON.stringify(header)}\n`);
31
+ }
32
+ finally {
33
+ closeSync(descriptor);
34
+ }
35
+ manager.setSessionFile(path);
36
+ }
37
+ return { handle, alreadyCurrent: false };
38
+ }
39
+ catch (error) {
40
+ handle.release();
41
+ throw error;
42
+ }
43
+ }
44
+ activate(pending) {
45
+ if (pending === undefined || pending.alreadyCurrent)
46
+ return;
47
+ const previous = this.#current;
48
+ this.#current = pending.handle;
49
+ previous?.release();
50
+ }
51
+ abandon(pending) {
52
+ if (pending !== undefined && !pending.alreadyCurrent)
53
+ pending.handle.release();
54
+ }
55
+ release() {
56
+ this.#current?.release();
57
+ this.#current = undefined;
58
+ }
59
+ }
60
+ const runtimeSessionWriters = new WeakMap();
6
61
  /**
7
62
  * Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
8
63
  * into a scoped model list, keep the resolver's warnings (e.g. "No models match
@@ -69,40 +124,52 @@ export async function createPiRuntimeIntegration(options) {
69
124
  cwd: options.cwd, selection,
70
125
  ...(sessionDir === undefined ? {} : { sessionDir }),
71
126
  ...(options.sessionForkPrompt === undefined ? {} : { forkPrompt: options.sessionForkPrompt }),
127
+ guardWriter: true,
72
128
  });
129
+ const sessionWriters = new SessionWriterCoordinator();
73
130
  const createRuntime = async ({ cwd, sessionManager: targetSessionManager, sessionStartEvent, }) => {
74
- const { services, trust } = await createPiRuntimeServicesAfterTrust({
75
- cwd,
76
- agentDir: options.agentDir,
77
- ...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
78
- ...(options.preflightDependencies === undefined ? {} : { preflightDependencies: options.preflightDependencies }),
79
- ...(options.markStartupPhase === undefined ? {} : { markStartupPhase: options.markStartupPhase }),
80
- });
81
- const modelScope = await resolveConfiguredModelScope(services, options.signal);
82
- const hasExistingSession = targetSessionManager.buildSessionContext().messages.length > 0;
83
- const created = await createAgentSessionFromServices({
84
- services,
85
- sessionManager: targetSessionManager,
86
- ...(sessionStartEvent ? { sessionStartEvent } : {}),
87
- ...(modelScope.model && !hasExistingSession ? { model: modelScope.model } : {}),
88
- ...(modelScope.thinkingLevel && !hasExistingSession ? { thinkingLevel: modelScope.thinkingLevel } : {}),
89
- ...(modelScope.scopedModels.length > 0 ? { scopedModels: [...modelScope.scopedModels] } : {}),
90
- });
91
- await (options.markStartupPhase ?? defaultStartupPhaseMarker)("session-created");
92
- return {
93
- ...created,
94
- services,
95
- diagnostics: [
96
- ...(trust.diagnostic === null ? [] : [{ type: "warning", code: "project-trust", message: trust.diagnostic }]),
97
- ...modelScope.diagnostics,
98
- ],
99
- };
131
+ const pendingWriter = sessionWriters.prepare(targetSessionManager);
132
+ try {
133
+ const { services, trust } = await createPiRuntimeServicesAfterTrust({
134
+ cwd,
135
+ agentDir: options.agentDir,
136
+ ...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
137
+ ...(options.preflightDependencies === undefined ? {} : { preflightDependencies: options.preflightDependencies }),
138
+ ...(options.markStartupPhase === undefined ? {} : { markStartupPhase: options.markStartupPhase }),
139
+ });
140
+ const modelScope = await resolveConfiguredModelScope(services, options.signal);
141
+ const hasExistingSession = targetSessionManager.buildSessionContext().messages.length > 0;
142
+ const created = await createAgentSessionFromServices({
143
+ services,
144
+ sessionManager: targetSessionManager,
145
+ ...(sessionStartEvent ? { sessionStartEvent } : {}),
146
+ ...(modelScope.model && !hasExistingSession ? { model: modelScope.model } : {}),
147
+ ...(modelScope.thinkingLevel && !hasExistingSession ? { thinkingLevel: modelScope.thinkingLevel } : {}),
148
+ ...(modelScope.scopedModels.length > 0 ? { scopedModels: [...modelScope.scopedModels] } : {}),
149
+ });
150
+ sessionWriters.activate(pendingWriter);
151
+ await (options.markStartupPhase ?? defaultStartupPhaseMarker)("session-created");
152
+ return {
153
+ ...created,
154
+ services,
155
+ diagnostics: [
156
+ ...(trust.diagnostic === null ? [] : [{ type: "warning", code: "project-trust", message: trust.diagnostic }]),
157
+ ...modelScope.diagnostics,
158
+ ],
159
+ };
160
+ }
161
+ catch (error) {
162
+ sessionWriters.abandon(pendingWriter);
163
+ throw error;
164
+ }
100
165
  };
101
- return createAgentSessionRuntime(createRuntime, {
166
+ const runtime = await createAgentSessionRuntime(createRuntime, {
102
167
  cwd: sessionManager.getCwd(),
103
168
  agentDir: options.agentDir,
104
169
  sessionManager,
105
170
  });
171
+ runtimeSessionWriters.set(runtime, sessionWriters);
172
+ return runtime;
106
173
  }
107
174
  function defaultStartupPhaseMarker(phase) {
108
175
  return markStartupPhase(process.env, phase);
@@ -119,5 +186,11 @@ export async function replacePiRuntimeSession(runtime, replacement) {
119
186
  export async function disposePiRuntimeIntegration(runtime) {
120
187
  runtime.setRebindSession(undefined);
121
188
  runtime.setBeforeSessionInvalidate(undefined);
122
- await runtime.dispose();
189
+ try {
190
+ await runtime.dispose();
191
+ }
192
+ finally {
193
+ runtimeSessionWriters.get(runtime)?.release();
194
+ runtimeSessionWriters.delete(runtime);
195
+ }
123
196
  }
@@ -64,12 +64,6 @@ export interface PiEngineRuntimePorts {
64
64
  diagnostic(severity: OwnedUiDiagnostics["severity"], code: string, message: string, recoverable: boolean): void;
65
65
  emitView(): void;
66
66
  }
67
- /**
68
- * The pinned Pi runtime and the session bound to it: creation through the runtime factory, the
69
- * session generation that stamps and invalidates events, the subscription that forwards session
70
- * events, compaction progress observation, the queued-input integration, and disposal order. The
71
- * adapter derives its view state from the session it is handed and never touches these directly.
72
- */
73
67
  export declare class PiEngineRuntime {
74
68
  #private;
75
69
  constructor(options: PiEngineRuntimeOptions, ports: PiEngineRuntimePorts);
@@ -18,6 +18,9 @@ const GIT_BRANCH_MAX_BUFFER_BYTES = 4 * 1024;
18
18
  * events, compaction progress observation, the queued-input integration, and disposal order. The
19
19
  * adapter derives its view state from the session it is handed and never touches these directly.
20
20
  */
21
+ function hasSessionContent(path, session) {
22
+ return path !== null && existsSync(path) && session.messages.length > 0;
23
+ }
21
24
  export class PiEngineRuntime {
22
25
  #options;
23
26
  #ports;
@@ -154,14 +157,16 @@ export class PiEngineRuntime {
154
157
  return typeof value === "string" && value.length > 0 ? value : null;
155
158
  }
156
159
  currentSessionResumeMetadata() {
157
- const manager = this.#session?.sessionManager;
158
- if (manager === undefined
160
+ const session = this.#session;
161
+ const manager = session?.sessionManager;
162
+ if (session === undefined
163
+ || manager === undefined
159
164
  || typeof manager.isPersisted !== "function"
160
165
  || typeof manager.getSessionId !== "function"
161
166
  || typeof manager.getSessionDir !== "function"
162
167
  || typeof manager.usesDefaultSessionDir !== "function"
163
168
  || manager.isPersisted() !== true
164
- || !existsSync(this.currentSessionFile() ?? ""))
169
+ || !hasSessionContent(this.currentSessionFile(), session))
165
170
  return null;
166
171
  const sessionId = manager.getSessionId();
167
172
  const sessionDir = manager.getSessionDir();
@@ -1,4 +1,5 @@
1
1
  import { SessionManager, type SessionInfo } from "../startup-public.js";
2
+ import { type SessionWriterHandle } from "../../../foundation/session-writer-lock/index.js";
2
3
  export interface PiSessionSelection {
3
4
  readonly target: string;
4
5
  readonly sessionDir?: string;
@@ -16,11 +17,13 @@ interface SessionSelectionOptions {
16
17
  readonly selection: PiSessionSelection;
17
18
  readonly sessionDir?: string;
18
19
  readonly forkPrompt?: PiSessionForkPrompt;
20
+ readonly guardWriter?: boolean;
19
21
  /** Public API seam for deterministic lookup/open race and listing-order evidence. */
20
22
  readonly sessions?: Pick<typeof SessionManager, "list" | "open" | "forkFrom"> & {
21
23
  readonly listAll: (sessionDir?: string) => Promise<SessionInfo[]>;
22
24
  };
23
25
  }
26
+ export declare function takeSelectedSessionWriter(manager: SessionManager): SessionWriterHandle | undefined;
24
27
  /** Pinned Pi 0.84.2 main.ts policy, using SDK APIs rather than importing the CLI. */
25
28
  export declare function openSelectedPiSession(options: SessionSelectionOptions): Promise<SessionManager>;
26
29
  /** Resolve before switching effective cwd, including quoted home-relative paths. */
@@ -2,6 +2,7 @@ import { accessSync, constants, existsSync, realpathSync, statSync } from "node:
2
2
  import { homedir } from "node:os";
3
3
  import { resolve } from "node:path";
4
4
  import { SessionManager } from "../startup-public.js";
5
+ import { acquireExisting, acquireSessionWriter, SessionWriterConflict, } from "../../../foundation/session-writer-lock/index.js";
5
6
  /** Expected resume outcomes are concise CLI diagnostics, not engine crashes. */
6
7
  export class PiSessionSelectionError extends Error {
7
8
  exitCode;
@@ -11,13 +12,23 @@ export class PiSessionSelectionError extends Error {
11
12
  this.name = "PiSessionSelectionError";
12
13
  }
13
14
  }
15
+ const selectedSessionWriters = new WeakMap();
16
+ export function takeSelectedSessionWriter(manager) {
17
+ const writer = selectedSessionWriters.get(manager);
18
+ selectedSessionWriters.delete(manager);
19
+ return writer;
20
+ }
21
+ function holdSelectedWriter(manager, writer) {
22
+ selectedSessionWriters.set(manager, writer);
23
+ return manager;
24
+ }
14
25
  /** Pinned Pi 0.84.2 main.ts policy, using SDK APIs rather than importing the CLI. */
15
26
  export async function openSelectedPiSession(options) {
27
+ const sessions = options.sessions ?? SessionManager;
28
+ const cwd = resolve(options.cwd);
29
+ const directory = options.selection.sessionDir ?? options.sessionDir ?? process.env.PI_CODING_AGENT_SESSION_DIR;
30
+ const sessionDir = directory === undefined ? undefined : resolveSessionArgumentPath(directory, cwd);
16
31
  try {
17
- const sessions = options.sessions ?? SessionManager;
18
- const cwd = resolve(options.cwd);
19
- const directory = options.selection.sessionDir ?? options.sessionDir ?? process.env.PI_CODING_AGENT_SESSION_DIR;
20
- const sessionDir = directory === undefined ? undefined : resolveSessionArgumentPath(directory, cwd);
21
32
  const target = options.selection.target;
22
33
  let path;
23
34
  let global;
@@ -43,18 +54,43 @@ export async function openSelectedPiSession(options) {
43
54
  }
44
55
  assertResumeFile(path);
45
56
  assertCwd(cwd);
46
- return sessions.forkFrom(path, cwd, sessionDir);
57
+ const manager = sessions.forkFrom(path, cwd, sessionDir);
58
+ if (!options.guardWriter)
59
+ return manager;
60
+ const target = manager.getSessionFile();
61
+ if (target === undefined)
62
+ throw new PiSessionSelectionError("Fork did not create a session file");
63
+ return holdSelectedWriter(manager, acquireSessionWriter(target));
47
64
  }
48
65
  // Invariant: no await between the final guard and SDK open. A disappeared target
49
66
  // can otherwise produce a new in-memory header instead of an existing session.
50
- const manager = sessions.open(path, sessionDir);
51
- assertResumeFile(path);
52
- assertCwd(manager.getCwd());
53
- return manager;
67
+ const writer = options.guardWriter ? acquireExisting(path) : undefined;
68
+ try {
69
+ const manager = sessions.open(path, sessionDir);
70
+ assertResumeFile(path);
71
+ assertCwd(manager.getCwd());
72
+ return writer === undefined ? manager : holdSelectedWriter(manager, writer);
73
+ }
74
+ catch (error) {
75
+ writer?.release();
76
+ throw error;
77
+ }
54
78
  }
55
79
  catch (error) {
56
80
  if (error instanceof PiSessionSelectionError)
57
81
  throw error;
82
+ if (error instanceof SessionWriterConflict) {
83
+ if (options.forkPrompt && await options.forkPrompt({ sourceCwd: cwd })) {
84
+ const manager = sessions.forkFrom(error.path, cwd, sessionDir);
85
+ if (!options.guardWriter)
86
+ return manager;
87
+ const target = manager.getSessionFile();
88
+ if (target === undefined)
89
+ throw new PiSessionSelectionError("Fork did not create a session file");
90
+ return holdSelectedWriter(manager, acquireSessionWriter(target));
91
+ }
92
+ throw new PiSessionSelectionError("session is open in another A1 process");
93
+ }
58
94
  throw new PiSessionSelectionError(`Could not resume session: ${error instanceof Error ? error.message : String(error)}`);
59
95
  }
60
96
  }
@@ -3,6 +3,7 @@ import { dirname, resolve } from "node:path";
3
3
  import { dynamicObject, isRecord, requireCapability, stringProperty } from "./message-values.js";
4
4
  import { ProjectTrustStore, SessionManager } from "../startup-public.js";
5
5
  import { resolveConfiguredModelIds, scopedModelRecords, scopedModelReference, workflowOptions } from "./workflow-support.js";
6
+ import { acquireExisting } from "../../../foundation/session-writer-lock/index.js";
6
7
  /**
7
8
  * The selector contexts and option lists the owned UI's workflow dialogs read from pinned Pi:
8
9
  * model, scoped-model, session, tree, fork, project-trust, and provider-authentication state,
@@ -102,7 +103,15 @@ export class PiWorkflowContexts {
102
103
  const next = (nextName ?? "").trim();
103
104
  if (!next)
104
105
  return;
105
- SessionManager.open(sessionFilePath).appendSessionInfo(next);
106
+ const isCurrent = typeof currentSessionFilePath === "string"
107
+ && resolve(currentSessionFilePath) === resolve(sessionFilePath);
108
+ const writer = isCurrent ? undefined : acquireExisting(sessionFilePath);
109
+ try {
110
+ SessionManager.open(sessionFilePath).appendSessionInfo(next);
111
+ }
112
+ finally {
113
+ writer?.release();
114
+ }
106
115
  },
107
116
  };
108
117
  }