@timurproko/a1 0.2.7-dev.820 → 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 (157) 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/session-shell-root.d.ts +13 -6
  7. package/dist/app/session-shell/session-shell-root.js +9 -5
  8. package/dist/app/session-shell/session-shell.js +68 -18
  9. package/dist/app/session-shell/shell-host.js +6 -3
  10. package/dist/app/session-shell/transcript-document.js +1 -1
  11. package/dist/cli/tabs.d.ts +1 -1
  12. package/dist/cli/tabs.js +10 -14
  13. package/dist/composition/owned-settings.d.ts +12 -0
  14. package/dist/composition/owned-settings.js +36 -0
  15. package/dist/composition/owned-ui.d.ts +5 -14
  16. package/dist/composition/owned-ui.js +93 -63
  17. package/dist/composition/resident-tab-bridge.d.ts +10 -1
  18. package/dist/composition/resident-tab-bridge.js +21 -0
  19. package/dist/composition/settings-route-host.d.ts +12 -2
  20. package/dist/composition/settings-route-host.js +27 -9
  21. package/dist/composition/settings-search-input.d.ts +9 -0
  22. package/dist/composition/settings-search-input.js +58 -0
  23. package/dist/composition/utility-tab.d.ts +28 -0
  24. package/dist/composition/utility-tab.js +242 -0
  25. package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
  26. package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
  27. package/dist/contracts/presentation/index.d.ts +11 -0
  28. package/dist/contracts/presentation/index.js +12 -0
  29. package/dist/features/launch/development-launch.js +6 -0
  30. package/dist/features/launch/profile-paths.js +4 -0
  31. package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
  32. package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
  33. package/dist/features/owned-ui/project-trust-dialog.d.ts +1 -1
  34. package/dist/features/owned-ui/project-trust-dialog.js +6 -8
  35. package/dist/features/owned-ui/project-trust-prompt.d.ts +1 -0
  36. package/dist/features/owned-ui/project-trust-prompt.js +7 -3
  37. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
  38. package/dist/features/owned-ui/reference-screen-app.js +12 -2
  39. package/dist/features/owned-ui/settings-app.d.ts +10 -3
  40. package/dist/features/owned-ui/settings-app.js +98 -45
  41. package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
  42. package/dist/features/owned-ui/settings-search-input.js +1 -0
  43. package/dist/features/prompt-history/index.d.ts +1 -1
  44. package/dist/features/prompt-history/paths.d.ts +2 -1
  45. package/dist/features/prompt-history/paths.js +10 -2
  46. package/dist/features/prompt-history/service.d.ts +5 -0
  47. package/dist/features/prompt-history/service.js +1 -1
  48. package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
  49. package/dist/features/resident-tab/shortcut-declarations.js +2 -0
  50. package/dist/foundation/launch-context/index.d.ts +2 -0
  51. package/dist/foundation/launch-context/index.js +1 -0
  52. package/dist/foundation/lifecycle/session-repository-context.js +16 -2
  53. package/dist/foundation/release/automatic-update.d.ts +37 -0
  54. package/dist/foundation/release/automatic-update.js +265 -0
  55. package/dist/foundation/release/bootstrap.d.ts +1 -9
  56. package/dist/foundation/release/bootstrap.js +7 -3
  57. package/dist/foundation/release/early-accent.d.ts +12 -0
  58. package/dist/foundation/release/early-accent.js +59 -0
  59. package/dist/foundation/release/index.d.ts +1 -1
  60. package/dist/foundation/release/index.js +1 -1
  61. package/dist/foundation/release/latest-release.d.ts +3 -2
  62. package/dist/foundation/release/latest-release.js +1 -1
  63. package/dist/foundation/release/progress-palette.generated.d.ts +137 -3
  64. package/dist/foundation/release/progress-palette.generated.js +137 -3
  65. package/dist/foundation/release/release-gc.js +19 -4
  66. package/dist/foundation/release/update-execution-lease.d.ts +36 -0
  67. package/dist/foundation/release/update-execution-lease.js +145 -0
  68. package/dist/foundation/release/update.d.ts +9 -7
  69. package/dist/foundation/release/update.js +244 -175
  70. package/dist/foundation/resident-tabs/bridge-client.d.ts +9 -2
  71. package/dist/foundation/resident-tabs/bridge-client.js +19 -7
  72. package/dist/foundation/resident-tabs/maintenance.d.ts +2 -6
  73. package/dist/foundation/resident-tabs/maintenance.js +5 -23
  74. package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +34 -0
  75. package/dist/foundation/resident-tabs/progress-observation-schema.js +1 -0
  76. package/dist/foundation/resident-tabs/progress-observation.d.ts +3 -6
  77. package/dist/foundation/resident-tabs/progress-observation.js +43 -12
  78. package/dist/foundation/resident-tabs/release-holds.js +3 -14
  79. package/dist/foundation/resident-tabs/routes.d.ts +5 -0
  80. package/dist/foundation/resident-tabs/routes.js +5 -0
  81. package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
  82. package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
  83. package/dist/integrations/pi/components/modal-frame.js +48 -9
  84. package/dist/integrations/pi/components/models-dialog.js +4 -52
  85. package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
  86. package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
  87. package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
  88. package/dist/integrations/pi/components/shell-footer-status.js +19 -3
  89. package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
  90. package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
  91. package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
  92. package/dist/integrations/pi/components/skills-dialog.js +2 -2
  93. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
  94. package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
  95. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
  96. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
  97. package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
  98. package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
  99. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
  100. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
  101. package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
  102. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
  103. package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
  104. package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
  105. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +2 -0
  106. package/dist/integrations/pi/components/upstream/theme/theme.js +5 -1
  107. package/dist/integrations/pi/engine/adapter.js +7 -2
  108. package/dist/integrations/pi/engine/offline-settings-port.d.ts +23 -0
  109. package/dist/integrations/pi/engine/offline-settings-port.js +53 -0
  110. package/dist/integrations/pi/engine/ports.d.ts +1 -1
  111. package/dist/integrations/pi/engine/ports.js +4 -0
  112. package/dist/integrations/pi/engine/settings-bridge.d.ts +5 -0
  113. package/dist/integrations/pi/engine/settings-bridge.js +13 -0
  114. package/dist/integrations/pi/engine/settings-port.js +1 -14
  115. package/dist/integrations/pi/engine/workflow-runner.js +9 -2
  116. package/dist/native/darwin-arm64/manifest.json +1 -1
  117. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  118. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  119. package/dist/native/darwin-x64/manifest.json +1 -1
  120. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  121. package/dist/native/darwin-x64/terminal-host.node +0 -0
  122. package/dist/native/linux-x64/manifest.json +1 -1
  123. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  124. package/dist/native/linux-x64/terminal-host.node +0 -0
  125. package/dist/native/win32-x64/manifest.json +2 -2
  126. package/dist/native/win32-x64/process-guardian.node +0 -0
  127. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  128. package/dist/native/win32-x64/terminal-host.node +0 -0
  129. package/dist/product-identity.d.ts +1 -1
  130. package/dist/product-identity.js +2 -2
  131. package/dist/product-identity.json +2 -0
  132. package/dist/ui/apps/host.d.ts +2 -0
  133. package/dist/ui/apps/host.js +7 -1
  134. package/dist/ui/components/dialog-panel.js +35 -30
  135. package/dist/ui/components/line-input.d.ts +3 -0
  136. package/dist/ui/components/line-input.js +3 -1
  137. package/dist/ui/components/prompt-input.d.ts +3 -2
  138. package/dist/ui/components/prompt-input.js +6 -4
  139. package/dist/ui/components/spans.js +2 -2
  140. package/dist/ui/components/surface.js +4 -2
  141. package/dist/ui/components/text.d.ts +3 -6
  142. package/dist/ui/components/text.js +6 -8
  143. package/dist/ui/settings/declarations.d.ts +56 -10
  144. package/dist/ui/settings/declarations.js +38 -9
  145. package/dist/ui/settings/early-accent.d.ts +81 -0
  146. package/dist/ui/settings/early-accent.js +50 -0
  147. package/dist/ui/settings/manager.js +7 -3
  148. package/dist/ui/settings/migrations.js +15 -0
  149. package/dist/ui/settings/progress-palette.generated.d.ts +139 -0
  150. package/dist/ui/settings/progress-palette.generated.js +139 -0
  151. package/dist/ui/settings/sections.d.ts +4 -0
  152. package/dist/ui/settings/sections.js +9 -3
  153. package/docs/architecture/resident-tabs.md +48 -17
  154. package/docs/architecture/tool-image-presentation.md +2 -1
  155. package/docs/features/prompt-history.md +41 -20
  156. package/docs/validation.md +25 -5
  157. package/package.json +1 -1
