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

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 (124) hide show
  1. package/README.md +7 -1
  2. package/bin/automatic-update.js +7 -0
  3. package/bin/ui.js +1 -0
  4. package/dist/app/session-shell/resident-tab-coordinator.d.ts +6 -9
  5. package/dist/app/session-shell/resident-tab-coordinator.js +9 -52
  6. package/dist/app/session-shell/session-shell-root.d.ts +9 -3
  7. package/dist/app/session-shell/session-shell.js +70 -20
  8. package/dist/app/session-shell/session-viewport-controller.js +6 -4
  9. package/dist/app/session-shell/transcript-document.js +1 -1
  10. package/dist/composition/owned-ui.d.ts +3 -11
  11. package/dist/composition/owned-ui.js +93 -43
  12. package/dist/composition/resident-tab-bridge.d.ts +10 -1
  13. package/dist/composition/resident-tab-bridge.js +21 -0
  14. package/dist/composition/settings-route-host.d.ts +12 -2
  15. package/dist/composition/settings-route-host.js +27 -9
  16. package/dist/composition/settings-search-input.d.ts +9 -0
  17. package/dist/composition/settings-search-input.js +58 -0
  18. package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
  19. package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
  20. package/dist/contracts/presentation/index.d.ts +11 -0
  21. package/dist/contracts/presentation/index.js +12 -0
  22. package/dist/features/launch/development-launch.js +6 -0
  23. package/dist/features/launch/profile-paths.js +4 -0
  24. package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
  25. package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
  26. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
  27. package/dist/features/owned-ui/reference-screen-app.js +12 -2
  28. package/dist/features/owned-ui/settings-app.d.ts +10 -3
  29. package/dist/features/owned-ui/settings-app.js +98 -45
  30. package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
  31. package/dist/features/owned-ui/settings-search-input.js +1 -0
  32. package/dist/features/prompt-history/index.d.ts +1 -1
  33. package/dist/features/prompt-history/paths.d.ts +2 -1
  34. package/dist/features/prompt-history/paths.js +10 -2
  35. package/dist/features/prompt-history/service.d.ts +5 -0
  36. package/dist/features/prompt-history/service.js +1 -1
  37. package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
  38. package/dist/features/resident-tab/shortcut-declarations.js +2 -0
  39. package/dist/foundation/launch-context/index.d.ts +2 -0
  40. package/dist/foundation/launch-context/index.js +1 -0
  41. package/dist/foundation/release/automatic-update.d.ts +37 -0
  42. package/dist/foundation/release/automatic-update.js +265 -0
  43. package/dist/foundation/release/bootstrap.d.ts +1 -9
  44. package/dist/foundation/release/bootstrap.js +7 -3
  45. package/dist/foundation/release/index.d.ts +1 -1
  46. package/dist/foundation/release/index.js +1 -1
  47. package/dist/foundation/release/latest-release.d.ts +3 -2
  48. package/dist/foundation/release/latest-release.js +1 -1
  49. package/dist/foundation/release/update-execution-lease.d.ts +36 -0
  50. package/dist/foundation/release/update-execution-lease.js +145 -0
  51. package/dist/foundation/release/update.d.ts +8 -6
  52. package/dist/foundation/release/update.js +232 -167
  53. package/dist/foundation/resident-tabs/bridge-client.d.ts +9 -3
  54. package/dist/foundation/resident-tabs/bridge-client.js +14 -7
  55. package/dist/foundation/resident-tabs/routes.d.ts +5 -0
  56. package/dist/foundation/resident-tabs/routes.js +5 -0
  57. package/dist/foundation/resident-tabs/settings.d.ts +0 -1
  58. package/dist/foundation/resident-tabs/settings.js +0 -2
  59. package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
  60. package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
  61. package/dist/integrations/pi/components/modal-frame.js +48 -9
  62. package/dist/integrations/pi/components/models-dialog.js +4 -52
  63. package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
  64. package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
  65. package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
  66. package/dist/integrations/pi/components/shell-footer-status.js +19 -3
  67. package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
  68. package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
  69. package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
  70. package/dist/integrations/pi/components/skills-dialog.js +2 -2
  71. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
  72. package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
  73. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
  74. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
  75. package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
  76. package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
  77. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
  78. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
  79. package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
  80. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
  81. package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
  82. package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
  83. package/dist/integrations/pi/engine/adapter.js +7 -2
  84. package/dist/integrations/pi/engine/ports.d.ts +1 -1
  85. package/dist/integrations/pi/engine/ports.js +4 -0
  86. package/dist/integrations/pi/engine/workflow-runner.js +9 -2
  87. package/dist/native/darwin-arm64/manifest.json +1 -1
  88. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  89. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  90. package/dist/native/darwin-x64/manifest.json +1 -1
  91. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  92. package/dist/native/darwin-x64/terminal-host.node +0 -0
  93. package/dist/native/linux-x64/manifest.json +1 -1
  94. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  95. package/dist/native/linux-x64/terminal-host.node +0 -0
  96. package/dist/native/win32-x64/manifest.json +2 -2
  97. package/dist/native/win32-x64/process-guardian.node +0 -0
  98. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  99. package/dist/native/win32-x64/terminal-host.node +0 -0
  100. package/dist/product-identity.d.ts +1 -1
  101. package/dist/product-identity.js +1 -1
  102. package/dist/product-identity.json +1 -0
  103. package/dist/ui/apps/host.d.ts +2 -0
  104. package/dist/ui/apps/host.js +7 -1
  105. package/dist/ui/components/dialog-panel.js +35 -30
  106. package/dist/ui/components/line-input.d.ts +3 -0
  107. package/dist/ui/components/line-input.js +3 -1
  108. package/dist/ui/components/prompt-input.d.ts +5 -4
  109. package/dist/ui/components/prompt-input.js +8 -6
  110. package/dist/ui/components/submitted-prompt.d.ts +2 -2
  111. package/dist/ui/components/submitted-prompt.js +3 -3
  112. package/dist/ui/components/surface.js +4 -2
  113. package/dist/ui/components/transcript-viewport.d.ts +1 -1
  114. package/dist/ui/components/transcript-viewport.js +141 -24
  115. package/dist/ui/settings/declarations.d.ts +65 -15
  116. package/dist/ui/settings/declarations.js +45 -13
  117. package/dist/ui/settings/manager.js +7 -3
  118. package/dist/ui/settings/migrations.js +22 -0
  119. package/dist/ui/settings/sections.d.ts +4 -0
  120. package/dist/ui/settings/sections.js +9 -3
  121. package/docs/architecture/resident-tabs.md +39 -11
  122. package/docs/architecture/tool-image-presentation.md +2 -1
  123. package/docs/features/prompt-history.md +41 -20
  124. package/package.json +1 -1
