@timurproko/a1 0.2.7-dev.788 → 0.2.7-dev.792

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 (66) hide show
  1. package/dist/app/session-shell/session-shell-root.d.ts +0 -1
  2. package/dist/app/session-shell/session-shell-root.js +41 -15
  3. package/dist/app/session-shell/session-shell.js +5 -4
  4. package/dist/app/session-shell/session-viewport-controller.d.ts +2 -2
  5. package/dist/app/session-shell/session-viewport-controller.js +24 -12
  6. package/dist/composition/owned-ui.d.ts +1 -0
  7. package/dist/composition/owned-ui.js +27 -2
  8. package/dist/contracts/owned-ui/index.d.ts +1 -1
  9. package/dist/contracts/owned-ui/model.d.ts +3 -0
  10. package/dist/features/owned-ui/settings-app.js +9 -6
  11. package/dist/features/resident-tab/index.d.ts +3 -1
  12. package/dist/features/resident-tab/index.js +5 -1
  13. package/dist/features/resident-tab/shortcut-bindings-file.d.ts +8 -0
  14. package/dist/features/resident-tab/shortcut-bindings-file.js +40 -0
  15. package/dist/features/resident-tab/{shortcuts.d.ts → shortcut-declarations.d.ts} +5 -2
  16. package/dist/features/resident-tab/{shortcuts.js → shortcut-declarations.js} +5 -17
  17. package/dist/features/resident-tab/shortcut-projection.d.ts +29 -0
  18. package/dist/features/resident-tab/shortcut-projection.js +64 -0
  19. package/dist/foundation/release/bootstrap.js +1 -0
  20. package/dist/foundation/resident-tabs/bridge-client.d.ts +5 -0
  21. package/dist/foundation/resident-tabs/bridge-client.js +17 -1
  22. package/dist/foundation/resident-tabs/prompt-journal.d.ts +0 -2
  23. package/dist/foundation/resident-tabs/prompt-journal.js +0 -1
  24. package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -0
  25. package/dist/integrations/pi/components/owned-editor-ux.js +13 -0
  26. package/dist/integrations/pi/components/shell-editor-autocomplete.js +1 -0
  27. package/dist/integrations/pi/components/shell-presenters-info.d.ts +1 -0
  28. package/dist/integrations/pi/components/shell-presenters-info.js +10 -30
  29. package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
  30. package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
  31. package/dist/native/darwin-arm64/manifest.json +3 -2
  32. package/dist/native/darwin-arm64/terminal-host +0 -0
  33. package/dist/native/darwin-arm64/terminal-host.manifest.json +7 -6
  34. package/dist/native/darwin-x64/a1_session_lock.node +0 -0
  35. package/dist/native/darwin-x64/manifest.json +5 -4
  36. package/dist/native/darwin-x64/process-guardian +0 -0
  37. package/dist/native/darwin-x64/terminal-host +0 -0
  38. package/dist/native/darwin-x64/terminal-host.manifest.json +7 -6
  39. package/dist/native/linux-x64/a1_session_lock.node +0 -0
  40. package/dist/native/linux-x64/manifest.json +3 -2
  41. package/dist/native/linux-x64/terminal-host +0 -0
  42. package/dist/native/linux-x64/terminal-host.manifest.json +7 -6
  43. package/dist/native/win32-x64/a1_session_lock.node +0 -0
  44. package/dist/native/win32-x64/manifest.json +4 -3
  45. package/dist/native/win32-x64/process-guardian.exe +0 -0
  46. package/dist/native/win32-x64/terminal-host.exe +0 -0
  47. package/dist/native/win32-x64/terminal-host.manifest.json +6 -5
  48. package/dist/ui/components/index.d.ts +2 -2
  49. package/dist/ui/components/index.js +1 -1
  50. package/dist/ui/components/selection-copy.d.ts +2 -0
  51. package/dist/ui/components/selection-copy.js +13 -0
  52. package/dist/ui/components/submitted-prompt.d.ts +7 -4
  53. package/dist/ui/components/submitted-prompt.js +18 -10
  54. package/dist/ui/components/transcript-viewport.d.ts +10 -6
  55. package/dist/ui/components/transcript-viewport.js +28 -13
  56. package/dist/ui/settings/declarations.d.ts +13 -1
  57. package/dist/ui/settings/declarations.js +12 -1
  58. package/dist/ui/settings/migrations.js +7 -0
  59. package/docs/architecture/evidence/resident-tabs/macos-x64.md +1 -1
  60. package/docs/architecture/process-guardian-provenance.md +1 -1
  61. package/docs/architecture/project-structure.md +8 -0
  62. package/docs/architecture/resident-tabs.md +18 -2
  63. package/docs/architecture/terminal-host-provenance.md +1 -1
  64. package/docs/architecture/toolchain.md +1 -1
  65. package/docs/validation.md +3 -3
  66. package/package.json +1 -1
@@ -53,7 +53,6 @@ export interface OwnedUiShellEngineOptions {
53
53
  readonly sessionFile: string | null;
54
54
  readonly kind: "prompt" | "steer" | "follow-up";
55
55
  readonly text: string;
56
- readonly images?: readonly unknown[];
57
56
  }): Promise<{
58
57
  readonly id: string;
59
58
  }>;
@@ -96,9 +96,8 @@ export class OwnedUiSessionShellRoot {
96
96
  ...(handlers.promptImageLimit === undefined ? {} : { imageLimit: handlers.promptImageLimit }) });
97
97
  this.#submittedPromptComposer = this.#customViewport