@@ -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", "tabsPosition"] } : {}),
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,10 +198,7 @@ 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
203
  admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
225
204
  dispatched: () => tabRecovery.dispatched(),
@@ -290,18 +269,13 @@ export async function composeOwnedUi(options = {}) {
290
269
  // Rationale: pinned Pi's version check is unreachable because the owned shell never runs its
291
270
  // InteractiveMode, so A1 checks its own channel. The result is never awaited on the startup path;
292
271
  // settings are read here because the runner resolves them only just before start.
293
- const announceNewerRelease = async () => {
294
- const check = options.checkForNewerRelease
295
- ?? (await import("../foundation/release/latest-release.js")).checkForNewerRelease;
296
- const release = await check({
297
- runningVersion: releaseNote.packageVersion ?? await readPackageVersion(),
298
- configDir: productPaths.configDir,
299
- interactive: process.stdout.isTTY === true,
300
- ...(settings === null || !ownedSurfaces ? {} : { settingEnabled: settings.value("updateCheck") }),
301
- });
302
- if (release !== null)
303
- backend.extensions.announceReleaseUpdate(release);
304
- };
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
+ });
305
279
  const exitNoticePath = process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
306
280
  // Security: tools the agent runs must never see, or rewrite, this instance's notice.
307
281
  delete process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