@@ -6,11 +6,13 @@ import { PromptImageSidecar } from "../features/prompt-history/image-sidecar.js"
6
6
  import { resolvePromptHistoryPath } from "../features/prompt-history/paths.js";
7
7
  import { resolveLaunchProfilePaths, resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
8
8
  import { resolveProductPaths } from "../foundation/lifecycle/paths.js";
9
+ import { MAX_CLIPBOARD_TEXT_BYTES } from "../contracts/owned-ui/index.js";
9
10
  import { applyConfiguredPiTheme, getAvailablePiThemes, setPiAccentColor, setPiPackageBorderProjectionEnabled } from "../integrations/pi/components/upstream/theme/theme.js";
10
11
  import { createPiEngineHost } from "../integrations/pi/engine/host.js";
11
12
  import { createPiSessionPresenters } from "../integrations/pi/session-presenters/index.js";
12
13
  import { ClipboardDiagnosticCapture } from "../app/session-shell/clipboard-diagnostics.js";
13
14
  import { createOwnedUiClipboardServices } from "../app/session-shell/clipboard-services.js";
15
+ import { writeSystemClipboardText } from "../app/session-shell/system-clipboard.js";
14
16
  import { createPiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
15
17
  import { OwnedUiSessionPresenter, OwnedUiTerminalHost, sessionTerminalHostOptions, } from "../app/session-shell/session-shell.js";
16
18
  import { OwnedSettingsManager } from "../ui/settings/manager.js";
@@ -19,7 +21,7 @@ import { createOwnedRouteHost } from "./settings-route-host.js";
19
21
  import { renderPiShellChangelogLines } from "../integrations/pi/components/shell-presenters-info.js";
20
22
  import { nativeHyperlinkStyle } from "../ui/components/spans.js";
21
23
  import { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
22
- import { bindResidentTabBridge, correlateResidentTabSubmission, currentBackendSessionFile, residentTabKeybindings } from "./resident-tab-bridge.js";
24
+ import { bindResidentTabBridge, closeResidentUtilityRoute, correlateResidentTabSubmission, currentBackendSessionFile, residentRouteHostOptions, residentTabKeybindings } from "./resident-tab-bridge.js";
23
25
  import { SessionRuntimeRegistry } from "./session-runtime-registry.js";
24
26
  import { readPackageVersion, StartupReleaseNote } from "./startup-route.js";
25
27
  export { bindResidentTabBridge, correlateResidentTabSubmission } from "./resident-tab-bridge.js";
@@ -50,7 +52,7 @@ export async function composeOwnedUi(options = {}) {
50
52
  namespace: PRODUCT_IDENTITY.commandName,
51
53
  legacyConfigDir: productPaths.configDir,
52
54
  agentProvider: () => backend.settingsPort(),
53
- ...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsAutoName"] } : {}),
55
+ ...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsAutoName", "tabsPosition", "tabBarPlusMenu"] } : {}),
54
56
  hiddenAgentSettingIds: ["fullscreenWheelScrollLines"],
55
57
  agentSettingLabelOverrides: { fullscreenCopyOnSelect: "Copy on select" },
56
58
  });
@@ -130,12 +132,15 @@ export async function composeOwnedUi(options = {}) {
130
132
  host.setAccentColor(accent);
131
133
  tabBridge?.accent(accent);
132
134
  });
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
135
+ // Rationale: resident tabs and separate solo agents share a settings document; a live scope
136
+ // change saved in either reaches the others only by rereading that document.
137
+ const unwatchSharedSettings = settings === null || !ownedSurfaces
136
138
  ? () => { }
137
139
  : watchSharedSettings(settings);
138
140
  const unsubscribeAccent = () => { unwatchSharedSettings(); unsubscribeAccentChange(); };
141
+ // Invariant: the Settings search and agent prompt borrow the same resolved Pi keybindings.
142
+ const keybindingsHost = createPiKeybindingsHost({ profile: ownedSurfaces ? "a1" : "pi", agentDir: backend.identity.agentDir });
143
+ const keybindings = residentTabKeybindings(tabBridge, backend.identity.agentDir, keybindingsHost);
139
144
  // Rationale: Routes open only after shell construction.
140
145
  const references = {
141
146
  changelog: async (input) => {
@@ -151,9 +156,8 @@ export async function composeOwnedUi(options = {}) {
151
156
  },
152
157
  session: () => import("./session-info-reference.js").then(m => m.loadSessionInfoReference(backend)),
153
158
  };
154
- const routeHost = settings === null || !ownedSurfaces ? null : createOwnedRouteHost(settings, references, {
155
- settingsPinned: tabBridge?.tabId.startsWith("settings-") === true,
156
- });
159
+ const routeHost = settings === null || !ownedSurfaces ? null
160
+ : createOwnedRouteHost(settings, references, settingsRouteOptions(tabBridge, keybindings, backend.identity.agentDir, cwd));
157
161
  const viewportSettings = settings === null || !ownedSurfaces ? null : {
158
162
  snapshot: () => viewportSettingsSnapshot(settings),
159
163
  onChange: listener => settings.onChange(() => listener(viewportSettingsSnapshot(settings))),
@@ -181,26 +185,11 @@ export async function composeOwnedUi(options = {}) {
181
185
  // Rationale: one listener covers both the A1 presentation choice and the engine skill-command toggle.
182
186
  onChange: (listener) => settings.onChange(() => listener()),
183
187
  };
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
- };
188
+ const promptHistory = createPromptHistoryConfiguration(settings, backend, ownedSurfaces);
197
189
  const clipboardDestination = options.clipboardDiagnosticsPath ?? process.env[PRODUCT_IDENTITY.environment.clipboardDiagnostics];
198
190
  const clipboardDiagnostics = settings !== null && ownedSurfaces && clipboardDestination?.trim()
199
191
  ? 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);
192
+ // Invariant: process-wide services are created once here and borrowed by every session shell.
204
193
  const clipboardServices = ownedSurfaces ? createOwnedUiClipboardServices() : null;
205
194
  let createdTerminalHost;
206
195
  let terminalHost;
@@ -216,12 +205,8 @@ export async function composeOwnedUi(options = {}) {
216
205
  ...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
217
206
  ...(tabBridge === null || tabRecovery === null ? {} : { residentTab: {
218
207
  requestDetach: () => tabBridge.requestDetach(),
219
- closeOwnedRoute: route => {
220
- if (tabBridge.tabId.startsWith("settings-") && route === "settings")
221
- void tabBridge.requestClose();
222
- },
208
+ closeOwnedRoute: route => closeResidentUtilityRoute(tabBridge, route),
223
209
  reportStatus: status => tabBridge.status(status),
224
- stallNoticeSeconds: 300,
225
210
  admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
226
211
  dispatched: () => tabRecovery.dispatched(),
227
212
  correlated: (submissionId, entryId) => correlateResidentTabSubmission(tabRecovery, backend, submissionId, entryId),
@@ -291,18 +276,13 @@ export async function composeOwnedUi(options = {}) {
291
276
  // Rationale: pinned Pi's version check is unreachable because the owned shell never runs its
292
277
  // InteractiveMode, so A1 checks its own channel. The result is never awaited on the startup path;
293
278
  // 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
- };
279
+ const releaseUpdates = createStartupReleaseUpdateObservation({
280
+ backend,
281
+ configDir: productPaths.configDir,
282
+ mode: settings === null || !ownedSurfaces ? "auto" : settings.value("updateCheck"),
283
+ packageVersion: releaseNote.packageVersion, notificationVersion: options.notificationVersion,
284
+ checkForNewerRelease: options.checkForNewerRelease,
285
+ });
306
286
  const exitNoticePath = process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
307
287
  // Security: tools the agent runs must never see, or rewrite, this instance's notice.
308
288
  delete process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
@@ -315,11 +295,12 @@ export async function composeOwnedUi(options = {}) {
315
295
  exitNotice = import("../app/session-shell/exit-notice.js")
316
296
  .then(module => module.armExitNotice(backend, exitNoticePath), () => null);
317
297
  presenter.start();
318
- void announceNewerRelease().catch(() => undefined);
298
+ releaseUpdates.start();
319
299
  },
320
300
  flush: () => backend.session.flushEvents(),
321
301
  waitUntilStopped: () => presenter.waitUntilStopped(),
322
302
  dispose: async () => {
303
+ releaseUpdates.stop();
323
304
  try {
324
305
  await terminalHost.dispose();
325
306
  // Invariant: cleared only once the terminal is restored; a failed dispose leaves it armed.
@@ -344,6 +325,63 @@ export async function composeOwnedUi(options = {}) {
344
325
  };
345
326
  return { application, settings };
346
327
  }
328
+ function createPromptHistoryConfiguration(settings, backend, ownedSurfaces) {
329
+ const limit = settings?.value("promptHistoryMaxItems") ?? 100;
330
+ const scope = settings?.value("promptHistoryScope") ?? "unique";
331
+ const enabled = settings !== null && ownedSurfaces && settings.value("promptHistoryEnabled");
332
+ const sessionId = enabled ? backend.identity.currentHistorySessionId() : null;
333
+ if (settings === null || !enabled || (scope === "unique" && sessionId === null))
334
+ return null;
335
+ const dataDir = resolvePromptHistoryDataDir();
336
+ const forScope = (selected, id) => {
337
+ if (selected === "unique" && id === null)
338
+ return null;
339
+ const location = resolvePromptHistoryPath(dataDir, backend.identity.agentDir, process.platform, selected, id ?? "");
340
+ return {
341
+ store: new PromptHistoryService({ dataDir, profileRoot: backend.identity.agentDir, limit, scope: selected, sessionId: id ?? "" }),
342
+ imageSidecar: new PromptImageSidecar(location.imagesDir),
343
+ };
344
+ };
345
+ return {
346
+ limit, scope,
347
+ ...(sessionId === null ? {} : { sessionId }),
348
+ forScope,
349
+ onScopeChange: (listener) => settings.onChange(() => listener(settings.value("promptHistoryScope"))),
350
+ ...forScope(scope, sessionId),
351
+ };
352
+ }
353
+ function createStartupReleaseUpdateObservation(input) {
354
+ const controller = new AbortController();
355
+ const observe = async () => {
356
+ const check = input.checkForNewerRelease
357
+ ?? (await import("../foundation/release/latest-release.js")).checkForNewerRelease;
358
+ const runningVersion = input.packageVersion ?? await readPackageVersion();
359
+ const discoveryVersion = input.mode === "notify" ? input.notificationVersion ?? runningVersion : runningVersion;
360
+ const release = await check({
361
+ runningVersion: discoveryVersion,
362
+ configDir: input.configDir,
363
+ interactive: process.stdout.isTTY === true,
364
+ settingMode: input.mode,
365
+ });
366
+ if (release === null || controller.signal.aborted)
367
+ return;
368
+ if (input.mode === "notify") {
369
+ input.backend.extensions.announceReleaseUpdate(release);
370
+ return;
371
+ }
372
+ if (input.mode === "auto") {
373
+ const installed = await import("../foundation/release/automatic-update.js")
374
+ .then(module => module.requestAutomaticUpdate({ release, runningVersion, signal: controller.signal }));
375
+ if (installed !== null && !controller.signal.aborted) {
376
+ input.backend.extensions.announceReleaseUpdate({ status: "installed", version: installed });
377
+ }
378
+ }
379
+ };
380
+ return {
381
+ start() { void observe().catch(() => undefined); },
382
+ stop() { controller.abort(); },
383
+ };
384
+ }
347
385
  const SHARED_SETTINGS_POLL_MS = 250;
348
386
  /** Rereads the shared settings document whenever its stat changes; returns the stop. */
349
387
  export function watchSharedSettings(settings) {
@@ -359,6 +397,18 @@ export function watchSharedSettings(settings) {
359
397
  watchFile(file, { interval: SHARED_SETTINGS_POLL_MS, persistent: false }, listener);
360
398
  return () => unwatchFile(file, listener);
361
399
  }
400
+ function settingsRouteOptions(tabBridge, keybindings, agentDir, cwd) {
401
+ return {
402
+ ...residentRouteHostOptions(tabBridge),
403
+ keybindings,
404
+ agentDir,
405
+ cwd,
406
+ copyText: text => {
407
+ if (Buffer.byteLength(text) <= MAX_CLIPBOARD_TEXT_BYTES)
408
+ void writeSystemClipboardText(text).catch(() => { });
409
+ },
410
+ };
411
+ }
362
412
  function quitOutroSettingsSnapshot(settings) {
363
413
  return {
364
414
  enabled: settings.value("quitAnimation"),
@@ -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
+ }
@@ -37,6 +37,8 @@ export interface OwnedUiSessionIdentityPort {
37
37
  readonly sessionBindingGeneration: number;
38
38
  readonly disposed: boolean;
39
39
  currentSessionFile(): string | null;
40
+ /** The durable conversation identity; unlike sessionId it survives agent process restart. */
41
+ currentHistorySessionId(): string | null;
40
42
  currentSessionResumeMetadata(): OwnedUiSessionResumeMetadata | null;
41
43
  }
42
44
  /** The session lifecycle, its view, its event stream, and command admission. */
@@ -337,9 +337,13 @@ export interface OwnedUiVisualExtensionSupport {
337
337
  readonly renderCallbacks: typeof OWNED_UI_EXTENSION_RENDER_CALLBACKS;
338
338
  readonly diagnostic: string;
339
339
  }
340
- /** A newer release found by the startup check. */
341
- export interface OwnedUiReleaseUpdate {
340
+ /** A newer release found by the startup check, or one installed for the next restart. */
341
+ export type OwnedUiReleaseUpdate = {
342
+ readonly status?: "available";
342
343
  readonly version: string;
343
344
  readonly command: string;
344
345
  readonly changelogUrl: string | null;
345
- }
346
+ } | {
347
+ readonly status: "installed";
348
+ readonly version: string;
349
+ };
@@ -14,6 +14,17 @@ export declare const DIALOG_BACK_SHORTCUT_HINT: Readonly<{
14
14
  key: string;
15
15
  action: string;
16
16
  }>;
17
+ /** Global inset inherited by every semantic row inside an A1-owned dialog frame. */
18
+ export declare const DIALOG_CONTENT_PADDING_X = 1;
19
+ export interface DialogFrameGeometry {
20
+ readonly frameWidth: number;
21
+ readonly contentPaddingX: number;
22
+ readonly contentWidth: number;
23
+ }
24
+ /** Shared full-width-rule/content-width geometry for string and component dialog renderers. */
25
+ export declare function dialogFrameGeometry(width: number): DialogFrameGeometry;
26
+ /** Injects a semantic row into the frame's global content inset. */
27
+ export declare function insetDialogContent(row: string, geometry: DialogFrameGeometry): string;
17
28
  export interface ShortcutHintRoles {
18
29
  key(label: string): string;
19
30
  action(name: string): string;
@@ -2,6 +2,18 @@
2
2
  export const DIALOG_CLOSE_SHORTCUT_HINT = Object.freeze({ key: "esc", action: "close" });
3
3
  /** Canonical return guidance for a nested step within a dialog surface. */
4
4
  export const DIALOG_BACK_SHORTCUT_HINT = Object.freeze({ key: "esc", action: "back" });
5
+ /** Global inset inherited by every semantic row inside an A1-owned dialog frame. */
6
+ export const DIALOG_CONTENT_PADDING_X = 1;
7
+ /** Shared full-width-rule/content-width geometry for string and component dialog renderers. */
8
+ export function dialogFrameGeometry(width) {
9
+ const frameWidth = Math.max(0, width);
10
+ const contentPaddingX = Math.min(DIALOG_CONTENT_PADDING_X, frameWidth);
11
+ return { frameWidth, contentPaddingX, contentWidth: frameWidth - contentPaddingX };
12
+ }
13
+ /** Injects a semantic row into the frame's global content inset. */
14
+ export function insetDialogContent(row, geometry) {
15
+ return `${" ".repeat(geometry.contentPaddingX)}${row}`;
16
+ }
5
17
  const SHORTCUT_KEY_NAMES = Object.freeze({
6
18
  alt: "Alt", backspace: "Backspace", cmd: "Cmd", ctrl: "Ctrl", delete: "Delete", down: "Down", end: "End",
7
19
  enter: "Enter", esc: "Esc", escape: "Escape", home: "Home", insert: "Insert", left: "Left", meta: "Meta",
@@ -19,6 +19,11 @@ export function resolveDevelopmentLaunchEnvironment(canonicalPackageRoot, releas
19
19
  ? join(developmentRoot, "config")
20
20
  : join(checkoutRoot, "config");
21
21
  const dataDir = resolve(source[PRODUCT_IDENTITY.environment.dataDir] ?? join(developmentRoot, "data"));
22
+ // Rationale: solo processes keep isolated control state but must see the same history
23
+ // after selecting Shared; use a checkout-wide root across rebuilds unless overridden.
24
+ const historyDataDir = resolve(source[PRODUCT_IDENTITY.environment.developmentHistoryDataDir]
25
+ ?? source[PRODUCT_IDENTITY.environment.dataDir]
26
+ ?? (selectedDevelopmentRoot ? dataDir : join(checkoutRoot, "data")));
22
27
  return {
23
28
  checkoutId,
24
29
  releaseId,
@@ -27,6 +32,7 @@ export function resolveDevelopmentLaunchEnvironment(canonicalPackageRoot, releas
27
32
  environment: {
28
33
  ...source,
29
34
  [PRODUCT_IDENTITY.environment.developmentInstanceId]: instanceId,
35
+ [PRODUCT_IDENTITY.environment.developmentHistoryDataDir]: historyDataDir,
30
36
  [PRODUCT_IDENTITY.environment.configDir]: resolve(source[PRODUCT_IDENTITY.environment.configDir] ?? persistentConfigDir),
31
37
  [PRODUCT_IDENTITY.environment.dataDir]: dataDir,
32
38
  [PRODUCT_IDENTITY.environment.runtimeDir]: resolve(source[PRODUCT_IDENTITY.environment.runtimeDir] ?? join(developmentRoot, "runtime")),
@@ -18,6 +18,10 @@ export function resolveLaunchProfilePaths(options = {}) {
18
18
  export function resolvePromptHistoryDataDir(options = {}) {
19
19
  const environment = options.environment ?? process.env;
20
20
  const platform = options.platform ?? process.platform;
21
+ const developmentHistoryDataDir = environment[PRODUCT_IDENTITY.environment.developmentHistoryDataDir];
22
+ if (developmentHistoryDataDir !== undefined) {
23
+ return resolveProductPaths({ ...environment, [PRODUCT_IDENTITY.environment.dataDir]: developmentHistoryDataDir }, platform).dataDir;
24
+ }
21
25
  if (environment[PRODUCT_IDENTITY.environment.dataDir] !== undefined) {
22
26
  return resolveProductPaths(environment, platform).dataDir;
23
27
  }
@@ -0,0 +1,22 @@
1
+ import type { UiTheme } from "../../ui/components/index.js";
2
+ /** The A1 setting holding the tab kinds the strip's `+` offers. */
3
+ export declare const PLUS_MENU_SETTING_ID = "tabBarPlusMenu";
4
+ /** One tab kind the plus menu can offer. */
5
+ export interface PlusMenuKind {
6
+ readonly key: string;
7
+ readonly label: string;
8
+ }
9
+ /**
10
+ * The plus-menu dialog: every tab kind with `●` when `+` offers it and `○` when not, filtered by typed
11
+ * text. It owns only its filter and the row in hand; the enabled record and its saving stay with the
12
+ * Settings screen that opened it.
13
+ */
14
+ export declare class PlusMenuDialog {
15
+ #private;
16
+ constructor(kinds: readonly PlusMenuKind[]);
17
+ /** The kinds the filter keeps, in kind order. */
18
+ shown(): readonly PlusMenuKind[];
19
+ /** Handles one key by its resolved name; `toggle` flips a kind. Returns whether the dialog closed. */
20
+ handle(name: string, data: string, toggle: (key: string) => void): "close" | "handled";
21
+ lines(enabled: Readonly<Record<string, boolean | string>>, width: number, theme: UiTheme): readonly string[];
22
+ }