@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
package/README.md CHANGED
@@ -145,7 +145,13 @@ Stuck on `0.2.2` and `a1 update` fails? Run the installer once, then use `a1 upd
145
145
  npm x -y -- @timurproko/a1-install
146
146
  ```
147
147
 
148
- A1 shows an `Update Available` notice at most once a day and never installs anything by itself. Turn it off in Settings (`Update check`) or with `A1_SKIP_VERSION_CHECK=1`.
148
+ The **Update** setting controls the at-most-daily startup check:
149
+
150
+ - **Auto** (default) silently installs a verified update for the running channel, then shows `Update installed · Restart to update`.
151
+ - **Notify** keeps the `Update Available` notice and leaves installation manual.
152
+ - **No** performs no startup update check; updates remain available through the commands above.
153
+
154
+ `A1_SKIP_VERSION_CHECK=1` also disables the startup check. Manual stable, development, and exact-version update commands remain available in every mode.
149
155
 
150
156
  <p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/assets/readme/separator-dark.svg"><img src="docs/assets/readme/separator.svg" alt="" width="280"></picture></p>
151
157
 
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ import("../dist/foundation/release/automatic-update.js").then(async ({ runAutomaticUpdateWorker }) => {
4
+ process.exitCode = await runAutomaticUpdateWorker(process.argv.slice(2));
5
+ }).catch(() => {
6
+ process.exitCode = 1;
7
+ });
@@ -2,6 +2,7 @@ export interface TabLaunchCredentials {
2
2
  readonly tabId: string;
3
3
  readonly token: string;
4
4
  readonly endpoint: string;
5
+ readonly releaseId?: string;
5
6
  }
6
7
 
7
8
  export function formatResidentFallbackNotice(reason: string): string;
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
 
3
- const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT"];
3
+ const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT", "A1_TAB_RELEASE_ID"];
4
4
 
5
5
  /** @param {string} reason */
