@timurproko/a1 0.2.7-dev.810 → 0.2.7-dev.828

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 (166) hide show
  1. package/README.md +7 -1
  2. package/bin/automatic-update.js +7 -0
  3. package/bin/tab-launch-context.d.ts +1 -0
  4. package/bin/tab-launch-context.js +3 -3
  5. package/bin/ui.js +96 -66
  6. package/dist/app/session-shell/resident-tab-coordinator.d.ts +6 -9
  7. package/dist/app/session-shell/resident-tab-coordinator.js +9 -52
  8. package/dist/app/session-shell/session-shell-root.d.ts +14 -8
  9. package/dist/app/session-shell/session-shell-root.js +9 -5
  10. package/dist/app/session-shell/session-shell.js +70 -20
  11. package/dist/app/session-shell/session-viewport-controller.js +6 -4
  12. package/dist/app/session-shell/shell-host.js +6 -3
  13. package/dist/app/session-shell/transcript-document.js +1 -1
  14. package/dist/cli/tabs.d.ts +1 -1
  15. package/dist/cli/tabs.js +10 -14
  16. package/dist/composition/owned-settings.d.ts +12 -0
  17. package/dist/composition/owned-settings.js +36 -0
  18. package/dist/composition/owned-ui.d.ts +5 -14
  19. package/dist/composition/owned-ui.js +93 -64
  20. package/dist/composition/resident-tab-bridge.d.ts +10 -1
  21. package/dist/composition/resident-tab-bridge.js +21 -0
  22. package/dist/composition/settings-route-host.d.ts +12 -2
  23. package/dist/composition/settings-route-host.js +27 -9
  24. package/dist/composition/settings-search-input.d.ts +9 -0
  25. package/dist/composition/settings-search-input.js +58 -0
  26. package/dist/composition/utility-tab.d.ts +28 -0
  27. package/dist/composition/utility-tab.js +242 -0
  28. package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
  29. package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
  30. package/dist/contracts/presentation/index.d.ts +11 -0
  31. package/dist/contracts/presentation/index.js +12 -0
  32. package/dist/features/launch/development-launch.js +6 -0
  33. package/dist/features/launch/profile-paths.js +4 -0
  34. package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
  35. package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
  36. package/dist/features/owned-ui/project-trust-dialog.d.ts +1 -1
  37. package/dist/features/owned-ui/project-trust-dialog.js +6 -8
  38. package/dist/features/owned-ui/project-trust-prompt.d.ts +1 -0
  39. package/dist/features/owned-ui/project-trust-prompt.js +7 -3
  40. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
  41. package/dist/features/owned-ui/reference-screen-app.js +12 -2
  42. package/dist/features/owned-ui/settings-app.d.ts +10 -3
  43. package/dist/features/owned-ui/settings-app.js +98 -45
  44. package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
  45. package/dist/features/owned-ui/settings-search-input.js +1 -0
  46. package/dist/features/prompt-history/index.d.ts +1 -1
  47. package/dist/features/prompt-history/paths.d.ts +2 -1
  48. package/dist/features/prompt-history/paths.js +10 -2
  49. package/dist/features/prompt-history/service.d.ts +5 -0
  50. package/dist/features/prompt-history/service.js +1 -1
  51. package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
  52. package/dist/features/resident-tab/shortcut-declarations.js +2 -0
  53. package/dist/foundation/launch-context/index.d.ts +2 -0
  54. package/dist/foundation/launch-context/index.js +1 -0
  55. package/dist/foundation/lifecycle/session-repository-context.js +16 -2
  56. package/dist/foundation/release/automatic-update.d.ts +37 -0
  57. package/dist/foundation/release/automatic-update.js +265 -0
  58. package/dist/foundation/release/bootstrap.d.ts +1 -9
  59. package/dist/foundation/release/bootstrap.js +7 -3
  60. package/dist/foundation/release/early-accent.d.ts +12 -0
  61. package/dist/foundation/release/early-accent.js +59 -0
  62. package/dist/foundation/release/index.d.ts +1 -1
  63. package/dist/foundation/release/index.js +1 -1
  64. package/dist/foundation/release/latest-release.d.ts +3 -2
  65. package/dist/foundation/release/latest-release.js +1 -1
  66. package/dist/foundation/release/progress-palette.generated.d.ts +137 -3
  67. package/dist/foundation/release/progress-palette.generated.js +137 -3
  68. package/dist/foundation/release/release-gc.js +19 -4
  69. package/dist/foundation/release/update-execution-lease.d.ts +36 -0
  70. package/dist/foundation/release/update-execution-lease.js +145 -0
  71. package/dist/foundation/release/update.d.ts +9 -7
  72. package/dist/foundation/release/update.js +244 -175
  73. package/dist/foundation/resident-tabs/bridge-client.d.ts +10 -3
  74. package/dist/foundation/resident-tabs/bridge-client.js +20 -8
  75. package/dist/foundation/resident-tabs/maintenance.d.ts +2 -6
  76. package/dist/foundation/resident-tabs/maintenance.js +5 -23
  77. package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +34 -0
  78. package/dist/foundation/resident-tabs/progress-observation-schema.js +1 -0
  79. package/dist/foundation/resident-tabs/progress-observation.d.ts +3 -6
  80. package/dist/foundation/resident-tabs/progress-observation.js +43 -12
  81. package/dist/foundation/resident-tabs/release-holds.js +3 -14
  82. package/dist/foundation/resident-tabs/routes.d.ts +5 -0
  83. package/dist/foundation/resident-tabs/routes.js +5 -0
  84. package/dist/foundation/resident-tabs/settings.d.ts +0 -1
  85. package/dist/foundation/resident-tabs/settings.js +0 -2
  86. package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
  87. package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
  88. package/dist/integrations/pi/components/modal-frame.js +48 -9
  89. package/dist/integrations/pi/components/models-dialog.js +4 -52
  90. package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
  91. package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
  92. package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
  93. package/dist/integrations/pi/components/shell-footer-status.js +19 -3
  94. package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
  95. package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
  96. package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
  97. package/dist/integrations/pi/components/skills-dialog.js +2 -2
  98. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
  99. package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
  100. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
  101. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
  102. package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
  103. package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
  104. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
  105. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
  106. package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
  107. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
  108. package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
  109. package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
  110. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +2 -0
  111. package/dist/integrations/pi/components/upstream/theme/theme.js +5 -1
  112. package/dist/integrations/pi/engine/adapter.js +7 -2
  113. package/dist/integrations/pi/engine/offline-settings-port.d.ts +23 -0
  114. package/dist/integrations/pi/engine/offline-settings-port.js +53 -0
  115. package/dist/integrations/pi/engine/ports.d.ts +1 -1
  116. package/dist/integrations/pi/engine/ports.js +4 -0
  117. package/dist/integrations/pi/engine/settings-bridge.d.ts +5 -0
  118. package/dist/integrations/pi/engine/settings-bridge.js +13 -0
  119. package/dist/integrations/pi/engine/settings-port.js +1 -14
  120. package/dist/integrations/pi/engine/workflow-runner.js +9 -2
  121. package/dist/native/darwin-arm64/manifest.json +1 -1
  122. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  123. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  124. package/dist/native/darwin-x64/manifest.json +1 -1
  125. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  126. package/dist/native/darwin-x64/terminal-host.node +0 -0
  127. package/dist/native/linux-x64/manifest.json +1 -1
  128. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  129. package/dist/native/linux-x64/terminal-host.node +0 -0
  130. package/dist/native/win32-x64/manifest.json +2 -2
  131. package/dist/native/win32-x64/process-guardian.node +0 -0
  132. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  133. package/dist/native/win32-x64/terminal-host.node +0 -0
  134. package/dist/product-identity.d.ts +1 -1
  135. package/dist/product-identity.js +2 -2
  136. package/dist/product-identity.json +2 -0
  137. package/dist/ui/apps/host.d.ts +2 -0
  138. package/dist/ui/apps/host.js +7 -1
  139. package/dist/ui/components/dialog-panel.js +35 -30
  140. package/dist/ui/components/line-input.d.ts +3 -0
  141. package/dist/ui/components/line-input.js +3 -1
  142. package/dist/ui/components/prompt-input.d.ts +5 -4
  143. package/dist/ui/components/prompt-input.js +8 -6
  144. package/dist/ui/components/spans.js +2 -2
  145. package/dist/ui/components/submitted-prompt.d.ts +2 -2
  146. package/dist/ui/components/submitted-prompt.js +3 -3
  147. package/dist/ui/components/surface.js +4 -2
  148. package/dist/ui/components/text.d.ts +3 -6
  149. package/dist/ui/components/text.js +6 -8
  150. package/dist/ui/components/transcript-viewport.d.ts +1 -1
  151. package/dist/ui/components/transcript-viewport.js +141 -24
  152. package/dist/ui/settings/declarations.d.ts +67 -17
  153. package/dist/ui/settings/declarations.js +47 -15
  154. package/dist/ui/settings/early-accent.d.ts +81 -0
  155. package/dist/ui/settings/early-accent.js +50 -0
  156. package/dist/ui/settings/manager.js +7 -3
  157. package/dist/ui/settings/migrations.js +22 -0
  158. package/dist/ui/settings/progress-palette.generated.d.ts +139 -0
  159. package/dist/ui/settings/progress-palette.generated.js +139 -0
  160. package/dist/ui/settings/sections.d.ts +4 -0
  161. package/dist/ui/settings/sections.js +9 -3
  162. package/docs/architecture/resident-tabs.md +52 -21
  163. package/docs/architecture/tool-image-presentation.md +2 -1
  164. package/docs/features/prompt-history.md +41 -20
  165. package/docs/validation.md +25 -5
  166. package/package.json +1 -1