98
98
  ? {
99
- layout: submittedPromptLayout,
100
- compose: (rows, width, source, style) => composeSubmittedPromptRows(rows, width, source, { ...style, prefix: text => promptArrow(text, piTheme()) })
101
- .map(row => nativeHyperlinkStyle(row, nativeTranscriptLinkColor)),
99
+ layout: (width, source) => submittedPromptLayout(width, source, this.#viewportController.config.timeFormat ?? "8:00 PM"),
100
+ compose: (rows, width, source, style) => composeSubmittedPromptRows(rows, width, source, { ...style, prefix: text => promptArrow(text, piTheme()) }, this.#viewportController.config.timeFormat ?? "8:00 PM").map(row => nativeHyperlinkStyle(row, nativeTranscriptLinkColor)),
102
101
  }
103
102
  : undefined;
104
103
  this.#cwd = cwd;
@@ -430,6 +429,23 @@ export class OwnedUiSessionShellRoot {
430
429
  const selectableDocumentRowCount = document.rows.length;
431
430
  const dockStartRow = height - dockRows.length + 1;
432
431
  const editorOffset = dock.editorOffset;
432
+ // Rationale: rendered chip tags are lossy presentation (truncated labels), so a frame drag over
433
+ // a draft holding chips stays visual-only; editor-owned word/line selection still copies the
434
+ // expanded chip text through its semantic route.
435
+ const draft = this.usesDefaultInputSurface() ? this.editor.getText() : "";
436
+ const promptCopyRows = draft.length > 0
437
+ && !draft.split("\n").some(line => this.#promptChips.atomicRanges(line).length > 0)
438
+ ? Math.max(0, dock.inputRows - 2)
439
+ : 0;
440
+ const copyableDockRows = [
441
+ ...Array.from({ length: promptCopyRows }, (_value, index) => ({
442
+ row: editorOffset + index + 1,
443
+ prompt: index === 0 ? "first" : "continuation",
444
+ })),
445
+ ...Array.from({ length: dock.footerRows }, (_value, index) => ({
446
+ row: dockRows.length - dock.footerRows + index,
447
+ })),
448
+ ];
433
449
  this.#viewportController.setInputSurfaceFrame(this.usesDefaultInputSurface() ? undefined : {
434
450
  component: this.#inputSurface,
435
451
  columnStart: 1,
@@ -458,7 +474,7 @@ export class OwnedUiSessionShellRoot {
458
474
  && snapshot.inputSurface === inputSurface
459
475
  && snapshot.viewportRevision === viewportRevision
460
476
  && snapshot.selectionRevision === selectionRevision
461
- ? this.#viewportController.composeDockOnly(dockRows, width, height, dock.footerRows)
477
+ ? this.#viewportController.composeDockOnly(dockRows, width, height, copyableDockRows)
462
478
  : null;
463
479
  if (frame === null) {
464
480
  frame = this.#viewportController.compose({
@@ -474,7 +490,7 @@ export class OwnedUiSessionShellRoot {
474
490
  ...(document.liveTailStartRow === undefined ? {} : { liveTailStartRow: document.liveTailStartRow }),
475
491
  bottomAlignedTailRowCount: steeringRows.length + statusRows.length,
476
492
  dockRows,
477
- explicitCopyDockRows: dock.footerRows,
493
+ copyableDockRows,
478
494
  promptAnchors: document.promptAnchors,
479
495
  width,
480
496
  height,
@@ -541,8 +557,11 @@ export class OwnedUiSessionShellRoot {
541
557
  return this.#viewportController.hasSelection || this.editor.hasSelection();
542
558
  }
543
559
  setViewportConfig(config) {
544
- if (this.#viewportController.setConfig(config))
560
+ if (this.#viewportController.setConfig(config)) {
561
+ this.#renderedRows.clear();
545
562
  this.#documentLayouts.clear();
563
+ this.#visibleViewportSnapshot = undefined;
564
+ }
546
565
  }
547
566
  setFullscreenCopyOnSelect(enabled) {
548
567
  this.#viewportController.setCopyOnSelect(enabled);
@@ -751,11 +770,13 @@ export class OwnedUiSessionShellRoot {
751
770
  }
752
771
  rows.push(...blockRows);
753
772
  if (promptLike && blockRows[0] !== undefined) {
773
+ const source = pinnedPromptSource(block, blockRows[0], blockWidth, this.#viewportController.config.timeFormat);
754
774
  promptAnchors.push({
755
775
  id: block.id,
756
776
  firstRow,
757
777
  lastRow: Math.max(firstRow, rows.length - 1),
758
- sourceRow: pinnedPromptSourceRow(block, blockRows[0], blockWidth),
778
+ sourceRow: source.row,
779
+ copySuffixWidth: source.copySuffixWidth,
759
780
  prominentSourceRow: blockRows[0],
760
781
  });
761
782
  }
@@ -1322,20 +1343,25 @@ function withoutTerminalBackground(text) {
1322
1343
  return text.replace(TERMINAL_BACKGROUND, "");
1323
1344
  }
1324
1345
  /** Preserve the stable metadata-grey foreground for every pinned timestamp state. */
1325
- function pinnedPromptSourceRow(block, sourceRow, width) {
1346
+ function pinnedPromptSource(block, sourceRow, width, timeFormat) {
1347
+ const unchanged = { row: sourceRow, copySuffixWidth: 0 };
1326
1348
  if (typeof block.payload !== "object" || block.payload === null)
1327
- return sourceRow;
1349
+ return unchanged;
1328
1350
  const value = block.payload.timestamp;
1329
1351
  if (typeof value !== "number" || !Number.isFinite(value))
1330
- return sourceRow;
1331
- const timestamp = formatSubmittedPromptTime(value);
1332
- if (timestamp === null || submittedPromptLayout(width, value).timestamp === null)
1333
- return sourceRow;
1352
+ return unchanged;
1353
+ const format = timeFormat ?? "8:00 PM";
1354
+ const timestamp = formatSubmittedPromptTime(value, format);
1355
+ if (timestamp === null || submittedPromptLayout(width, value, format).timestamp === null)
1356
+ return unchanged;
1334
1357
  const rowWidth = displayWidth(sourceRow);
1335
1358
  const timestampWidth = displayWidth(timestamp);
1336
1359
  if (rowWidth < timestampWidth)
1337
- return sourceRow;
1338
- return overlaySpan(sourceRow, rowWidth - timestampWidth, rowWidth, paintPiSubmittedPromptTimestamp(timestamp));
1360
+ return unchanged;
1361
+ return {
1362
+ row: overlaySpan(sourceRow, rowWidth - timestampWidth, rowWidth, paintPiSubmittedPromptTimestamp(timestamp)),
1363
+ copySuffixWidth: timestampWidth,
1364
+ };
1339
1365
  }
1340
1366
  /**
1341
1367
  * The pinned-profile screen: a followed transcript scroll above the dock. The runtime mounts one such layout,
@@ -270,7 +270,7 @@ export class OwnedUiSessionPresenter {
270
270
  // Invariant: the presenter owns its root (theme subscription and helper clients) and disposes it once the
271
271
  // terminal no longer renders it.
272
272
  constructed.push(() => this.root.dispose());
273
- this.#restoreResidentInput();
273
+ this.#restoreResidentInput(options.history?.imageSidecar);
274
274
  const initialPiSettings = this.backend.settings.snapshot();
275
275
  this.#programStatus = new ProgramStatusReporter(status => this.#terminal.setProgramStatus(status), () => this.view().status.footer?.sessionName ?? undefined);
276
276
  this.#reloadPresentation = reloadPresentation;
@@ -802,7 +802,6 @@ export class OwnedUiSessionPresenter {
802
802
  sessionFile: this.#currentSessionFile(),
803
803
  kind: type,
804
804
  text: input,
805
- ...(prepared.images.length === 0 ? {} : { images: prepared.images }),
806
805
  });
807
806
  }
808
807
  catch {
@@ -1213,11 +1212,13 @@ export class OwnedUiSessionPresenter {
1213
1212
  this.#resolveStoppedLifecycle();
1214
1213
  }
1215
1214
  }
1216
- #restoreResidentInput() {
1215
+ #restoreResidentInput(imageSidecar) {
1217
1216
  const recovered = this.#residentTabOptions?.recovered;
1218
1217
  if (recovered === undefined)
1219
1218
  return;
1220
- const prompts = recovered.prompts.map(prompt => prompt.text).filter(Boolean);
1219
+ const prompts = recovered.prompts
1220
+ .map(prompt => this.root.rehydrateHistoryText(prompt.text, id => imageSidecar?.readAttachment(id) ?? null))
1221
+ .filter(Boolean);
1221
1222
  if (prompts.length > 0) {
1222
1223
  this.root.editor.setText(prompts[0]);
1223
1224
  if (recovered.draft)
@@ -1,7 +1,7 @@
1
1
  import type { PiTuiPointerSurface } from "../../integrations/pi/tui-runtime/contracts.js";
2
2
  import type { OwnedUiViewportSettings } from "../../contracts/owned-ui/index.js";
3
3
  import { type TranscriptViewportFrame, type TranscriptViewportFrameInput } from "../../ui/components/transcript-viewport.js";
4
- import type { SelectionCopySnapshot } from "../../ui/components/selection-copy.js";
4
+ import { type SelectionCopySnapshot } from "../../ui/components/selection-copy.js";
5
5
  import type { PiShellEditorPort } from "../../integrations/pi/components/shell-shared-facade.js";
6
6
  import type { PasteEvent } from "./paste-protocol.js";
7
7
  export interface SessionViewportControllerOptions {
@@ -64,7 +64,7 @@ export declare class SessionViewportController {
64
64
  setOverlaySurfaces(surfaces: readonly PiTuiPointerSurface[] | null): void;
65
65
  get inputGeometryReady(): boolean;
66
66
  compose(input: Omit<TranscriptViewportFrameInput, "pointerPosition">): TranscriptViewportFrame;
67
- composeDockOnly(dockRows: readonly string[], width: number, height: number, explicitCopyDockRows?: number): TranscriptViewportFrame | null;
67
+ composeDockOnly(dockRows: readonly string[], width: number, height: number, copyableDockRows?: TranscriptViewportFrameInput["copyableDockRows"]): TranscriptViewportFrame | null;
68
68
  /** Returns true when document wrapping may have changed. */
69
69
  setConfig(config: OwnedUiViewportSettings): boolean;
70
70
  resumeFollowing(): void;
@@ -3,6 +3,7 @@ import { TranscriptViewport } from "../../ui/components/transcript-viewport.js";
3
3
  import { readVisibleHyperlinks } from "../../ui/components/visible-hyperlinks.js";
4
4
  import { routeMouseInput } from "../../ui/components/mouse.js";
5
5
  import { scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows } from "../../ui/components/scrollbar.js";
6
+ import { literalSelectionCopySnapshot } from "../../ui/components/selection-copy.js";
6
7
  function withinTailControl(region, column, row) {
7
8
  return row >= region.rowStart && row <= region.rowEnd && column >= region.columnStart && column <= region.columnEnd;
8
9
  }
@@ -22,6 +23,7 @@ export class SessionViewportController {
22
23
  #pasteDiagnosticRequest = 0;
23
24
  #viewport = new TranscriptViewport();
24
25
  #config = {
26
+ timeFormat: "8:00 PM",
25
27
  scrollbarAppearance: "auto",
26
28
  scrollbarStyle: "thin",
27
29
  scrollbarSpeed: "normal",
@@ -165,16 +167,18 @@ export class SessionViewportController {
165
167
  }
166
168
  return frame;
167
169
  }
168
- composeDockOnly(dockRows, width, height, explicitCopyDockRows = 0) {
169
- return this.#viewport.composeDockOnly(dockRows, width, height, explicitCopyDockRows);
170
+ composeDockOnly(dockRows, width, height, copyableDockRows = []) {
171
+ return this.#viewport.composeDockOnly(dockRows, width, height, copyableDockRows);
170
172
  }
171
173
  /** Returns true when document wrapping may have changed. */
172
174
  setConfig(config) {
173
- const appearanceChanged = config.scrollbarAppearance !== this.#config.scrollbarAppearance;
174
- this.#config = config;
175
- this.#viewport.setConfig(config);
175
+ const normalized = { ...config, timeFormat: config.timeFormat ?? "8:00 PM" };
176
+ const layoutChanged = normalized.scrollbarAppearance !== this.#config.scrollbarAppearance
177
+ || normalized.timeFormat !== this.#config.timeFormat;
178
+ this.#config = normalized;
179
+ this.#viewport.setConfig(normalized);
176
180
  this.#requestRender();
177
- return appearanceChanged;
181
+ return layoutChanged;
178
182
  }
179
183
  resumeFollowing() {
180
184
  if (!this.#enabled)
@@ -302,12 +306,10 @@ export class SessionViewportController {
302
306
  });
303
307
  }
304
308
  if (data === "\u0003") {
305
- if (editorActive && this.#editor.hasSelection()) {
306
- if (this.#viewport.clearSelection())
307
- this.#requestRender();
308
- }
309
- else if (this.#viewport.hasSelection) {
310
- const copySelection = this.#viewport.captureSelectedText("explicit");
309
+ // Invariant: the newest complete-frame gesture owns explicit copy even if an
310
+ // older editor word/line selection remains painted beneath it.
311
+ if (this.#viewport.hasSelection) {
312
+ const copySelection = this.#viewport.captureSelectedText();
311
313
  this.#viewport.clearSelection();
312
314
  this.#stopSelectionAutoScroll();
313
315
  this.#requestRender();
@@ -315,6 +317,10 @@ export class SessionViewportController {
315
317
  ? { data: "", consumed: true }
316
318
  : { data: "", consumed: true, copySelection };
317
319
  }
320
+ if (editorActive && this.#editor.hasSelection()) {
321
+ if (this.#viewport.clearSelection())
322
+ this.#requestRender();
323
+ }
318
324
  }
319
325
  const previousPrompt = SHIFT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
320
326
  if (editorActive && allowWheel && (previousPrompt || SHIFT_DOWN_INPUTS.has(data))) {
@@ -541,6 +547,12 @@ export class SessionViewportController {
541
547
  const editorRow = pendingEditorClick.row - this.#editorPointerFrame.rowStart + 1;
542
548
  this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
543
549
  this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
550
+ if (this.#copyOnSelect) {
551
+ const selected = this.#editor.selectedText?.();
552
+ if (selected !== undefined) {
553
+ completedCopy = literalSelectionCopySnapshot(selected, this.#viewport.selectionRevision) ?? undefined;
554
+ }
555
+ }
544
556
  }
545
557
  forceRepaint = true;
546
558
  repaint = true;
@@ -88,3 +88,4 @@ export declare function composeOwnedUi(options?: OwnedUiCompositionOptions): Pro
88
88
  export declare function bindResidentTabBridge(tabBridge: TabBridgeClient, backend: Pick<ComposedSessionBackend, "identity" | "session" | "workflows" | "generateResidentTabName">, recovery?: Pick<ResidentTabRecovery, "settled"> | null, options?: {
89
89
  readonly autoName?: boolean;
90
90
  }): Promise<() => void>;
91
+ export declare function correlateResidentTabSubmission(recovery: Pick<ResidentTabRecovery, "correlated" | "settled">, backend: Pick<ComposedSessionBackend, "identity" | "session">, submissionId: string, entryId: string): Promise<void>;
@@ -12,6 +12,8 @@ import { createPiSessionPresenters } from "../integrations/pi/session-presenters
12
12
  import { ClipboardDiagnosticCapture } from "../app/session-shell/clipboard-diagnostics.js";
13
13
  import { createOwnedUiClipboardServices } from "../app/session-shell/clipboard-services.js";
14
14
  import { createPiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
15
+ import { projectResidentTabShortcuts } from "../features/resident-tab/shortcut-projection.js";
16
+ import { readShortcutBindingsFile } from "../features/resident-tab/shortcut-bindings-file.js";
15
17
  import { OwnedUiSessionPresenter, OwnedUiTerminalHost, sessionTerminalHostOptions, } from "../app/session-shell/session-shell.js";
16
18
  import { OwnedSettingsManager } from "../ui/settings/manager.js";
17
19
  import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentation-adapter.js";
@@ -254,7 +256,18 @@ export async function composeOwnedUi(options = {}) {
254
256
  ? new ClipboardDiagnosticCapture(clipboardDestination) : null;
255
257
  // Invariant: process-wide services are created once here and borrowed by every session shell. The
256
258
  // keybinding registry is applied before any presenter is built, so none of them reads Pi's defaults.
257
- const keybindings = createPiKeybindingsHost({ profile: ownedSurfaces ? "a1" : "pi", agentDir: backend.identity.agentDir });
259
+ const keybindingsHost = createPiKeybindingsHost({ profile: ownedSurfaces ? "a1" : "pi", agentDir: backend.identity.agentDir });
260
+ // Invariant: the resident host dispatches and labels the tab shortcuts this child projects
261
+ // from its effective declarations, so a customization reload republishes the projection.
262
+ const publishTabShortcuts = () => tabBridge?.shortcuts(projectResidentTabShortcuts({
263
+ configured: readShortcutBindingsFile(backend.identity.agentDir),
264
+ reserved: keybindingsHost.manager.getEffectiveConfig(),
265
+ }).assignments);
266
+ const keybindings = tabBridge === null ? keybindingsHost : {
267
+ ...keybindingsHost,
268
+ reload: () => { keybindingsHost.reload(); publishTabShortcuts(); },
269
+ };
270
+ publishTabShortcuts();
258
271
  const clipboardServices = ownedSurfaces ? createOwnedUiClipboardServices() : null;
259
272
  let createdTerminalHost;
260
273
  let terminalHost;
@@ -284,7 +297,7 @@ export async function composeOwnedUi(options = {}) {
284
297
  stallNoticeSeconds: 300,
285
298
  admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
286
299
  dispatched: () => tabRecovery.dispatched(),
287
- correlated: (submissionId, entryId) => tabRecovery.correlated(submissionId, entryId),
300
+ correlated: (submissionId, entryId) => correlateResidentTabSubmission(tabRecovery, backend, submissionId, entryId),
288
301
  checkpoint: text => tabRecovery.checkpoint(text),
289
302
  gracefulStop: sessionFile => tabRecovery.gracefulStop(sessionFile),
290
303
  ...(recoveredTabInput === undefined ? {} : { recovered: recoveredTabInput }),
@@ -463,6 +476,9 @@ export async function bindResidentTabBridge(tabBridge, backend, recovery = null,
463
476
  if (command.type === "rename") {
464
477
  void backend.workflows.executeWorkflow({ command: "name", argument: command.name }).catch(() => { });
465
478
  }
479
+ else if (command.type === "route") {
480
+ void backend.workflows.executeWorkflow({ command: command.route, argument: "" }).catch(() => { });
481
+ }
466
482
  else if (command.type === "stop") {
467
483
  void backend.session.dispose().catch(() => { });
468
484
  }
@@ -502,6 +518,14 @@ export async function bindResidentTabBridge(tabBridge, backend, recovery = null,
502
518
  unsubscribeEvents();
503
519
  };
504
520
  }
521
+ export async function correlateResidentTabSubmission(recovery, backend, submissionId, entryId) {
522
+ await recovery.correlated(submissionId, entryId);
523
+ // Concurrency: a normal prompt command can resolve after the ready transition already fired.
524
+ // Re-read lifecycle only after correlation is active; a concurrent ready observer and this path
525
+ // safely race because recovery consumes the active entry once and retirement is idempotent.
526
+ if (backend.session.view().lifecycle !== "busy")
527
+ await recovery.settled(currentBackendSessionFile(backend));
528
+ }
505
529
  function currentBackendSessionFile(backend) {
506
530
  const direct = backend.identity.currentSessionFile?.();
507
531
  if (direct !== undefined && direct !== null)
@@ -523,6 +547,7 @@ function quitOutroSettingsSnapshot(settings) {
523
547
  }
524
548
  function viewportSettingsSnapshot(settings) {
525
549
  return {
550
+ timeFormat: settings.value("timeFormat"),
526
551
  scrollbarAppearance: settings.value("scrollbarAppearance"),
527
552
  scrollbarStyle: settings.value("scrollbarStyle"),
528
553
  scrollbarSpeed: settings.value("scrollbarSpeed"),
@@ -3,7 +3,7 @@ export type UiAccentColor = "purple" | "blue" | "cyan" | "green" | "orange" | "p
3
3
  export { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiExtensionUiPort, } from "./extension-ui.js";
4
4
  export type { OwnedUiExtensionAutocompleteItem, OwnedUiExtensionAutocompleteProvider, OwnedUiExtensionAutocompleteProviderFactory, OwnedUiExtensionAutocompleteSuggestions, OwnedUiExtensionComponent, OwnedUiExtensionComponentFactory, OwnedUiExtensionComponentFactoryContext, OwnedUiExtensionCustomFactory, OwnedUiExtensionCustomOptions, OwnedUiExtensionDialogOptions, OwnedUiExtensionEditorComponent, OwnedUiExtensionEditorFactory, OwnedUiExtensionEditorThemePort, OwnedUiExtensionEntryRenderInput, OwnedUiExtensionFooterDataPort, OwnedUiExtensionFooterFactory, OwnedUiExtensionInputListener, OwnedUiExtensionKeybindingsPort, OwnedUiExtensionMessageRenderInput, OwnedUiExtensionNotificationSeverity, OwnedUiExtensionOverlayAnchor, OwnedUiExtensionOverlayHandle, OwnedUiExtensionOverlayMargin, OwnedUiExtensionOverlayOptions, OwnedUiExtensionRenderCallback, OwnedUiExtensionRendererPort, OwnedUiExtensionRuntimePort, OwnedUiExtensionSelectListThemePort, OwnedUiExtensionSizeValue, OwnedUiExtensionTerminalPort, OwnedUiExtensionThemeBackground, OwnedUiExtensionThemeColor, OwnedUiExtensionThemeInfo, OwnedUiExtensionThemePort, OwnedUiExtensionToolRenderContext, OwnedUiExtensionUiCallback, OwnedUiExtensionUiPort, OwnedUiExtensionUiProperty, OwnedUiExtensionWidgetOptions, OwnedUiExtensionWidgetPlacement, OwnedUiExtensionWorkingIndicatorOptions, } from "./extension-ui.js";
5
5
  export { OWNED_UI_CONTRACT_VERSION } from "./model.js";
6
- export type { OwnedUiBackgroundSettingsPort, OwnedUiBackgroundStyle, OwnedUiBlockStatus, OwnedUiCommand, OwnedUiCommandOutcome, OwnedUiCorrelationId, OwnedUiCustomization, OwnedUiDiagnostics, OwnedUiDialog, OwnedUiEditorState, OwnedUiEntityId, OwnedUiEvent, OwnedUiFooterView, OwnedUiImageAttachment, OwnedUiLifecycle, OwnedUiModelInfo, OwnedUiOverlay, OwnedUiPromptSuggestionGeneratorPort, OwnedUiPromptSuggestionIdentity, OwnedUiPromptSuggestionReasoning, OwnedUiPromptSuggestionRequest, OwnedUiPromptSuggestionResult, OwnedUiPromptSuggestionState, OwnedUiQuitOutroSettings, OwnedUiQuitOutroSettingsPort, OwnedUiSessionId, OwnedUiSessionViewModel, OwnedUiSeverity, OwnedUiSlotId, OwnedUiSnapshot, OwnedUiStatusView, OwnedUiTerminalSurface, OwnedUiThinkingLevel, OwnedUiToolRendering, OwnedUiToolResultPart, OwnedUiToolState, OwnedUiTranscriptBlock, OwnedUiTranscriptBlockKind, OwnedUiTranscriptImageReference, OwnedUiUsageView, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, } from "./model.js";
6
+ export type { OwnedUiBackgroundSettingsPort, OwnedUiBackgroundStyle, OwnedUiBlockStatus, OwnedUiCommand, OwnedUiCommandOutcome, OwnedUiCorrelationId, OwnedUiCustomization, OwnedUiDiagnostics, OwnedUiDialog, OwnedUiEditorState, OwnedUiEntityId, OwnedUiEvent, OwnedUiFooterView, OwnedUiImageAttachment, OwnedUiLifecycle, OwnedUiModelInfo, OwnedUiOverlay, OwnedUiPromptSuggestionGeneratorPort, OwnedUiPromptSuggestionIdentity, OwnedUiPromptSuggestionReasoning, OwnedUiPromptSuggestionRequest, OwnedUiPromptSuggestionResult, OwnedUiPromptSuggestionState, OwnedUiQuitOutroSettings, OwnedUiQuitOutroSettingsPort, OwnedUiSessionId, OwnedUiSessionViewModel, OwnedUiSeverity, OwnedUiSlotId, OwnedUiSnapshot, OwnedUiStatusView, OwnedUiTerminalSurface, OwnedUiThinkingLevel, OwnedUiTimeFormat, OwnedUiToolRendering, OwnedUiToolResultPart, OwnedUiToolState, OwnedUiTranscriptBlock, OwnedUiTranscriptBlockKind, OwnedUiTranscriptImageReference, OwnedUiUsageView, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, } from "./model.js";
7
7
  export type { OwnedUiExtensionPort, OwnedUiSessionBackend, OwnedUiSessionCatalogPort, OwnedUiSessionFactory, OwnedUiSessionIdentityPort, OwnedUiSessionPort, OwnedUiSessionRequest, OwnedUiSessionSettingsPort, OwnedUiWorkflowPort, } from "./session-backend.js";
8
8
  export type { OwnedUiDialogHost, OwnedUiInputSurface, OwnedUiPresenterHost, OwnedUiSessionPresenter, OwnedUiSessionPresenters, OwnedUiShortcutDescription, OwnedUiTerminalHost, OwnedUiTranscriptRendererPort, } from "./session-presenters.js";
9
9
  export { OWNED_UI_HIDDEN_COMMAND_NAMES, OWNED_UI_MODELS_COMMAND_NAME, OWNED_UI_WORKFLOW_COMMAND_NAMES, } from "./session-workflows.js";
@@ -53,7 +53,10 @@ export interface OwnedUiPromptSuggestionGeneratorPort {
53
53
  generate(request: OwnedUiPromptSuggestionRequest): Promise<OwnedUiPromptSuggestionResult>;
54
54
  }
55
55
  export type OwnedUiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
56
+ export type OwnedUiTimeFormat = "20:00" | "8:00 PM";
56
57
  export interface OwnedUiViewportSettings {
58
+ /** Omitted by older SDK callers; bare A1 composition always supplies the owned default. */
59
+ readonly timeFormat?: OwnedUiTimeFormat;
57
60
  readonly scrollbarAppearance: "auto" | "always" | "hidden";
58
61
  readonly scrollbarStyle: "thin" | "thick";
59
62
  /** normal is baseline; fast is 2x normal; high is normal plus fast. */
@@ -1,5 +1,5 @@
1
1
  import { DIALOG_BACK_SHORTCUT_HINT, DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
2
- import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, numericValues, RAIL_COLUMNS, renderDialogPanel, renderSteppedDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderShortcutHintsWithClose, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
2
+ import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, numericValues, RAIL_COLUMNS, renderDialogPanel, renderSteppedDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderShortcutHintsWithClose, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, formatLocalTime, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
3
3
  import { SETTINGS_APP_ID } from "./settings-route.js";
4
4
  export { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
5
5
  const SCOPE = SETTINGS_APP_ID;
@@ -921,7 +921,7 @@ export class SettingsApp {
921
921
  ? CONFIGURE
922
922
  : shown === null
923
923
  ? describeRaw(entry.rawValue)
924
- : displayValue(shown);
924
+ : displaySettingValue(entry, shown);
925
925
  const range = rangeOf(entry);
926
926
  return {
927
927
  key,
@@ -938,11 +938,11 @@ export class SettingsApp {
938
938
  return lines;
939
939
  }
940
940
  const state = {
941
- choices: menu.choices.map(choice => displayValue(choice)),
941
+ choices: menu.choices.map(choice => displaySettingValue(menu.entry, choice)),
942
942
  ...(menu.entry.id === "accentColor" ? {
943
943
  previews: menu.choices.map(choice => theme.accentPreview?.(String(choice), "■") ?? ""),
944
944
  } : {}),
945
- current: this.#shownValue(menu.entry) === null ? null : displayValue(this.#shownValue(menu.entry)),
945
+ current: this.#shownValue(menu.entry) === null ? null : displaySettingValue(menu.entry, this.#shownValue(menu.entry)),
946
946
  index: menu.index,
947
947
  };
948
948
  const frame = valueMenuFrame(state, {
@@ -1084,8 +1084,11 @@ function labelOf(entry) {
1084
1084
  function rangeOf(entry) {
1085
1085
  return { minimum: entry.minimum, maximum: entry.maximum, values: numericValues(entry.choices) };
1086
1086
  }
1087
- /** Booleans read as yes and no; everything else prints as itself. */
1088
- function displayValue(value) {
1087
+ /** Booleans read as yes and no; time-format choices preview the current local time. */
1088
+ function displaySettingValue(entry, value) {
1089
+ if (entry.backend === "a1" && entry.id === "timeFormat" && (value === "20:00" || value === "8:00 PM")) {
1090
+ return formatLocalTime(new Date(), value) ?? value;
1091
+ }
1089
1092
  if (typeof value === "boolean")
1090
1093
  return value ? "yes" : "no";
1091
1094
  return String(value);
@@ -1,3 +1,5 @@
1
1
  export { AUTO_NAME_MAX_LENGTH, AUTO_NAME_TIMEOUT_MS, AUTO_NAME_TOKEN_BUDGETS, fallbackAutoName, generateAutoName, normalizeAutoName, type AutoNameCompletion, } from "./auto-name.js";
2
- export { RESIDENT_TAB_SHORTCUTS, residentTabShortcutRegistry, type ResidentTabAction, type ResidentTabShortcut, } from "./shortcuts.js";
2
+ export { RESIDENT_TAB_SHORTCUTS, type ResidentTabAction, type ResidentTabShortcut, } from "./shortcut-declarations.js";
3
+ export { projectResidentTabShortcuts, RESIDENT_TAB_GLOBAL_SHORTCUTS, type ResidentTabGlobalAction, type ResidentTabShortcutConflict, type ResidentTabShortcutProjection, type ShortcutBindings, } from "./shortcut-projection.js";
4
+ export { readShortcutBindingsFile } from "./shortcut-bindings-file.js";
3
5
  export { deriveResidentTabStatus, type ResidentTabDerivedStatus, type ResidentTabFacts, } from "./status-deriver.js";
@@ -1,3 +1,7 @@
1
1
  export { AUTO_NAME_MAX_LENGTH, AUTO_NAME_TIMEOUT_MS, AUTO_NAME_TOKEN_BUDGETS, fallbackAutoName, generateAutoName, normalizeAutoName, } from "./auto-name.js";
2
- export { RESIDENT_TAB_SHORTCUTS, residentTabShortcutRegistry, } from "./shortcuts.js";
2
+ // Performance: the registry builder lives in `shortcuts.ts`, outside this barrel, so startup
3
+ // presentation can read the declarations without loading the component registry.
4
+ export { RESIDENT_TAB_SHORTCUTS, } from "./shortcut-declarations.js";
5
+ export { projectResidentTabShortcuts, RESIDENT_TAB_GLOBAL_SHORTCUTS, } from "./shortcut-projection.js";
6
+ export { readShortcutBindingsFile } from "./shortcut-bindings-file.js";
3
7
  export { deriveResidentTabStatus, } from "./status-deriver.js";
@@ -0,0 +1,8 @@
1
+ import type { ShortcutBindings } from "./shortcut-projection.js";
2
+ /** Largest `keybindings.json` the projection reads; a larger file counts as absent. */
3
+ export declare const MAX_SHORTCUT_BINDINGS_BYTES: number;
4
+ /**
5
+ * Reads the user's `keybindings.json` as shortcut bindings, keeping `null` entries so an
6
+ * explicit unbind survives, and treating any unreadable or malformed file as empty.
7
+ */
8
+ export declare function readShortcutBindingsFile(agentDir: string): ShortcutBindings;
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** Largest `keybindings.json` the projection reads; a larger file counts as absent. */
4
+ export const MAX_SHORTCUT_BINDINGS_BYTES = 256 * 1024;
5
+ /**
6
+ * Reads the user's `keybindings.json` as shortcut bindings, keeping `null` entries so an
7
+ * explicit unbind survives, and treating any unreadable or malformed file as empty.
8
+ */
9
+ export function readShortcutBindingsFile(agentDir) {
10
+ let text;
11
+ try {
12
+ text = readFileSync(join(agentDir, "keybindings.json"), "utf8");
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ if (text.length > MAX_SHORTCUT_BINDINGS_BYTES)
18
+ return {};
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(text.replace(/^/u, ""));
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
27
+ return {};
28
+ const bindings = {};
29
+ for (const [action, value] of Object.entries(parsed)) {
30
+ if (action === "$schema")
31
+ continue;
32
+ if (value === null || typeof value === "string") {
33
+ bindings[action] = value;
34
+ continue;
35
+ }
36
+ if (Array.isArray(value))
37
+ bindings[action] = value.filter((key) => typeof key === "string");
38
+ }
39
+ return bindings;
40
+ }
@@ -1,4 +1,3 @@
1
- import { ShortcutRegistry } from "../../ui/components/index.js";
2
1
  export type ResidentTabAction = "tabs.new" | "tabs.close" | "tabs.rename" | "tabs.retry" | "tabs.fresh" | "tabs.next" | "tabs.previous" | "tabs.moveRight" | "tabs.moveLeft" | `tabs.jump${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}`;
3
2
  export interface ResidentTabShortcut {
4
3
  readonly id: ResidentTabAction;
@@ -6,5 +5,9 @@ export interface ResidentTabShortcut {
6
5
  readonly scope: "global" | "failed-banner";
7
6
  readonly description: string;
8
7
  }
8
+ /**
9
+ * The tab shortcut declarations, in the order that decides which action keeps a duplicated key.
10
+ * This module stays dependency-free so startup presentation and the resident projection can
11
+ * read it without loading the component registry.
12
+ */
9
13
  export declare const RESIDENT_TAB_SHORTCUTS: readonly ResidentTabShortcut[];
10
- export declare function residentTabShortcutRegistry(assignments?: Readonly<Partial<Record<ResidentTabAction, string | null>>>): ShortcutRegistry<ResidentTabAction>;
@@ -1,10 +1,14 @@
1
- import { GLOBAL_SCOPE, ShortcutRegistry } from "../../ui/components/index.js";
2
1
  const jumps = Array.from({ length: 10 }, (_, index) => ({
3
2
  id: `tabs.jump${index + 1}`,
4
3
  defaultKey: `alt+${(index + 1) % 10}`,
5
4
  scope: "global",
6
5
  description: `View A1 tab ${index + 1}`,
7
6
  }));
7
+ /**
8
+ * The tab shortcut declarations, in the order that decides which action keeps a duplicated key.
9
+ * This module stays dependency-free so startup presentation and the resident projection can
10
+ * read it without loading the component registry.
11
+ */
8
12
  export const RESIDENT_TAB_SHORTCUTS = Object.freeze([
9
13
  { id: "tabs.new", defaultKey: "alt+a", scope: "global", description: "New A1 tab" },
10
14
  { id: "tabs.close", defaultKey: "alt+w", scope: "global", description: "Close A1 tab" },
@@ -17,19 +21,3 @@ export const RESIDENT_TAB_SHORTCUTS = Object.freeze([
17
21
  { id: "tabs.retry", defaultKey: "r", scope: "failed-banner", description: "Retry failed A1 tab" },
18
22
  { id: "tabs.fresh", defaultKey: "f", scope: "failed-banner", description: "Start failed A1 tab fresh" },
19
23
  ]);
20
- export function residentTabShortcutRegistry(assignments = {}) {
21
- const registry = new ShortcutRegistry();
22
- for (const shortcut of RESIDENT_TAB_SHORTCUTS) {
23
- const key = assignments[shortcut.id] === undefined ? shortcut.defaultKey : assignments[shortcut.id];
24
- if (!key)
25
- continue;
26
- const declaration = {
27
- key,
28
- scope: shortcut.scope === "global" ? GLOBAL_SCOPE : shortcut.scope,
29
- section: "Tabs",
30
- description: shortcut.description,
31
- };
32
- registry.declare(declaration, shortcut.id);
33
- }
34
- return registry;
35
- }
@@ -0,0 +1,29 @@
1
+ import { type ResidentTabAction } from "./shortcut-declarations.js";
2
+ /** Key assignments as `keybindings.json` and the keybinding manager express them. */
3
+ export type ShortcutBindings = Readonly<Record<string, string | readonly string[] | null | undefined>>;
4
+ export type ResidentTabGlobalAction = Exclude<ResidentTabAction, "tabs.retry" | "tabs.fresh">;
5
+ export interface ResidentTabShortcutConflict {
6
+ readonly id: ResidentTabGlobalAction;
7
+ readonly key: string;
8
+ /** `reserved` keys belong to an A1 action; `duplicate` keys were claimed by an earlier tab action. */
9
+ readonly reason: "reserved" | "duplicate";
10
+ }
11
+ /** The effective tab shortcuts the resident shell dispatches and every presentation shows. */
12
+ export interface ResidentTabShortcutProjection {
13
+ readonly assignments: Readonly<Record<ResidentTabGlobalAction, string | null>>;
14
+ readonly conflicts: readonly ResidentTabShortcutConflict[];
15
+ }
16
+ export interface ResidentTabShortcutInput {
17
+ /** The user's `keybindings.json`, including any `tabs.*` entries; `null` unbinds an action. */
18
+ readonly configured?: ShortcutBindings | undefined;
19
+ /** The effective A1 declarations (defaults plus customization) whose keys tab actions may not take. */
20
+ readonly reserved?: ShortcutBindings | undefined;
21
+ }
22
+ /** The declarations the projection covers, in the order that decides duplicate claims. */
23
+ export declare const RESIDENT_TAB_GLOBAL_SHORTCUTS: import("./shortcut-declarations.js").ResidentTabShortcut[];
24
+ /**
25
+ * Projects the effective A1 declarations and the user's customization into one tab shortcut
26
+ * assignment per action. A key an A1 action holds after customization never becomes a tab
27
+ * shortcut; between tab actions the earlier declaration keeps a duplicated key.
28
+ */
29
+ export declare function projectResidentTabShortcuts(input?: ResidentTabShortcutInput): ResidentTabShortcutProjection;
@@ -0,0 +1,64 @@
1
+ import { RESIDENT_TAB_SHORTCUTS } from "./shortcut-declarations.js";
2
+ /** The declarations the projection covers, in the order that decides duplicate claims. */
3
+ export const RESIDENT_TAB_GLOBAL_SHORTCUTS = RESIDENT_TAB_SHORTCUTS.filter(shortcut => shortcut.scope === "global");
4
+ function normalizeKey(key) {
5
+ return key.trim().toLowerCase();
6
+ }
7
+ function keysOf(value) {
8
+ if (value === null || value === undefined)
9
+ return value;
10
+ if (typeof value === "string")
11
+ return [value];
12
+ return value.filter((key) => typeof key === "string");
13
+ }
14
+ /**
15
+ * Projects the effective A1 declarations and the user's customization into one tab shortcut
16
+ * assignment per action. A key an A1 action holds after customization never becomes a tab
17
+ * shortcut; between tab actions the earlier declaration keeps a duplicated key.
18
+ */
19
+ export function projectResidentTabShortcuts(input = {}) {
20
+ const configured = input.configured ?? {};
21
+ const claimedByOwnedActions = new Map();
22
+ for (const [action, value] of Object.entries(input.reserved ?? {})) {
23
+ if (action.startsWith("tabs."))
24
+ continue;
25
+ claimedByOwnedActions.set(action, keysOf(value) ?? []);
26
+ }
27
+ for (const [action, value] of Object.entries(configured)) {
28
+ if (action.startsWith("tabs.") || action === "$schema")
29
+ continue;
30
+ claimedByOwnedActions.set(action, keysOf(value) ?? []);
31
+ }
32
+ const reserved = new Set();
33
+ for (const keys of claimedByOwnedActions.values())
34
+ for (const key of keys)
35
+ reserved.add(normalizeKey(key));
36
+ const claimed = new Set();
37
+ const conflicts = [];
38
+ const assignments = {};
39
+ for (const shortcut of RESIDENT_TAB_GLOBAL_SHORTCUTS) {
40
+ const id = shortcut.id;
41
+ const custom = configured[id];
42
+ const candidates = custom === undefined
43
+ ? (shortcut.defaultKey === null ? [] : [shortcut.defaultKey])
44
+ : (keysOf(custom) ?? []);
45
+ let assigned = null;
46
+ for (const candidate of candidates.map(normalizeKey)) {
47
+ if (candidate.length === 0)
48
+ continue;
49
+ if (reserved.has(candidate)) {
50
+ conflicts.push({ id, key: candidate, reason: "reserved" });
51
+ continue;
52
+ }
53
+ if (claimed.has(candidate)) {
54
+ conflicts.push({ id, key: candidate, reason: "duplicate" });
55
+ continue;
56
+ }
57
+ assigned = candidate;
58
+ claimed.add(candidate);
59
+ break;
60
+ }
61
+ assignments[id] = assigned;
62
+ }
63
+ return { assignments, conflicts };
64
+ }
@@ -66,6 +66,7 @@ async function runBootstrapAttempt(options, retriesRemaining) {
66
66
  "attach", "--endpoint", resident.endpoint, "--cwd", process.cwd(), "--clear-exit-ms", String(CLEAR_EXIT_INTERVAL_MS),
67
67
  "--tabs-max", String((await residentLaunchSettings).tabsMax),
68
68
  "--profile", environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir,
69
+ "--settings-file", resolve(agentProfileDir, "settings.json"),
69
70
  "--data-dir", paths.dataDir, "--runtime-dir", paths.runtimeDir,
70
71
  "--release-root", release.releaseRoot, "--node-executable", process.execPath,
71
72
  ]),