@@ -314,11 +288,12 @@ export async function composeOwnedUi(options = {}) {
314
288
  exitNotice = import("../app/session-shell/exit-notice.js")
315
289
  .then(module => module.armExitNotice(backend, exitNoticePath), () => null);
316
290
  presenter.start();
317
- void announceNewerRelease().catch(() => undefined);
291
+ releaseUpdates.start();
318
292
  },
319
293
  flush: () => backend.session.flushEvents(),
320
294
  waitUntilStopped: () => presenter.waitUntilStopped(),
321
295
  dispose: async () => {
296
+ releaseUpdates.stop();
322
297
  try {
323
298
  await terminalHost.dispose();
324
299
  // Invariant: cleared only once the terminal is restored; a failed dispose leaves it armed.
@@ -343,20 +318,75 @@ export async function composeOwnedUi(options = {}) {
343
318
  };
344
319
  return { application, settings };
345
320
  }
346
- const SHARED_SETTINGS_POLL_MS = 250;
347
- /** Rereads the shared settings document whenever its stat changes; returns the stop. */
348
- export function watchSharedSettings(settings) {
349
- const file = settings.file;
350
- const listener = () => {
351
- try {
352
- 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;
353
364
  }
354
- catch {
355
- // 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
+ }
356
371
  }
357
372
  };
358
- watchFile(file, { interval: SHARED_SETTINGS_POLL_MS, persistent: false }, listener);
359
- 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
+ };
360
390
  }