@@ -322,8 +322,8 @@ export class SessionViewportController {
322
322
  this.#requestRender();
323
323
  }
324
324
  }
325
- const previousPrompt = SHIFT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
326
- if (editorActive && allowWheel && (previousPrompt || SHIFT_DOWN_INPUTS.has(data))) {
325
+ const previousPrompt = PROMPT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
326
+ if (editorActive && allowWheel && (previousPrompt || PROMPT_DOWN_INPUTS.has(data))) {
327
327
  const scrolled = previousPrompt
328
328
  ? this.#viewport.scrollToPreviousPrompt(now)
329
329
  : this.#viewport.scrollToNextPrompt(now);
@@ -670,5 +670,7 @@ export class SessionViewportController {
670
670
  }
671
671
  const SELECTION_AUTO_SCROLL_INTERVAL_MS = 30;
672
672
  const EDITOR_LINK_DELETION_INPUTS = new Set(["\b", "\u007f", "\u001b[3~"]);
673
- const SHIFT_UP_INPUTS = new Set(["\u001b[1;2A"]);
674
- const SHIFT_DOWN_INPUTS = new Set(["\u001b[1;2B"]);
673
+ // Platform: Windows Terminal may consume Shift+Arrow for native selection. Ctrl+Arrow matches
674
+ // solo A1's Windows binding while retaining Shift+Arrow whenever the terminal delivers it.
675
+ const PROMPT_UP_INPUTS = new Set(["\u001b[1;2A", "\u001b[1;5A"]);
676
+ const PROMPT_DOWN_INPUTS = new Set(["\u001b[1;2B", "\u001b[1;5B"]);
@@ -102,10 +102,13 @@ export class OwnedUiTerminalHost {
102
102
  },
103
103
  }),