6
6
  export function formatResidentFallbackNotice(reason) {
@@ -83,12 +83,12 @@ export function consumeTabLaunchContext(arguments_, environment) {
83
83
  const incarnationArgument = uiArguments.indexOf("--tab-incarnation");
84
84
  const tabIncarnation = incarnationArgument === -1 ? undefined : Number(uiArguments[incarnationArgument + 1]);
85
85
  if (incarnationArgument !== -1) uiArguments.splice(incarnationArgument, 2);
86
- const [tabId, token, endpoint] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
86
+ const [tabId, token, endpoint, releaseId] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
87
87
  for (const name of TAB_ENVIRONMENT_NAMES) delete environment[name];
88
88
  if (!tabMode) return { uiArguments, tabCredentials: undefined, tabStandby: false };
89
89
  if (!tabId || !token || !endpoint || !tabDataDir || tabDataDir.includes("\0")
90
90
  || typeof tabIncarnation !== "number" || !Number.isSafeInteger(tabIncarnation) || tabIncarnation < 1) {
91
91
  throw new Error("A1 tab mode requires bridge credentials, recovery directory, and incarnation");
92
92
  }
93
- return { uiArguments, tabCredentials: { tabId, token, endpoint }, tabDataDir, tabIncarnation, tabStandby };
93
+ return { uiArguments, tabCredentials: { tabId, token, endpoint, ...(releaseId === undefined ? {} : { releaseId }) }, tabDataDir, tabIncarnation, tabStandby };
94
94
  }
package/bin/ui.js CHANGED
@@ -7,6 +7,9 @@ const { uiArguments, tabCredentials, tabDataDir, tabIncarnation, tabStandby } =
7
7
  // Invariant: one terminal module identity per process, decided before any A1 or Pi module loads.
8
8
  const { installPinnedPiTuiResolver } = await import("./module-resolver.js");
9
9
  installPinnedPiTuiResolver(new URL("..", import.meta.url).pathname.replace(/^\/(.:)/, "$1").replace(/\/$/, ""));
10
+ const utilityRoute = tabCredentials === undefined
11
+ ? null
12
+ : (await import("../dist/foundation/resident-tabs/routes.js")).residentUtilityRoute(tabCredentials.tabId);
10
13
  const startup = await import("../dist/foundation/startup/startup-runtime.js");
11
14
  startup.enableEnvironmentCompileCache(process.env);
12
15
  const { hidesTabSettings, readLaunchContext } = await import("../dist/foundation/launch-context/index.js");
@@ -32,78 +35,105 @@ const fatal = profile === "a1" ? installFatalExit({
32
35
  ...(launchContext.releaseId === undefined ? {} : { releaseId: launchContext.releaseId }),
33
36
  dispose: () => runningApplication?.dispose(),
34
37
  }) : undefined;
35
- const [{ fileURLToPath }, identity, descriptor] = await Promise.all([
38
+ const [{ fileURLToPath }, identity] = await Promise.all([
36
39
  import("node:url"),
37
40
  import("./module-identity.js"),
38
- import("../dist/foundation/startup/startup-descriptor.js"),
39
41
  ]);
40
42
  const packageRootPath = fileURLToPath(new URL("..", import.meta.url));
41
43
  identity.configurePinnedPiPublicPackage(packageRootPath);
42
- // Performance: begin the exact launch graph together while the trace write is pending.
43
- // Direct owned-module entries avoid evaluating unrelated barrel exports before first paint.
44
- const modules = descriptor.loadDeclaredStartupGraph();
45
- await startup.markStartupPhase(process.env, "ui-entry");
46
- const [
47
- { runSelectedInteractiveRuntime },
48
- { parseSessionSelection },
49
- { createConsoleProjectTrustPrompt },
50
- { createConsoleSessionForkPrompt },
51
- { runOwnedUi, terminateOwnedUiProcess },
52
- { composeOwnedUi },
53
- ] = await modules;
54
44
 
55
- if (tabStandby) await waitForStandbyPromotion(process.stdin, tabDataDir === undefined ? undefined : join(tabDataDir, "standby-promote"));
45
+ // Compatibility: before either composition uses pinned Pi's terminal stack, confirm A1 and Pi
46
+ // resolve it to the same copy so every owned screen shares one module identity.
47
+ const assertPiTuiIdentity = () => identity.assertSinglePiTuiModuleAtLaunch(
48
+ packageRootPath,
49
+ (/** @type {string} */ message) => process.stderr.write(message),
50
+ );
51
+
52
+ if (utilityRoute !== null) {
53
+ if (profile !== "a1" || tabCredentials === undefined) throw new Error("utility tabs require the normal A1 tab profile");
54
+ await startup.markStartupPhase(process.env, "ui-entry");
55
+ assertPiTuiIdentity();
56
+ const utility = await import("../dist/composition/utility-tab.js");
57
+ await startup.markStartupPhase(process.env, "ui-modules-loaded");
58
+ const bridge = new (await import("../dist/foundation/resident-tabs/bridge-client.js")).TabBridgeClient({ credentials: tabCredentials });
59
+ completeRun(utility.runUtilityTab({
60
+ route: utilityRoute,
61
+ bridge,
62
+ cwd: process.cwd(),
63
+ profileId: profile,
64
+ onApplication: application => { runningApplication = application; },
65
+ }), utility.terminateOwnedUiProcess);
66
+ } else {
67
+ const descriptor = await import("../dist/foundation/startup/startup-descriptor.js");
68
+ // Performance: begin the exact Agent launch graph together while the trace write is pending.
69
+ // Direct owned-module entries avoid evaluating unrelated barrel exports before first paint.
70
+ const modules = descriptor.loadDeclaredStartupGraph();
71
+ await startup.markStartupPhase(process.env, "ui-entry");
72
+ const [
73
+ { runSelectedInteractiveRuntime },
74
+ { parseSessionSelection },
75
+ { createConsoleProjectTrustPrompt },
76
+ { createConsoleSessionForkPrompt },
77
+ { runOwnedUi, terminateOwnedUiProcess },
78
+ { composeOwnedUi },
79
+ ] = await modules;
56
80
 
57
- // Compatibility: before the composition uses pinned Pi's terminal stack: confirm A1 and Pi
58
- // resolve it to the same copy, so extensions and the owned UI share one module identity.
59
- identity.assertSinglePiTuiModuleAtLaunch(packageRootPath, (/** @type {string} */ message) => process.stderr.write(message));
60
- await startup.markStartupPhase(process.env, "ui-modules-loaded");
81
+ if (tabStandby) await waitForStandbyPromotion(process.stdin, tabDataDir === undefined ? undefined : join(tabDataDir, "standby-promote"));
82
+ assertPiTuiIdentity();
83
+ await startup.markStartupPhase(process.env, "ui-modules-loaded");
84
+
85
+ const sessionSelection = parseSessionSelection(uiArguments);
86
+ completeRun(Promise.resolve().then(async () => {
87
+ const tab = tabCredentials === undefined ? undefined : {
88
+ bridge: new (await import("../dist/foundation/resident-tabs/bridge-client.js")).TabBridgeClient({ credentials: tabCredentials }),
89
+ dataDir: /** @type {string} */ (tabDataDir),
90
+ incarnation: /** @type {number} */ (tabIncarnation),
91
+ };
92
+ if (sessionSelection && profile !== "a1") throw new Error("session selection requires the normal A1 profile");
93
+ return runSelectedInteractiveRuntime(profile, {
94
+ ownedUi: async (profileId, ownedSurfaces) => {
95
+ const processInspector = ownedSurfaces
96
+ ? await import("../dist/foundation/process-containment/native-process-inspector.js")
97
+ .then(module => module.createNativeProcessInspector(process.env, process.platform, launchContext.releaseRoot))
98
+ .catch(() => null)
99
+ : null;
100
+ const { application, settings } = await composeOwnedUi({
101
+ cwd: process.cwd(),
102
+ profileId,
103
+ ownedSurfaces,
104
+ notificationVersion: launchContext.updateVersion,
105
+ ...(hidesTabSettings(launchContext) ? { hideTabSettings: true } : {}),
106
+ ...(processInspector === null ? {} : { inspectProcess: pid => processInspector.observe(pid) }),
107
+ projectTrustPrompt: createConsoleProjectTrustPrompt({ presentation: profile === "a1" ? "bare" : "comparison" }),
108
+ sessionForkPrompt: createConsoleSessionForkPrompt(),
109
+ ...(sessionSelection === undefined ? {} : { sessionSelection }),
110
+ ...(tab === undefined ? {} : { tab }),
111
+ ...(launchContext.residentFallback === undefined ? {} : {
112
+ residentFallbackNotice: formatResidentFallbackNotice(launchContext.residentFallback),
113
+ }),
114
+ });
115
+ runningApplication = application;
116
+ return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
117
+ },
118
+ });
119
+ }), terminateOwnedUiProcess);
120
+ }
61
121
 
62
- const sessionSelection = parseSessionSelection(uiArguments);
63
- Promise.resolve().then(async () => {
64
- const tab = tabCredentials === undefined ? undefined : {
65
- bridge: new (await import("../dist/foundation/resident-tabs/bridge-client.js")).TabBridgeClient({ credentials: tabCredentials }),
66
- dataDir: /** @type {string} */ (tabDataDir),
67
- incarnation: /** @type {number} */ (tabIncarnation),
68
- };
69
- if (sessionSelection && profile !== "a1") throw new Error("session selection requires the normal A1 profile");
70
- return runSelectedInteractiveRuntime(profile, {
71
- ownedUi: async (profileId, ownedSurfaces) => {
72
- const processInspector = ownedSurfaces
73
- ? await import("../dist/foundation/process-containment/native-process-inspector.js")
74
- .then(module => module.createNativeProcessInspector(process.env, process.platform, launchContext.releaseRoot))
75
- .catch(() => null)
76
- : null;
77
- const { application, settings } = await composeOwnedUi({
78
- cwd: process.cwd(),
79
- profileId,
80
- ownedSurfaces,
81
- ...(hidesTabSettings(launchContext) ? { hideTabSettings: true } : {}),
82
- ...(processInspector === null ? {} : { inspectProcess: pid => processInspector.observe(pid) }),
83
- projectTrustPrompt: createConsoleProjectTrustPrompt({ presentation: profile === "a1" ? "bare" : "comparison" }),
84
- sessionForkPrompt: createConsoleSessionForkPrompt(),
85
- ...(sessionSelection === undefined ? {} : { sessionSelection }),
86
- ...(tab === undefined ? {} : { tab }),
87
- ...(launchContext.residentFallback === undefined ? {} : {
88
- residentFallbackNotice: formatResidentFallbackNotice(launchContext.residentFallback),
89
- }),
90
- });
91
- runningApplication = application;
92
- return await runOwnedUi({ application, ...(settings === null ? {} : { settings }) });
122
+ /** @param {Promise<number>} run @param {(code: number) => Promise<never>} terminate */
123
+ function completeRun(run, terminate) {
124
+ run.then(
125
+ code => {
126
+ fatal?.remove();
127
+ return terminate(code);
93
128
  },
94
- });
95
- }).then(
96
- code => {
97
- fatal?.remove();
98
- return terminateOwnedUiProcess(code);
99
- },
100
- error => {
101
- const sessionSelectionError = error?.name === "PiSessionSelectionError";
102
- const trustExit = error?.name === "ProjectTrustPromptExitError";
103
- if (fatal && !sessionSelectionError && !trustExit) { fatal.fail(error); return; }
104
- fatal?.remove();
105
- if (trustExit) return terminateOwnedUiProcess(error.exitCode);
106
- console.error(error instanceof Error ? error.message : String(error));
107
- process.exitCode = sessionSelectionError ? error.exitCode : 1;
108
- },
109
- );
129
+ error => {
130
+ const sessionSelectionError = error?.name === "PiSessionSelectionError";
131
+ const trustExit = error?.name === "ProjectTrustPromptExitError";
132
+ if (fatal && !sessionSelectionError && !trustExit) { fatal.fail(error); return; }
133
+ fatal?.remove();
134
+ if (trustExit) return terminate(error.exitCode);
135
+ console.error(error instanceof Error ? error.message : String(error));
136
+ process.exitCode = sessionSelectionError ? error.exitCode : 1;
137
+ },
138
+ );
139
+ }
@@ -1,18 +1,15 @@
1
1
  import type { OwnedUiEvent, PromptHistoryImageSidecarPort } from "../../contracts/owned-ui/index.js";
2
2
  import type { SessionConcern } from "./session-concern.js";
3
3
  import type { OwnedUiShellEngineOptions, OwnedUiSessionShellRoot } from "./session-shell-root.js";
4
- import type { OwnedUiPresenterTerminal } from "./shell-host.js";
5
4
  export type ResidentTabOptions = NonNullable<OwnedUiShellEngineOptions["residentTab"]>;
6
5
  export interface ResidentTabCoordinatorPorts {
7
6
  readonly root: Pick<OwnedUiSessionShellRoot, "appendWorkflowStatus" | "rehydrateHistoryText" | "editor">;
8
- readonly terminal: Pick<OwnedUiPresenterTerminal, "requestRender">;
9
- readonly disposed: () => boolean;
10
7
  }
11
8
  /**
12
- * The session presenter's side of a resident tab: recovered input, the progress and stall status
13
- * it reports to the strip, prompt admission and correlation through the recovery journal, draft
14
- * checkpoints, detach on quit, and the graceful stop when the presenter is released. Outside a
15
- * resident tab every operation is a no-op.
9
+ * The session presenter's side of a resident tab: recovered input, the idle status it reports to
10
+ * the strip, prompt admission and correlation through the recovery journal, draft checkpoints,
11
+ * detach on quit, and the graceful stop when the presenter is released. Outside a resident tab
12
+ * every operation is a no-op.
16
13
  */
17
14
  export declare class ResidentTabCoordinator implements SessionConcern {
18
15
  #private;
@@ -23,8 +20,8 @@ export declare class ResidentTabCoordinator implements SessionConcern {
23
20
  closeOwnedRoute(route: string): void;
24
21
  /** Puts the prompts and draft a previous tab process left unfinished back into the editor. */
25
22
  restoreInput(imageSidecar?: PromptHistoryImageSidecarPort): void;
26
- /** Tracks working, idle, and stalled status from session events and arms the stall notice. */
27
- observeProgress(event: OwnedUiEvent): void;
23
+ /** Reports idle to the strip whenever the session leaves its busy lifecycle. */
24
+ observeLifecycle(event: OwnedUiEvent): void;
28
25
  /** Journals a prompt before it is sent; undefined outside a resident tab. Rejects when the journal cannot sync. */
29
26
  admit(input: Parameters<NonNullable<ResidentTabOptions["admit"]>>[0]): Promise<{
30
27
  readonly id: string;
@@ -1,18 +1,12 @@
1
- const PROGRESS_EVENTS = new Set([
2
- "transcript-block", "session-view", "assistant-message-completed", "agent-run-started",
3
- ]);
4
1
  /**
5
- * The session presenter's side of a resident tab: recovered input, the progress and stall status
6
- * it reports to the strip, prompt admission and correlation through the recovery journal, draft
7
- * checkpoints, detach on quit, and the graceful stop when the presenter is released. Outside a
8
- * resident tab every operation is a no-op.
2
+ * The session presenter's side of a resident tab: recovered input, the idle status it reports to
3
+ * the strip, prompt admission and correlation through the recovery journal, draft checkpoints,
4
+ * detach on quit, and the graceful stop when the presenter is released. Outside a resident tab
5
+ * every operation is a no-op.
9
6
  */
10
7
  export class ResidentTabCoordinator {
11
8
  #options;
12
9
  #ports;
13
- #progressTimer;
14
- #working = false;
15
- #stalled = false;
16
10
  constructor(options, ports) {
17
11
  this.#options = options;
18
12
  this.#ports = ports;
@@ -51,40 +45,10 @@ export class ResidentTabCoordinator {
51
45
  root.appendWorkflowStatus("Recovered the editor draft from the previous tab process.");
52
46
  }
53
47
  }
54
- /** Tracks working, idle, and stalled status from session events and arms the stall notice. */
55
- observeProgress(event) {
56
- const resident = this.#options;
57
- const seconds = resident?.stallNoticeSeconds;
58
- if (resident === undefined || seconds === undefined)
59
- return;
60
- if (event.type === "session-lifecycle") {
61
- this.#working = event.lifecycle === "busy";
62
- if (!this.#working) {
63
- this.#clearTimer();
64
- this.#stalled = false;
65
- resident.reportStatus?.("idle");
66
- return;
67
- }
68
- }
69
- else if (!this.#working || !PROGRESS_EVENTS.has(event.type)) {
70
- return;
71
- }
72
- if (this.#stalled) {
73
- this.#stalled = false;
74
- resident.reportStatus?.("working");
75
- }
76
- this.#clearTimer();
77
- this.#progressTimer = setTimeout(() => {
78
- this.#progressTimer = undefined;
79
- if (this.#ports.disposed() || !this.#working)
80
- return;
81
- this.#stalled = true;
82
- resident.reportStatus?.("stalled");
83
- const minutes = Math.max(1, Math.round(seconds / 60));
84
- this.#ports.root.appendWorkflowStatus(`No progress for ${minutes} min — Esc interrupts · use the tab restart action to restart`);
85
- this.#ports.terminal.requestRender();
86
- }, seconds * 1_000);
87
- this.#progressTimer.unref?.();
48
+ /** Reports idle to the strip whenever the session leaves its busy lifecycle. */
49
+ observeLifecycle(event) {
50
+ if (event.type === "session-lifecycle" && event.lifecycle !== "busy")
51
+ this.#options?.reportStatus?.("idle");
88
52
  }
89
53
  /** Journals a prompt before it is sent; undefined outside a resident tab. Rejects when the journal cannot sync. */
90
54
  admit(input) {
@@ -109,12 +73,5 @@ export class ResidentTabCoordinator {
109
73
  gracefulStop(sessionFile) {
110
74
  return this.#options?.gracefulStop?.(sessionFile) ?? Promise.resolve();
111
75
  }
112
- dispose() {
113
- this.#clearTimer();
114
- }
115
- #clearTimer() {
116
- if (this.#progressTimer !== undefined)
117
- clearTimeout(this.#progressTimer);
118
- this.#progressTimer = undefined;
119
- }
76
+ dispose() { }
120
77
  }
@@ -47,8 +47,7 @@ export interface OwnedUiShellEngineOptions {
47
47
  readonly residentTab?: {
48
48
  requestDetach(): Promise<boolean>;
49
49
  closeOwnedRoute?(route: string): void;
50
- reportStatus?(status: "working" | "idle" | "stalled"): void;
51
- readonly stallNoticeSeconds?: number;
50
+ reportStatus?(status: "working" | "idle"): void;
52
51
  admit?(input: {
53
52
  readonly sessionId: string;
54
53
  readonly sessionFile: string | null;
@@ -117,6 +116,13 @@ export interface OwnedUiShellPresentationOptions {
117
116
  /** Persistent prompt history: its store, its bound, the editor that recalls it, and the image sidecar. */
118
117
  export interface OwnedUiShellHistoryOptions {
119
118
  readonly store: import("../../contracts/owned-ui/index.js").PromptHistoryPort;
119
+ /** When present, session replacement selects a new store and sidecar, not the old session's recall. */
120
+ readonly sessionId?: string;
121
+ readonly scope?: "unique" | "shared";
122
+ readonly forSession?: (sessionId: string) => Pick<OwnedUiShellHistoryOptions, "store" | "imageSidecar">;
123
+ /** A live scope change selects the matching store for this session without copying records. */
124
+ readonly forScope?: (scope: "unique" | "shared", sessionId: string | null) => Pick<OwnedUiShellHistoryOptions, "store" | "imageSidecar"> | null;
125
+ readonly onScopeChange?: (listener: (scope: "unique" | "shared") => void) => () => void;
120
126
  readonly limit: number;
121
127
  readonly editor: import("../../integrations/pi/components/index.js").HistoryEditorConstructor;
122
128
  /** Optional per-profile filesystem sidecar for image chip payloads. When present, submit
@@ -166,19 +172,19 @@ export interface OwnedUiShellSharedServices {
166
172
  /** What composes an owned session shell, grouped by the collaborator that provides each part. */
167
173
  export interface OwnedUiSessionShellOptions {
168
174
  readonly engine: OwnedUiShellEngineOptions;
169
- /** Opens the selectors and resolves the transcript renderers whose inputs are engine objects. */
175
+ /** Resolves selectors and engine-backed transcript renderers. */
170
176
  readonly presenters: OwnedUiSessionPresenters;
171
177
  readonly presentation?: OwnedUiShellPresentationOptions;
172
178
  readonly history?: OwnedUiShellHistoryOptions;
173
179
  readonly suggestions?: OwnedUiShellSuggestionOptions;
174
- /** Supplied only to bare A1; absent retains the fixed eight-image comparison policy. */
180
+ /** Bare A1 only; absent keeps the eight-image comparison limit. */
175
181
  readonly promptImages?: OwnedUiShellPromptImagesOptions;
176
- /** Supplied only to the bare-A1 composition; absent keeps the pinned per-skill command list. */
182
+ /** Bare A1 only; absent keeps pinned per-skill commands. */
177
183
  readonly skills?: OwnedUiShellSkillsOptions;
178
184
  readonly diagnostics?: OwnedUiShellDiagnosticOptions;
179
185
  readonly shared?: OwnedUiShellSharedServices;
180
186
  }
181
- /** Composes backend session state into the owned transcript, viewport, editor, and dock presentation. */
187
+ /** Composes session state into the owned transcript, viewport, editor, and dock. */
182
188
  export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
183
189
  #private;
184
190
  readonly editor: PiShellEditorPort;
@@ -192,7 +198,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
192
198
  readonly requestHyperlinkCleanup?: (rows?: readonly number[]) => void;
193
199
  readonly enableDockInputReuse?: boolean;
194
200
  readonly persistentHistory?: boolean;
195
- readonly hideSettingsCommand?: boolean;
201
+ readonly hideUtilityTabCommands?: boolean;
196
202
  readonly historyEditor?: import("../../integrations/pi/components/index.js").HistoryEditorConstructor;
197
203
  readonly onSubmit: (text: string) => void;
198
204
  readonly onPasteRejected?: (error: unknown) => void;
@@ -218,7 +224,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
218
224
  readonly keybindings?: PiKeybindingsHost;
219
225
  readonly promptImageLimit?: () => number;
220
226
  readonly skillsPresentation?: () => "collapse" | "expand";
221
- /** A resident tab chip already shows the session name, so its footer omits the duplicate. */
227
+ /** Resident tab chips already show the session name. */
222
228
  readonly footerSessionName?: boolean;
223
229
  }, startup?: PiShellHeaderOptions, agentDir?: string, extensionRenderers?: OwnedUiTranscriptRendererPort, sessionLayout?: "pinned" | "custom-viewport", imageAssets?: PiShellImageAssetResolver);
224
230
  setEditorPaddingX(padding: number): void;
@@ -6,7 +6,7 @@ import { progressStatusFrame, progressStatusText } from "../../ui/components/pro
6
6
  import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
7
7
  import { createPiShellTranscriptComponent, } from "../../integrations/pi/components/shell-presenters-transcript.js";
8
8
  import { onPiThemeChange, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
9
- import {} from "../../integrations/pi/components/shell-shared-facade.js";
9
+ import { piShellTruncateToWidth, } from "../../integrations/pi/components/shell-shared-facade.js";
10
10
  import {} from "../../integrations/pi/tui-runtime/input-presentation-coordinator.js";
11
11
  import { PromptChipStore } from "./prompt-chips.js";
12
12
  import { SessionViewportController } from "./session-viewport-controller.js";
@@ -18,7 +18,7 @@ import { InputSurfaceSwitch } from "./input-surface-switch.js";
18
18
  import { PinnedLayout } from "./shell-layout.js";
19
19
  import { ViewportLayout } from "./viewport-layout.js";
20
20
  import { nativeTranscriptLinkColor, TranscriptDocument } from "./transcript-document.js";
21
- /** Composes backend session state into the owned transcript, viewport, editor, and dock presentation. */
21
+ /** Composes session state into the owned transcript, viewport, editor, and dock. */
22
22
  export class OwnedUiSessionShellRoot {
23
23
  editor;
24
24
  header;
@@ -128,8 +128,8 @@ export class OwnedUiSessionShellRoot {
128
128
  this.editor.addToHistory(block.text);
129
129
  }
130
130
  this.#document.sync(view.transcript);
131
- // Invariant: colours come from the active theme, so rendered rows outlive their revision only
132
- // until the theme under them changes.
131
+ // Invariant: rendered rows expire when
132
+ // their active theme changes.
133
133
  this.#themeUnsubscribe = onPiThemeChange(() => {
134
134
  this.invalidate();
135
135
  this.#componentRuntime.requestRender(true);
@@ -607,7 +607,11 @@ export class OwnedUiSessionShellRoot {
607
607
  return this.#customViewport ? this.#status.renderLive(width) : this.#status.render(width);
608
608
  }
609
609
  #renderFooter(width) {
610
- return (this.#extensions.footer ?? this.#footer).render(width);
610
+ const footer = this.#extensions.footer ?? this.#footer;
611
+ if (!this.#customViewport)
612
+ return footer.render(width);
613
+ width = Math.max(0, width - 2);
614
+ return footer.render(width).map(row => ` ${piShellTruncateToWidth(row, width)}`);
611
615
  }
612
616
  #setToolsExpanded(expanded) {
613
617
  this.#toolsExpanded = expanded;
@@ -27,6 +27,12 @@ import { writeSystemClipboardText, } from "./system-clipboard.js";
27
27
  import { OwnedUiSessionShellRoot, shellResourceEntries, } from "./session-shell-root.js";
28
28
  // Invariant: a disposed shell leaves the engine a host that holds no reference back to it.
29
29
  const DETACHED_WORKFLOW_INTERACTION = { prompt: async () => null, notify() { } };
30
+ /** Resident Agents refuse the commands their tab bar serves instead, naming where they live. */
31
+ const RESIDENT_UTILITY_TAB_REFUSALS = Object.freeze({
32
+ settings: "Settings is available from the global Settings tab.",
33
+ changelog: "The changelog opens as its own tab from the command palette (Alt+/).",
34
+ name: "Rename the session by renaming its tab (F2).",
35
+ });
30
36
  export { OwnedUiSessionShellRoot, } from "./session-shell-root.js";
31
37
  export { OwnedUiTerminalHost, sessionTerminalHostOptions } from "./shell-host.js";
32
38
  export { formatSessionResumeCommand, quoteCommandArgument } from "./session-lifecycle.js";
@@ -55,7 +61,7 @@ export class OwnedUiSessionPresenter {
55
61
  #unsubscribeSkills;
56
62
  #installedCommandSignature = "";
57
63
  #promptHistory = null;
58
- #promptHistoryImageSidecar;
64
+ #historyBinding;
59
65
  #extensionBridge;
60
66
  #stop = new SessionStopLatch();
61
67
  #dialogs;
@@ -169,7 +175,7 @@ export class OwnedUiSessionPresenter {
169
175
  }),
170
176
  enableDockInputReuse: inputPresentation?.viewportReuse !== false,
171
177
  footerSessionName: options.engine.residentTab === undefined,
172
- hideSettingsCommand: options.engine.residentTab !== undefined,
178
+ hideUtilityTabCommands: options.engine.residentTab !== undefined,
173
179
  persistentHistory: this.#customViewport && promptHistory !== undefined,
174
180
  ...(promptHistory === undefined ? {} : { historyEditor: promptHistory.editor }),
175
181
  onSubmit: text => { void this.submit(text).catch(() => this.#submission.reportError()); },
@@ -251,7 +257,7 @@ export class OwnedUiSessionPresenter {
251
257
  // Invariant: the presenter owns its root (theme subscription and helper clients) and disposes it once the
252
258
  // terminal no longer renders it.
253
259
  constructed.push(() => this.root.dispose());
254
- this.#resident = new ResidentTabCoordinator(options.engine.residentTab, { root: this.root, terminal: this.#terminal, disposed: () => this.#disposed });
260
+ this.#resident = new ResidentTabCoordinator(options.engine.residentTab, { root: this.root });
255
261
  constructed.push(() => this.#resident.dispose());
256
262
  const startupRoute = options.engine.startupRoute;
257
263
  this.#dialogs = new SessionDialogCoordinator({
@@ -316,9 +322,10 @@ export class OwnedUiSessionPresenter {
316
322
  });
317
323
  };
318
324
  applyViewportSettings();
319
- this.#unsubscribeSettings = this.#customViewport && viewportSettings
320
- ? viewportSettings.onChange(settings => this.root.setViewportConfig(settings))
321
- : () => { };
325
+ const unsubscribeViewport = this.#customViewport && viewportSettings ? viewportSettings.onChange(settings => this.root.setViewportConfig(settings)) : () => { };
326
+ const unsubscribeHistoryScope = this.#customViewport && promptHistory?.onScopeChange?.(scope => this.#rebindTranscript(scope));
327
+ this.#unsubscribeSettings = () => { unsubscribeViewport(); if (unsubscribeHistoryScope)
328
+ unsubscribeHistoryScope(); };
322
329
  constructed.push(this.#unsubscribeSettings);
323
330
  this.#unbindSessionSettings = bindSessionSettings(this.backend.settings, initialPiSettings, {
324
331
  terminal: this.#terminal,
@@ -331,8 +338,7 @@ export class OwnedUiSessionPresenter {
331
338
  }, { customViewport: this.#customViewport });
332
339
  constructed.push(this.#unbindSessionSettings);
333
340
  if (this.#customViewport && promptHistory !== undefined) {
334
- this.#promptHistoryImageSidecar = promptHistory.imageSidecar;
335
- const sidecar = this.#promptHistoryImageSidecar;
341
+ this.#historyBinding = { options: promptHistory, scope: promptHistory.scope ?? "unique", sessionId: promptHistory.sessionId, imageSidecar: promptHistory.imageSidecar };
336
342
  this.#promptHistory = new PromptHistoryController({
337
343
  editor: this.root.editor,
338
344
  store: promptHistory.store,
@@ -340,7 +346,7 @@ export class OwnedUiSessionPresenter {
340
346
  fallback: this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []),
341
347
  active: () => this.root.usesDefaultInputSurface(),
342
348
  render: () => this.#terminal.requestRender(),
343
- rehydrate: value => this.root.rehydrateHistoryText(value, id => sidecar?.readAttachment(id) ?? null),
349
+ rehydrate: value => this.root.rehydrateHistoryText(value, id => this.#historyBinding?.imageSidecar?.readAttachment(id) ?? null),
344
350
  });
345
351
  constructed.push(() => { void this.#promptHistory?.close().catch(() => false); });
346
352
  }
@@ -366,7 +372,7 @@ export class OwnedUiSessionPresenter {
366
372
  this.#stop.settleAfter(() => this.dispose());
367
373
  return;
368
374
  }
369
- this.#resident.observeProgress(event);
375
+ this.#resident.observeLifecycle(event);
370
376
  // Performance: a streamed chunk names one block, and touching only that block is what keeps the
371
377
  // cost of a chunk the same in a long session as in a new one. Everything else
372
378
  // resynchronizes the view, which is cheap next to re-reading the transcript.
@@ -1154,7 +1160,7 @@ export class OwnedUiSessionPresenter {
1154
1160
  residentTab: this.#resident,
1155
1161
  cwd,
1156
1162
  promptHistory: () => this.#promptHistory,
1157
- imageSidecar: () => this.#promptHistoryImageSidecar,
1163
+ imageSidecar: () => this.#historyBinding?.imageSidecar,
1158
1164
  disposed: () => this.#disposed,
1159
1165
  correlation: prefix => this.#correlation(prefix),
1160
1166
  lifecycle: () => this.view().lifecycle,
@@ -1183,13 +1189,57 @@ export class OwnedUiSessionPresenter {
1183
1189
  this.#resident,
1184
1190
  ]);
1185
1191
  }
1186
- // Invariant: delivery recovery invalidates callbacks, not same-session local recall or its draft.
1187
- #rebindTranscript() {
1188
- if (this.#sessionBindingGeneration === this.backend.identity.sessionBindingGeneration)
1192
+ // Invariant: delivery recovery does not replace a store; conversation or live scope changes can.
1193
+ #rebindTranscript(scope) {
1194
+ const replaced = this.#sessionBindingGeneration !== this.backend.identity.sessionBindingGeneration;
1195
+ if (!replaced && scope === undefined)
1189
1196
  return;
1190
- this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
1191
- this.root.resetTranscript();
1192
- this.#promptHistory?.reset(this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []));
1197
+ if (replaced) {
1198
+ this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
1199
+ this.root.resetTranscript();
1200
+ }
1201
+ const binding = this.#historyBinding;
1202
+ if (binding?.options.forSession !== undefined || binding?.options.forScope !== undefined) {
1203
+ const sessionId = this.backend.identity.currentHistorySessionId();
1204
+ const selectedScope = scope ?? binding.scope;
1205
+ if (selectedScope !== binding.scope || sessionId !== binding.sessionId) {
1206
+ if (!replaced)
1207
+ this.root.editor.recall?.restoreDraft?.();
1208
+ const previous = this.#promptHistory;
1209
+ this.#promptHistory = null;
1210
+ binding.imageSidecar = undefined;
1211
+ binding.scope = selectedScope;
1212
+ binding.sessionId = sessionId ?? undefined;
1213
+ void previous?.close().catch(() => false);
1214
+ const destination = binding.options.forScope !== undefined
1215
+ ? binding.options.forScope(selectedScope, sessionId)
1216
+ : sessionId === null ? null : binding.options.forSession(sessionId);
1217
+ if (destination !== null) {
1218
+ binding.imageSidecar = destination.imageSidecar;
1219
+ const controller = new PromptHistoryController({
1220
+ editor: this.root.editor,
1221
+ store: destination.store,
1222
+ limit: binding.options.limit,
1223
+ fallback: replaced ? this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []) : [],
1224
+ active: () => this.root.usesDefaultInputSurface(),
1225
+ render: () => this.#terminal.requestRender(),
1226
+ rehydrate: value => this.root.rehydrateHistoryText(value, id => this.#historyBinding?.imageSidecar?.readAttachment(id) ?? null),
1227
+ });
1228
+ this.#promptHistory = controller;
1229
+ if (this.#started)
1230
+ controller.start();
1231
+ }
1232
+ else {
1233
+ // Security: missing identity must not reopen Shared or retain another session's recall.
1234
+ const fallback = replaced ? this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []).reverse() : [];
1235
+ this.root.editor.recall?.reset();
1236
+ this.root.editor.recall?.replace(fallback);
1237
+ }
1238
+ return;
1239
+ }
1240
+ }
1241
+ if (replaced)
1242
+ this.#promptHistory?.reset(this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []));
1193
1243
  }
1194
1244
  #syncTerminalProgress(view) {
1195
1245
  if (!this.#terminal.running)
@@ -1254,9 +1304,9 @@ export class OwnedUiSessionPresenter {
1254
1304
  const separator = body.search(/\s/);
1255
1305
  const name = separator < 0 ? body : body.slice(0, separator);
1256
1306
  const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
1257
- if (name === "settings" && this.#resident.active) {
1258
- return rejected("Settings is available from the global Settings tab.");
1259
- }
1307
+ const utilityTab = this.#resident.active ? RESIDENT_UTILITY_TAB_REFUSALS[name] : undefined;
1308
+ if (utilityTab !== undefined)
1309
+ return rejected(utilityTab);
1260
1310
  if (this.#dialogs.claimsRoute(name))
1261
1311
  return this.#dialogs.openRoute(name);
1262
1312
  if (this.#isWorkflowRoute(name))