361
391
  function quitOutroSettingsSnapshot(settings) {
362
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,
@@ -0,0 +1,9 @@
1
+ import type { SettingsSearchInputFactory } from "../features/owned-ui/settings-search-input.js";
2
+ import { type PiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
3
+ export interface SettingsSearchInputFactoryOptions {
4
+ readonly keybindings?: PiKeybindingsHost;
5
+ readonly agentDir?: string;
6
+ readonly cwd?: string;
7
+ }
8
+ /** Builds Settings search from the same configurable owned editor used by the agent prompt. */
9
+ export declare function createSettingsSearchInputFactory(options?: SettingsSearchInputFactoryOptions): SettingsSearchInputFactory;
@@ -0,0 +1,58 @@
1
+ import { editorInsertedText } from "../integrations/pi/components/owned-editor-ux.js";
2
+ import { createPiShellEditor } from "../integrations/pi/components/shell-editor-autocomplete.js";
3
+ import { piShellTruncateToWidth, piShellVisibleWidth, } from "../integrations/pi/components/shell-shared-facade.js";
4
+ import { backgroundSgrSpan } from "../ui/components/spans.js";
5
+ import { caretCell } from "../ui/components/line-input.js";
6
+ import { PromptInput } from "../ui/components/prompt-input.js";
7
+ /** Builds Settings search from the same configurable owned editor used by the agent prompt. */
8
+ export function createSettingsSearchInputFactory(options = {}) {
9
+ return {
10
+ isTextInput: data => {
11
+ const text = editorInsertedText(data);
12
+ return text !== undefined && /[^\p{Cc}]/u.test(text);
13
+ },
14
+ create: surface => createSettingsSearchInput(surface, options),
15
+ };
16
+ }
17
+ function createSettingsSearchInput(surface, options) {
18
+ let columns = 80;
19
+ const editor = createPiShellEditor({
20
+ keybindingProfile: "a1",
21
+ inputCapabilities: {
22
+ singleLine: true,
23
+ paste: false,
24
+ autocomplete: false,
25
+ placeholder: "Search settings",
26
+ },
27
+ ...(options.keybindings === undefined ? {} : { keybindings: options.keybindings }),
28
+ ...(options.agentDir === undefined ? {} : { agentDir: options.agentDir }),
29
+ cwd: options.cwd ?? process.cwd(),
30
+ getColumns: () => columns,
31
+ getRows: surface.getRows,
32
+ requestRender: surface.requestRender,
33
+ onSubmit: () => { },
34
+ onCopyText: text => surface.copyText(text),
35
+ paintEditorSelection: (line, from, to, atomic) => backgroundSgrSpan(line, from, to, atomic ? "\u001b[7m" : "\u001b[27m\u001b[48;2;38;79;120m", atomic ? "\u001b[27m" : "\u001b[49m", piShellVisibleWidth),
36
+ promptPresentation: {
37
+ input: new PromptInput({ fg: (token, text) => surface.theme.fg(token, text) }, {
38
+ measure: piShellVisibleWidth,
39
+ truncate: piShellTruncateToWidth,
40
+ }),
41
+ styleSuggestion: text => surface.theme.fg("dim", text),
42
+ styleSuggestionCaret: caretCell,
43
+ },
44
+ });
45
+ editor.setFocused?.(true);
46
+ return {
47
+ render(width) {
48
+ columns = width;
49
+ return editor.render(width);
50
+ },
51
+ handleInput: (data) => editor.handleInput?.(data),
52
+ handlePointer: (event) => editor.handlePointer(event),
53
+ getText: () => editor.getText(),
54
+ hasSelection: () => editor.hasSelection(),
55
+ ownsPointer: () => editor.ownsPointer(),
56
+ setFocused: (focused) => editor.setFocused?.(focused),
57
+ };
58
+ }
@@ -0,0 +1,28 @@
1
+ import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contracts/presentation/index.js";
2
+ import type { TabBridgeClient } from "../foundation/resident-tabs/bridge-client.js";
3
+ import type { ResidentTabRoute } from "../foundation/resident-tabs/routes.js";
4
+ import type { ReleaseNoteCatalog } from "../features/owned-ui/release-notes.js";
5
+ import type { UiRouteHost } from "../ui/apps/contracts.js";
6
+ import type { OwnedSettingsManager } from "../ui/settings/manager.js";
7
+ import { terminateOwnedUiProcess } from "../features/owned-ui/run.js";
8
+ export { terminateOwnedUiProcess };
9
+ export interface UtilityTabOptions {
10
+ readonly route: ResidentTabRoute;
11
+ readonly bridge: TabBridgeClient;
12
+ readonly cwd?: string;
13
+ readonly profileId?: string;
14
+ readonly terminal?: PresentationTerminalPort;
15
+ readonly agentDir?: string;
16
+ readonly settings?: OwnedSettingsManager;
17
+ readonly routeHost?: UiRouteHost;
18
+ readonly releaseNotes?: ReleaseNoteCatalog;
19
+ readonly onApplication?: (application: OwnedUiApplicationPort) => void;
20
+ }
21
+ export interface UtilityTabComposition {
22
+ readonly application: OwnedUiApplicationPort;
23
+ readonly settings: OwnedSettingsManager;
24
+ }
25
+ /** Runs one pinned Settings or Changelog screen without constructing an Agent engine or session. */
26
+ export declare function runUtilityTab(options: UtilityTabOptions): Promise<number>;
27
+ export declare function composeUtilityTab(options: UtilityTabOptions): Promise<UtilityTabComposition>;
28
+ export declare function utilityAgentDir(environment?: NodeJS.ProcessEnv): string;