104
104
  } : { layoutRoot: pinnedLayoutRoot(this.#delegatingLayoutParts()) }),
105
- ...(input?.onEvent === undefined ? {} : {
105
+ ...(input?.onEvent === undefined && progressObserver === null ? {} : {
106
106
  inputDiagnostics: {
107
- onEvent: input.onEvent,
108
- ...(input.now === undefined ? {} : { now: input.now }),
107
+ onEvent: event => {
108
+ input?.onEvent?.(event);
109
+ progressObserver?.inputPhase?.(event.phase, event.revision);
110
+ },
111
+ ...(input?.now === undefined ? {} : { now: input.now }),
109
112
  },
110
113
  }),
111
114
  ...(options.terminal === undefined ? {} : { terminal: options.terminal }),
@@ -260,7 +260,7 @@ export class TranscriptDocument {
260
260
  : createPiShellChangelog(diagnostic.message).render(width)));
261
261
  rows.push(...diagnostics
262
262
  .filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "project-trust"
263
- && diagnostic.code !== "release-update"
263
+ && diagnostic.code !== "release-update" && diagnostic.code !== "release-update-installed"
264
264
  && diagnostic.code !== "package-updates" && diagnostic.code !== "changelog-collapsed"
265
265
  && diagnostic.code !== "changelog-expanded")
266
266
  .slice(-3)
@@ -2,7 +2,7 @@ import { runTerminalHostMaintenance, writeDoctorBundle } from "../foundation/res
2
2
  import type { TabsRequest } from "./tabs-request.js";
3
3
  export type { TabsRequest } from "./tabs-request.js";
4
4
  export interface TabsCommandOptions {
5
- /** The running package root; its terminal host is a candidate after the active release. */
5
+ /** The invoking package, never a different active or retained release. */
6
6
  readonly packageRoot?: string;
7
7
  readonly environment?: NodeJS.ProcessEnv;
8
8
  readonly stdout?: (message: string) => void;
package/dist/cli/tabs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolveProductPaths } from "../foundation/lifecycle/index.js";
2
- import { CohortStateStore } from "../foundation/release/index.js";
2
+ import { materializeRelease } from "../foundation/release/index.js";
3
3
  import { readResidentTabLaunchSettings, runTerminalHostMaintenance, writeDoctorBundle, } from "../foundation/resident-tabs/index.js";
4
4
  import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../product-identity.js";
5
5
  /** `a1 tabs`: inspect or stop resident tabs without attaching, and never start a host to report state. */
@@ -9,7 +9,7 @@ export async function runTabsCommand(request, options = {}) {
9
9
  const stderr = options.stderr ?? (message => process.stderr.write(message));
10
10
  const paths = resolveProductPaths(environment);
11
11
  const profileRoot = environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir;
12
- const releaseRoots = await installedReleaseRoots(paths.dataDir, options.packageRoot);
12
+ const releaseRoots = await invokingReleaseRoots(paths.dataDir, options.packageRoot, environment);
13
13
  const maintenance = options.maintenance ?? runTerminalHostMaintenance;
14
14
  const report = await maintenance({
15
15
  action: request.action === "doctor" ? "host-status" : request.action,
@@ -51,22 +51,18 @@ export async function runTabsCommand(request, options = {}) {
51
51
  }
52
52
  return report.exitCode === 0 ? 0 : 1;
53
53
  }
54
- async function installedReleaseRoots(dataDir, packageRoot) {
55
- const roots = [];
54
+ async function invokingReleaseRoots(dataDir, packageRoot, environment) {
55
+ if (!packageRoot)
56
+ return [];
57
+ if (environment[PRODUCT_IDENTITY.environment.developmentInstanceId])
58
+ return [packageRoot];
56
59
  try {
57
- const state = await new CohortStateStore(dataDir).read();
58
- for (const id of [state.references.active, state.references.rollback]) {
59
- if (id && state.releases[id])
60
- roots.push(state.releases[id].releaseRoot);
61
- }
62
- roots.push(...Object.values(state.releases).map(record => record.releaseRoot));
60
+ // Invariant: immutable materialization selects exact payload bytes without activating or starting a host.
61
+ return [(await materializeRelease(packageRoot, dataDir)).releaseRoot];
63
62
  }
64
63
  catch {
65
- // Rationale: unreadable release state still leaves the running package's own host.
64
+ return [];
66
65
  }
67
- if (packageRoot)
68
- roots.push(packageRoot);
69
- return [...new Set(roots)];
70
66
  }
71
67
  const NO_TABS = "No resident tabs are running.\n";
72
68
  function describe(request, report) {
@@ -0,0 +1,12 @@
1
+ import type { AgentSettingsPort } from "../contracts/agent-engine/index.js";
2
+ import { OwnedSettingsManager } from "../ui/settings/manager.js";
3
+ export interface OwnedSettingsManagerFactoryOptions {
4
+ readonly profileId: string;
5
+ readonly hideTabSettings?: boolean;
6
+ readonly agent?: AgentSettingsPort | null;
7
+ readonly agentProvider?: () => AgentSettingsPort | null;
8
+ }
9
+ /** Builds the profile-global settings manager shared by the Agent and slim utility compositions. */
10
+ export declare function createOwnedSettingsManager(options: OwnedSettingsManagerFactoryOptions): OwnedSettingsManager;
11
+ /** Rereads the shared settings document whenever its stat changes; returns the stop. */
12
+ export declare function watchSharedSettings(settings: Pick<OwnedSettingsManager, "file" | "refresh">): () => void;
@@ -0,0 +1,36 @@
1
+ import { unwatchFile, watchFile } from "node:fs";
2
+ import { resolveLaunchProfilePaths } from "../features/launch/profile-paths.js";
3
+ import { resolveProductPaths } from "../foundation/lifecycle/paths.js";
4
+ import { PRODUCT_IDENTITY } from "../product-identity.js";
5
+ import { OwnedSettingsManager } from "../ui/settings/manager.js";
6
+ /** Builds the profile-global settings manager shared by the Agent and slim utility compositions. */
7
+ export function createOwnedSettingsManager(options) {
8
+ const profilePaths = resolveLaunchProfilePaths();
9
+ const productPaths = resolveProductPaths();
10
+ return new OwnedSettingsManager({
11
+ profileDir: profilePaths.agentProfile,
12
+ profileId: options.profileId,
13
+ namespace: PRODUCT_IDENTITY.commandName,
14
+ legacyConfigDir: productPaths.configDir,
15
+ ...(options.agent === undefined ? {} : { agent: options.agent }),
16
+ ...(options.agentProvider === undefined ? {} : { agentProvider: options.agentProvider }),
17
+ ...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsAutoName", "tabsPosition", "tabBarPlusMenu"] } : {}),
18
+ hiddenAgentSettingIds: ["fullscreenWheelScrollLines"],
19
+ agentSettingLabelOverrides: { fullscreenCopyOnSelect: "Copy on select" },
20
+ });
21
+ }
22
+ const SHARED_SETTINGS_POLL_MS = 250;
23
+ /** Rereads the shared settings document whenever its stat changes; returns the stop. */
24
+ export function watchSharedSettings(settings) {
25
+ const file = settings.file;
26
+ const listener = () => {
27
+ try {
28
+ settings.refresh();
29
+ }
30
+ catch {
31
+ // Compatibility: an unreadable peer write resolves on the next stat change.
32
+ }
33
+ };
34
+ watchFile(file, { interval: SHARED_SETTINGS_POLL_MS, persistent: false }, listener);
35
+ return () => unwatchFile(file, listener);
36
+ }
@@ -4,7 +4,7 @@ import type { SessionSelection } from "../foundation/lifecycle/session-selection
4
4
  import { type PiEngineHostOptions } from "../integrations/pi/engine/host.js";
5
5
  import type { PiProjectTrustPreflightPrompt } from "../integrations/pi/engine/project-trust-preflight.js";
6
6
  import type { PiSessionForkPrompt } from "../integrations/pi/engine/session-selection.js";
7
- import { OwnedSettingsManager } from "../ui/settings/manager.js";
7
+ import type { OwnedSettingsManager } from "../ui/settings/manager.js";
8
8
  import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contracts/presentation/index.js";
9
9
  import type { ReleaseNoteCatalog } from "../features/owned-ui/release-notes.js";
10
10
  import type { TabBridgeClient } from "../foundation/resident-tabs/bridge-client.js";
@@ -20,17 +20,11 @@ export interface OwnedUiCompositionOptions {
20
20
  readonly sessionPath?: string;
21
21
  readonly sessionSelection?: SessionSelection;
22
22
  readonly sessionForkPrompt?: PiSessionForkPrompt;
23
- /**
24
- * A1 profile whose settings this session reads and writes. Omitted keeps the
25
- * session settings-free, which is what the pinned comparison paths use.
26
- */
23
+ /** A1 settings profile; comparison paths omit it. */
27
24
  readonly profileId?: string;
28
25
  /** Hide resident-only tab controls for the explicit direct single-agent launch. */
29
26
  readonly hideTabSettings?: boolean;
30
- /**
31
- * Whether A1's product-specific surfaces are reachable. Comparison profiles
32
- * use the same composition with those surfaces withheld.
33
- */
27
+ /** `off` withholds A1-specific surfaces from comparison profiles. */
34
28
  readonly ownedSurfaces?: "on" | "off";
35
29
  readonly projectTrustPrompt?: PiProjectTrustPreflightPrompt;
36
30
  /** Explicit local diagnostic destination; ignored by comparison/settings-free compositions. */
@@ -39,18 +33,16 @@ export interface OwnedUiCompositionOptions {
39
33
  readonly clipboardDiagnosticsPath?: string;
40
34
  /** Deterministic release-note seams for composition tests. */
41
35
  readonly packageVersion?: string;
36
+ readonly notificationVersion?: string | undefined;
42
37
  readonly releaseNotes?: ReleaseNoteCatalog;
43
38
  /** Startup release-availability seam; defaults to the throttled registry check. */
44
39
  readonly checkForNewerRelease?: (options: StartupReleaseCheckOptions) => Promise<AvailableRelease | null>;
45
- /** Exact native process inspection injected by the shipped entry; absent SDK seams do not publish claims. */
46
40
  readonly inspectProcess?: (pid: number) => Promise<NativeProcessIdentity | null>;
47
- /** Private credentials were consumed by the entry before composition or extensions loaded. */
48
41
  readonly tab?: {
49
42
  readonly bridge: TabBridgeClient;
50
43
  readonly dataDir: string;
51
44
  readonly incarnation: number;
52
45
  };
53
- /** One prompt-adjacent notice explaining why a resident launch fell back. */
54
46
  readonly residentFallbackNotice?: string;
55
47
  }
56
48
  export interface OwnedUiComposition {
@@ -69,5 +61,4 @@ export declare function residentStartupPresentation(resident: boolean, fallbackN
69
61
  };
70
62
  export declare function composeOwnedUiApplication(options?: OwnedUiCompositionOptions): Promise<OwnedUiApplicationPort>;
71
63
  export declare function composeOwnedUi(options?: OwnedUiCompositionOptions): Promise<OwnedUiComposition>;
72
- /** Rereads the shared settings document whenever its stat changes; returns the stop. */
73
- export declare function watchSharedSettings(settings: Pick<OwnedSettingsManager, "file" | "refresh">): () => void;
64
+ export { watchSharedSettings } from "./owned-settings.js";
@@ -1,27 +1,28 @@
1
- import { unwatchFile, watchFile } from "node:fs";
2
1
  import { SuggestionDiagnosticCapture } from "../features/prompt-suggestions/diagnostics.js";
3
2
  import { PRODUCT_IDENTITY } from "../product-identity.js";
4
3
  import { PromptHistoryService } from "../features/prompt-history/service.js";
5
4
  import { PromptImageSidecar } from "../features/prompt-history/image-sidecar.js";
6
5
  import { resolvePromptHistoryPath } from "../features/prompt-history/paths.js";
7
- import { resolveLaunchProfilePaths, resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
6
+ import { resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
8
7
  import { resolveProductPaths } from "../foundation/lifecycle/paths.js";
8
+ import { MAX_CLIPBOARD_TEXT_BYTES } from "../contracts/owned-ui/index.js";
9
9
  import { applyConfiguredPiTheme, getAvailablePiThemes, setPiAccentColor, setPiPackageBorderProjectionEnabled } from "../integrations/pi/components/upstream/theme/theme.js";
10
10
  import { createPiEngineHost } from "../integrations/pi/engine/host.js";
11
11
  import { createPiSessionPresenters } from "../integrations/pi/session-presenters/index.js";
12
12
  import { ClipboardDiagnosticCapture } from "../app/session-shell/clipboard-diagnostics.js";
13
13
  import { createOwnedUiClipboardServices } from "../app/session-shell/clipboard-services.js";
14
+ import { writeSystemClipboardText } from "../app/session-shell/system-clipboard.js";
14
15
  import { createPiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
15
16
  import { OwnedUiSessionPresenter, OwnedUiTerminalHost, sessionTerminalHostOptions, } from "../app/session-shell/session-shell.js";
16
- import { OwnedSettingsManager } from "../ui/settings/manager.js";
17
17
  import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentation-adapter.js";
18
18
  import { createOwnedRouteHost } from "./settings-route-host.js";
19
19
  import { renderPiShellChangelogLines } from "../integrations/pi/components/shell-presenters-info.js";
20
20
  import { nativeHyperlinkStyle } from "../ui/components/spans.js";
21
21
  import { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
22
- import { bindResidentTabBridge, correlateResidentTabSubmission, currentBackendSessionFile, residentTabKeybindings } from "./resident-tab-bridge.js";
22
+ import { bindResidentTabBridge, closeResidentUtilityRoute, correlateResidentTabSubmission, currentBackendSessionFile, residentRouteHostOptions, residentTabKeybindings } from "./resident-tab-bridge.js";
23
23
  import { SessionRuntimeRegistry } from "./session-runtime-registry.js";
24
24
  import { readPackageVersion, StartupReleaseNote } from "./startup-route.js";
25
+ import { createOwnedSettingsManager, watchSharedSettings } from "./owned-settings.js";
25
26
  export { bindResidentTabBridge, correlateResidentTabSubmission } from "./resident-tab-bridge.js";
26
27
  export function residentStartupPresentation(resident, fallbackNotice) {
27
28
  if (resident)
@@ -39,20 +40,14 @@ export async function composeOwnedUi(options = {}) {
39
40
  inspectProcess: options.inspectProcess,
40
41
  });
41
42
  const productPaths = resolveProductPaths();
42
- const profilePaths = resolveLaunchProfilePaths();
43
43
  // Rationale: the manager reads the engine's settings port lazily, after the session exists.
44
44
  let backend;
45
45
  const settings = options.profileId === undefined
46
46
  ? null
47
- : new OwnedSettingsManager({
48
- profileDir: profilePaths.agentProfile,
47
+ : createOwnedSettingsManager({
49
48
  profileId: options.profileId,
50
- namespace: PRODUCT_IDENTITY.commandName,
51
- legacyConfigDir: productPaths.configDir,
52
49
  agentProvider: () => backend.settingsPort(),
53
- ...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsAutoName"] } : {}),
54
- hiddenAgentSettingIds: ["fullscreenWheelScrollLines"],
55
- agentSettingLabelOverrides: { fullscreenCopyOnSelect: "Copy on select" },
50
+ ...(options.hideTabSettings ? { hideTabSettings: true } : {}),
56
51
  });
57
52
  const tabBridge = options.tab?.bridge ?? null;
58
53
  if (tabBridge !== null)
@@ -130,12 +125,15 @@ export async function composeOwnedUi(options = {}) {
130
125
  host.setAccentColor(accent);
131
126
  tabBridge?.accent(accent);
132
127
  });
133
- // Rationale: every resident tab is its own process over one shared settings document, so a
134
- // change saved in any tab reaches the others only by rereading that document.
135
- const unwatchSharedSettings = settings === null || !ownedSurfaces || tabBridge === null
128
+ // Rationale: resident tabs and separate solo agents share a settings document; a live scope
129
+ // change saved in either reaches the others only by rereading that document.
130
+ const unwatchSharedSettings = settings === null || !ownedSurfaces
136
131
  ? () => { }
137
132
  : watchSharedSettings(settings);
138
133
  const unsubscribeAccent = () => { unwatchSharedSettings(); unsubscribeAccentChange(); };
134
+ // Invariant: the Settings search and agent prompt borrow the same resolved Pi keybindings.
135
+ const keybindingsHost = createPiKeybindingsHost({ profile: ownedSurfaces ? "a1" : "pi", agentDir: backend.identity.agentDir });
136
+ const keybindings = residentTabKeybindings(tabBridge, backend.identity.agentDir, keybindingsHost);
139
137
  // Rationale: Routes open only after shell construction.
140
138
  const references = {
141
139
  changelog: async (input) => {
@@ -151,9 +149,8 @@ export async function composeOwnedUi(options = {}) {
151
149
  },
152
150
  session: () => import("./session-info-reference.js").then(m => m.loadSessionInfoReference(backend)),
153
151
  };
154
- const routeHost = settings === null || !ownedSurfaces ? null : createOwnedRouteHost(settings, references, {
155
- settingsPinned: tabBridge?.tabId.startsWith("settings-") === true,
156
- });
152
+ const routeHost = settings === null || !ownedSurfaces ? null
153
+ : createOwnedRouteHost(settings, references, settingsRouteOptions(tabBridge, keybindings, backend.identity.agentDir, cwd));
157
154
  const viewportSettings = settings === null || !ownedSurfaces ? null : {
158
155
  snapshot: () => viewportSettingsSnapshot(settings),
159
156
  onChange: listener => settings.onChange(() => listener(viewportSettingsSnapshot(settings))),
@@ -181,26 +178,11 @@ export async function composeOwnedUi(options = {}) {
181
178
  // Rationale: one listener covers both the A1 presentation choice and the engine skill-command toggle.
182
179
  onChange: (listener) => settings.onChange(() => listener()),
183
180
  };
184
- const historyLimit = settings?.value("promptHistoryMaxItems");
185
- const historyProfileLocation = settings === null || !ownedSurfaces || !settings.value("promptHistoryEnabled")
186
- ? null
187
- : resolvePromptHistoryPath(resolvePromptHistoryDataDir(), backend.identity.agentDir);
188
- const promptHistory = historyProfileLocation === null ? null : {
189
- limit: typeof historyLimit === "number" ? historyLimit : 100,
190
- store: new PromptHistoryService({
191
- dataDir: resolvePromptHistoryDataDir(),
192
- profileRoot: backend.identity.agentDir,
193
- limit: typeof historyLimit === "number" ? historyLimit : 100,
194
- }),
195
- imageSidecar: new PromptImageSidecar(historyProfileLocation.imagesDir),
196
- };
181
+ const promptHistory = createPromptHistoryConfiguration(settings, backend, ownedSurfaces);
197
182
  const clipboardDestination = options.clipboardDiagnosticsPath ?? process.env[PRODUCT_IDENTITY.environment.clipboardDiagnostics];
198
183
  const clipboardDiagnostics = settings !== null && ownedSurfaces && clipboardDestination?.trim()
199
184
  ? new ClipboardDiagnosticCapture(clipboardDestination) : null;
200
- // Invariant: process-wide services are created once here and borrowed by every session shell. The
201
- // keybinding registry is applied before any presenter is built, so none of them reads Pi's defaults.
202
- const keybindingsHost = createPiKeybindingsHost({ profile: ownedSurfaces ? "a1" : "pi", agentDir: backend.identity.agentDir });
203
- const keybindings = residentTabKeybindings(tabBridge, backend.identity.agentDir, keybindingsHost);
185
+ // Invariant: process-wide services are created once here and borrowed by every session shell.
204
186
  const clipboardServices = ownedSurfaces ? createOwnedUiClipboardServices() : null;
205
187
  let createdTerminalHost;
206
188
  let terminalHost;
@@ -216,12 +198,8 @@ export async function composeOwnedUi(options = {}) {
216
198
  ...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
217
199
  ...(tabBridge === null || tabRecovery === null ? {} : { residentTab: {
218
200
  requestDetach: () => tabBridge.requestDetach(),
219
- closeOwnedRoute: route => {
220
- if (tabBridge.tabId.startsWith("settings-") && route === "settings")
221
- void tabBridge.requestClose();
222
- },
201
+ closeOwnedRoute: route => closeResidentUtilityRoute(tabBridge, route),
223
202
  reportStatus: status => tabBridge.status(status),
224
- stallNoticeSeconds: 300,
225
203
  admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
226
204
  dispatched: () => tabRecovery.dispatched(),
227
205
  correlated: (submissionId, entryId) => correlateResidentTabSubmission(tabRecovery, backend, submissionId, entryId),
@@ -291,18 +269,13 @@ export async function composeOwnedUi(options = {}) {
291
269
  // Rationale: pinned Pi's version check is unreachable because the owned shell never runs its
292
270
  // InteractiveMode, so A1 checks its own channel. The result is never awaited on the startup path;
293
271
  // settings are read here because the runner resolves them only just before start.
294
- const announceNewerRelease = async () => {
295
- const check = options.checkForNewerRelease
296
- ?? (await import("../foundation/release/latest-release.js")).checkForNewerRelease;
297
- const release = await check({
298
- runningVersion: releaseNote.packageVersion ?? await readPackageVersion(),
299
- configDir: productPaths.configDir,
300
- interactive: process.stdout.isTTY === true,
301
- ...(settings === null || !ownedSurfaces ? {} : { settingEnabled: settings.value("updateCheck") }),
302
- });
303
- if (release !== null)
304
- backend.extensions.announceReleaseUpdate(release);
305
- };
272
+ const releaseUpdates = createStartupReleaseUpdateObservation({
273
+ backend,
274
+ configDir: productPaths.configDir,
275
+ mode: settings === null || !ownedSurfaces ? "auto" : settings.value("updateCheck"),
276
+ packageVersion: releaseNote.packageVersion, notificationVersion: options.notificationVersion,
277
+ checkForNewerRelease: options.checkForNewerRelease,
278
+ });
306
279
  const exitNoticePath = process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
307
280
  // Security: tools the agent runs must never see, or rewrite, this instance's notice.
308
281
  delete process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
@@ -315,11 +288,12 @@ export async function composeOwnedUi(options = {}) {
315
288
  exitNotice = import("../app/session-shell/exit-notice.js")
316
289
  .then(module => module.armExitNotice(backend, exitNoticePath), () => null);
317
290
  presenter.start();
318
- void announceNewerRelease().catch(() => undefined);
291
+ releaseUpdates.start();
319
292
  },
320
293
  flush: () => backend.session.flushEvents(),
321
294
  waitUntilStopped: () => presenter.waitUntilStopped(),
322
295
  dispose: async () => {
296
+ releaseUpdates.stop();
323
297
  try {
324
298
  await terminalHost.dispose();
325
299
  // Invariant: cleared only once the terminal is restored; a failed dispose leaves it armed.
@@ -344,20 +318,75 @@ export async function composeOwnedUi(options = {}) {
344
318
  };
345
319
  return { application, settings };
346
320
  }
347
- const SHARED_SETTINGS_POLL_MS = 250;
348
- /** Rereads the shared settings document whenever its stat changes; returns the stop. */
349
- export function watchSharedSettings(settings) {
350
- const file = settings.file;
351
- const listener = () => {
352
- try {
353
- settings.refresh();
321
+ function createPromptHistoryConfiguration(settings, backend, ownedSurfaces) {
322
+ const limit = settings?.value("promptHistoryMaxItems") ?? 100;
323
+ const scope = settings?.value("promptHistoryScope") ?? "unique";
324
+ const enabled = settings !== null && ownedSurfaces && settings.value("promptHistoryEnabled");
325
+ const sessionId = enabled ? backend.identity.currentHistorySessionId() : null;
326
+ if (settings === null || !enabled || (scope === "unique" && sessionId === null))
327
+ return null;
328
+ const dataDir = resolvePromptHistoryDataDir();
329
+ const forScope = (selected, id) => {
330
+ if (selected === "unique" && id === null)
331
+ return null;
332
+ const location = resolvePromptHistoryPath(dataDir, backend.identity.agentDir, process.platform, selected, id ?? "");
333
+ return {
334
+ store: new PromptHistoryService({ dataDir, profileRoot: backend.identity.agentDir, limit, scope: selected, sessionId: id ?? "" }),
335
+ imageSidecar: new PromptImageSidecar(location.imagesDir),
336
+ };
337
+ };
338
+ return {
339
+ limit, scope,
340
+ ...(sessionId === null ? {} : { sessionId }),
341
+ forScope,
342
+ onScopeChange: (listener) => settings.onChange(() => listener(settings.value("promptHistoryScope"))),
343
+ ...forScope(scope, sessionId),
344
+ };
345
+ }
346
+ function createStartupReleaseUpdateObservation(input) {
347
+ const controller = new AbortController();
348
+ const observe = async () => {
349
+ const check = input.checkForNewerRelease
350
+ ?? (await import("../foundation/release/latest-release.js")).checkForNewerRelease;
351
+ const runningVersion = input.packageVersion ?? await readPackageVersion();
352
+ const discoveryVersion = input.mode === "notify" ? input.notificationVersion ?? runningVersion : runningVersion;
353
+ const release = await check({
354
+ runningVersion: discoveryVersion,
355
+ configDir: input.configDir,
356
+ interactive: process.stdout.isTTY === true,
357
+ settingMode: input.mode,
358
+ });
359
+ if (release === null || controller.signal.aborted)
360
+ return;
361
+ if (input.mode === "notify") {
362
+ input.backend.extensions.announceReleaseUpdate(release);
363
+ return;
354
364
  }
355
- catch {
356
- // Compatibility: an unreadable peer write resolves on the next stat change.
365
+ if (input.mode === "auto") {
366
+ const installed = await import("../foundation/release/automatic-update.js")
367
+ .then(module => module.requestAutomaticUpdate({ release, runningVersion, signal: controller.signal }));
368
+ if (installed !== null && !controller.signal.aborted) {
369
+ input.backend.extensions.announceReleaseUpdate({ status: "installed", version: installed });
370
+ }
357
371
  }
358
372
  };
359
- watchFile(file, { interval: SHARED_SETTINGS_POLL_MS, persistent: false }, listener);
360
- return () => unwatchFile(file, listener);
373
+ return {
374
+ start() { void observe().catch(() => undefined); },
375
+ stop() { controller.abort(); },
376
+ };
377
+ }
378
+ export { watchSharedSettings } from "./owned-settings.js";
379
+ function settingsRouteOptions(tabBridge, keybindings, agentDir, cwd) {
380
+ return {
381
+ ...residentRouteHostOptions(tabBridge),
382
+ keybindings,
383
+ agentDir,
384
+ cwd,
385
+ copyText: text => {
386
+ if (Buffer.byteLength(text) <= MAX_CLIPBOARD_TEXT_BYTES)
387
+ void writeSystemClipboardText(text).catch(() => { });
388
+ },
389
+ };
361
390
  }
362
391
  function quitOutroSettingsSnapshot(settings) {
363
392
  return {
@@ -1,16 +1,25 @@
1
1
  import type { TabBridgeClient } from "../foundation/resident-tabs/bridge-client.js";
2
+ import { type ResidentTabRoute } from "../foundation/resident-tabs/routes.js";
2
3
  import type { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
3
4
  import type { PiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
4
5
  import type { ComposedSessionBackend } from "./session-backend.js";
6
+ import type { OwnedRouteHostOptions } from "./settings-route-host.js";
5
7
  /**
6
8
  * The keybindings the shells borrow. Inside a resident tab the host dispatches and labels the tab
7
9
  * shortcuts this child projects from its effective declarations, so the projection is published now
8
10
  * and again after every customization reload.
9
11
  */
10
12
  export declare function residentTabKeybindings(tabBridge: TabBridgeClient | null, agentDir: string, keybindingsHost: PiKeybindingsHost): PiKeybindingsHost;
13
+ /**
14
+ * How owned screens are presented in a resident tab: a Settings or Changelog tab's screen is the tab, and the Plus menu entry opens
15
+ * the tab bar's own Plus Menu dialog while this tab's controlling client can show it.
16
+ */
17
+ export declare function residentRouteHostOptions(tabBridge: TabBridgeClient | null): OwnedRouteHostOptions;
18
+ /** Closes a utility tab when its own screen closes. Invariant: a utility tab's screen is the tab. */
19
+ export declare function closeResidentUtilityRoute(tabBridge: Pick<TabBridgeClient, "tabId" | "requestClose">, route: string): void;
11
20
  export declare function bindResidentTabBridge(tabBridge: TabBridgeClient, backend: Pick<ComposedSessionBackend, "identity" | "session" | "workflows" | "generateResidentTabName">, recovery?: Pick<ResidentTabRecovery, "settled"> | null, options?: {
12
21
  readonly autoName?: boolean;
13
- readonly openRoute?: (route: "settings") => void;
22
+ readonly openRoute?: (route: ResidentTabRoute) => void;
14
23
  }): Promise<() => void>;
15
24
  export declare function correlateResidentTabSubmission(recovery: Pick<ResidentTabRecovery, "correlated" | "settled">, backend: Pick<ComposedSessionBackend, "identity" | "session">, submissionId: string, entryId: string): Promise<void>;
16
25
  export declare function currentBackendSessionFile(backend: Pick<ComposedSessionBackend, "identity">): string | null;
@@ -1,3 +1,4 @@
1
+ import { residentUtilityRoute } from "../foundation/resident-tabs/routes.js";
1
2
  import { deriveResidentTabStatus } from "../features/resident-tab/status-deriver.js";
2
3
  import { projectResidentTabShortcuts } from "../features/resident-tab/shortcut-projection.js";
3
4
  import { readShortcutBindingsFile } from "../features/resident-tab/shortcut-bindings-file.js";
@@ -16,6 +17,26 @@ export function residentTabKeybindings(tabBridge, agentDir, keybindingsHost) {
16
17
  publish();
17
18
  return { ...keybindingsHost, reload: () => { keybindingsHost.reload(); publish(); } };
18
19
  }
20
+ /**
21
+ * How owned screens are presented in a resident tab: a Settings or Changelog tab's screen is the tab, and the Plus menu entry opens
22
+ * the tab bar's own Plus Menu dialog while this tab's controlling client can show it.
23
+ */
24
+ export function residentRouteHostOptions(tabBridge) {
25
+ return {
26
+ ...(tabBridge === null ? {} : { tabRoute: residentUtilityRoute(tabBridge.tabId) }),
27
+ configurePlusMenu: () => {
28
+ if (tabBridge === null || !tabBridge.available || tabBridge.captureAttachment() === null)
29
+ return false;
30
+ void tabBridge.requestAction("configure-plus");
31
+ return true;
32
+ },
33
+ };
34
+ }
35
+ /** Closes a utility tab when its own screen closes. Invariant: a utility tab's screen is the tab. */
36
+ export function closeResidentUtilityRoute(tabBridge, route) {
37
+ if (residentUtilityRoute(tabBridge.tabId) === route)
38
+ void tabBridge.requestClose();
39
+ }
19
40
  export async function bindResidentTabBridge(tabBridge, backend, recovery = null, options = {}) {
20
41
  await tabBridge.start().catch(() => false);
21
42
  tabBridge.status("starting");
@@ -1,6 +1,7 @@
1
1
  import type { ReferenceDocumentProvider } from "../features/owned-ui/reference-screen-app.js";
2
2
  import type { OwnedSettingsManager } from "../ui/settings/manager.js";
3
3
  import type { UiRouteHost, UiRouteInput } from "../ui/apps/contracts.js";
4
+ import type { PiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
4
5
  /** Styled document rows for one content width. */
5
6
  export type OwnedReferenceRows = NonNullable<ReferenceDocumentProvider["rows"]>;
6
7
  /** Documents supplied by composition so the route host never reads shell data itself. */
@@ -11,8 +12,17 @@ export interface OwnedReferenceProviders {
11
12
  }
12
13
  /** How the settings route is presented. */
13
14
  export interface OwnedRouteHostOptions {
14
- /** The resident Settings tab: the screen is the tab, so it offers no screen-level close. */
15
- readonly settingsPinned?: boolean;
15
+ /**
16
+ * The route whose screen is this resident utility tab. That screen is the tab, so it offers no
17
+ * screen-level close: neither Esc nor one Ctrl+C dismisses it, and it shows no close hint.
18
+ */
19
+ readonly tabRoute?: string | null;
20
+ /** Opens the tab bar's own Plus Menu dialog; false when no tab bar can show it. */
21
+ readonly configurePlusMenu?: () => boolean;
22
+ readonly keybindings?: PiKeybindingsHost;
23
+ readonly agentDir?: string;
24
+ readonly cwd?: string;
25
+ readonly copyText?: (text: string) => void;
16
26
  }
17
27
  /** Declares settings and lazy document routes. */
18
28
  export declare function createOwnedRouteHost(settings: OwnedSettingsManager, references?: OwnedReferenceProviders, options?: OwnedRouteHostOptions): UiRouteHost;
@@ -11,13 +11,14 @@ export function createOwnedRouteHost(settings, references, options = {}) {
11
11
  if (!routes.has(route))
12
12
  return null;
13
13
  if (route === SETTINGS_ROUTE) {
14
- return deferredSurface(SETTINGS_APP_ID, "settings", () => loadSettingsSurface(settings, options.settingsPinned === true));
14
+ return deferredSurface(SETTINGS_APP_ID, "settings", () => loadSettingsSurface(settings, options));
15
15
  }
16
16
  const changelog = route === CHANGELOG_ROUTE;
17
17
  const session = route === "session";
18
18
  const title = session ? "Session Info" : changelog ? input ? "What's New" : "Changelog" : HOTKEYS_TITLE;
19
19
  const document = session ? references.session() : changelog ? references.changelog(input) : references.hotkeys();
20
- return deferredSurface(route, title, () => loadReferenceSurface(settings, route, route, title, document));
20
+ const pinned = options.tabRoute === route;
21
+ return deferredSurface(route, title, () => loadReferenceSurface(settings, route, title, document, pinned));
21
22
  },
22
23
  };
23
24
  }
@@ -61,12 +62,27 @@ function deferredSurface(id, label, load) {
61
62
  onExitRequested: listener => { onExit = listener; },
62
63
  };
63
64
  }
64
- async function loadSettingsSurface(settings, pinned) {
65
- const { SettingsApp } = await import("../features/owned-ui/settings-app.js");
66
- // Invariant: a pinned Settings tab closes as a tab; neither Esc nor one Ctrl+C dismisses its screen.
67
- return hostApp(SETTINGS_APP_ID, SETTINGS_ROUTE, () => new SettingsApp(settings, { pinned }), !pinned);
65
+ async function loadSettingsSurface(settings, options) {
66
+ const [{ SettingsApp }, { createSettingsSearchInputFactory }] = await Promise.all([
67
+ import("../features/owned-ui/settings-app.js"),
68
+ import("./settings-search-input.js"),
69
+ ]);
70
+ const pinned = options.tabRoute === SETTINGS_ROUTE;
71
+ const searchInputFactory = createSettingsSearchInputFactory({
72
+ ...(options.keybindings === undefined ? {} : { keybindings: options.keybindings }),
73
+ ...(options.agentDir === undefined ? {} : { agentDir: options.agentDir }),
74
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
75
+ });
76
+ // Invariant: a pinned Settings tab closes as a tab; neither Esc nor one unclaimed Ctrl+C dismisses its screen.
77
+ return hostApp(SETTINGS_APP_ID, SETTINGS_ROUTE, () => new SettingsApp(settings, {
78
+ pinned,
79
+ searchInputFactory,
80
+ configurePlusMenu: options.configurePlusMenu ?? null,
81
+ ...(options.copyText === undefined ? {} : { copyText: options.copyText }),
82
+ }), !pinned, true);
68
83
  }
69
- async function loadReferenceSurface(settings, id, route, title, document) {
84
+ async function loadReferenceSurface(settings, route, title, document, pinned) {
85
+ const id = route;
70
86
  // Rationale: the app module and the document load together, so a document that fails
71
87
  // to render is reported by the placeholder rather than by a screen that never fills.
72
88
  const [{ ReferenceScreenApp }, content] = await Promise.all([import("../features/owned-ui/reference-screen-app.js"), document]);
@@ -79,9 +95,10 @@ async function loadReferenceSurface(settings, id, route, title, document) {
79
95
  scrollbarStyle: settings.value("scrollbarStyle"),
80
96
  scrollbarSpeed: settings.value("scrollbarSpeed"),
81
97
  }),
82
- }));
98
+ pinned,
99
+ }), !pinned);
83
100
  }
84
- async function hostApp(id, route, create, closeOnInterrupt = true) {
101
+ async function hostApp(id, route, create, closeOnInterrupt = true, appInputBeforeInterrupt = false) {
85
102
  const [{ UiAppHost }, { UiAppRegistry }] = await Promise.all([
86
103
  import("../ui/apps/host.js"),
87
104
  import("../ui/apps/registry.js"),
@@ -96,6 +113,7 @@ async function hostApp(id, route, create, closeOnInterrupt = true) {
96
113
  const host = new UiAppHost({
97
114
  registry,
98
115
  closeOnInterrupt,
116
+ appInputBeforeInterrupt,
99
117
  theme: pinnedTheme(),
100
118
  surface: {
101
119
  size: () => size,