@timurproko/a1 0.2.7-dev.754 → 0.2.7-dev.772

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 (217) hide show
  1. package/README.md +30 -0
  2. package/bin/cli.js +4 -0
  3. package/bin/guardian.js +5 -0
  4. package/bin/tab-launch-context.d.ts +26 -0
  5. package/bin/tab-launch-context.js +94 -0
  6. package/bin/ui.js +24 -6
  7. package/dist/app/session-shell/helper-pool.d.ts +3 -5
  8. package/dist/app/session-shell/helper-pool.js +3 -5
  9. package/dist/app/session-shell/session-shell-root.d.ts +28 -1
  10. package/dist/app/session-shell/session-shell-root.js +4 -3
  11. package/dist/app/session-shell/session-shell.js +149 -31
  12. package/dist/app/session-shell/session-viewport-controller.d.ts +1 -1
  13. package/dist/app/session-shell/session-viewport-controller.js +3 -3
  14. package/dist/app/session-shell/shell-host.d.ts +2 -0
  15. package/dist/app/session-shell/shell-host.js +20 -1
  16. package/dist/cli/dispatch.d.ts +11 -1
  17. package/dist/cli/dispatch.js +79 -5
  18. package/dist/cli/tabs-request.d.ts +7 -0
  19. package/dist/cli/tabs-request.js +1 -0
  20. package/dist/cli/tabs.d.ts +14 -0
  21. package/dist/cli/tabs.js +140 -0
  22. package/dist/composition/owned-ui.d.ts +26 -0
  23. package/dist/composition/owned-ui.js +176 -6
  24. package/dist/contracts/owned-ui/session-backend.d.ts +1 -0
  25. package/dist/features/launch/index.d.ts +1 -1
  26. package/dist/features/launch/intent.d.ts +3 -1
  27. package/dist/features/launch/intent.js +7 -4
  28. package/dist/features/owned-ui/settings-app.js +8 -35
  29. package/dist/features/resident-tab/auto-name.d.ts +11 -0
  30. package/dist/features/resident-tab/auto-name.js +42 -0
  31. package/dist/features/resident-tab/index.d.ts +3 -0
  32. package/dist/features/resident-tab/index.js +3 -0
  33. package/dist/features/resident-tab/shortcuts.d.ts +10 -0
  34. package/dist/features/resident-tab/shortcuts.js +35 -0
  35. package/dist/features/resident-tab/status-deriver.d.ts +9 -0
  36. package/dist/features/resident-tab/status-deriver.js +15 -0
  37. package/dist/foundation/launch-context/index.d.ts +10 -0
  38. package/dist/foundation/launch-context/index.js +25 -0
  39. package/dist/foundation/launch-guardian/main.d.ts +5 -0
  40. package/dist/foundation/launch-guardian/main.js +2 -1
  41. package/dist/foundation/release/bootstrap.d.ts +6 -0
  42. package/dist/foundation/release/bootstrap.js +51 -10
  43. package/dist/foundation/release/cohort-state.d.ts +1 -1
  44. package/dist/foundation/release/cohort-state.js +1 -1
  45. package/dist/foundation/release/dependency-certification.js +54 -19
  46. package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
  47. package/dist/foundation/release/progress-palette.generated.js +2 -2
  48. package/dist/foundation/release/release-gc.d.ts +3 -0
  49. package/dist/foundation/release/release-gc.js +44 -8
  50. package/dist/foundation/resident-tabs/bounded-host-command.d.ts +16 -0
  51. package/dist/foundation/resident-tabs/bounded-host-command.js +35 -0
  52. package/dist/foundation/resident-tabs/bridge-client.d.ts +56 -0
  53. package/dist/foundation/resident-tabs/bridge-client.js +279 -0
  54. package/dist/foundation/resident-tabs/doctor.d.ts +29 -0
  55. package/dist/foundation/resident-tabs/doctor.js +166 -0
  56. package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +27 -0
  57. package/dist/foundation/resident-tabs/draft-checkpoint.js +145 -0
  58. package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +31 -0
  59. package/dist/foundation/resident-tabs/ensure-terminal-host.js +147 -0
  60. package/dist/foundation/resident-tabs/fault-injection.d.ts +8 -0
  61. package/dist/foundation/resident-tabs/fault-injection.js +26 -0
  62. package/dist/foundation/resident-tabs/index.d.ts +12 -0
  63. package/dist/foundation/resident-tabs/index.js +6 -0
  64. package/dist/foundation/resident-tabs/maintenance.d.ts +42 -0
  65. package/dist/foundation/resident-tabs/maintenance.js +78 -0
  66. package/dist/foundation/resident-tabs/prompt-journal.d.ts +65 -0
  67. package/dist/foundation/resident-tabs/prompt-journal.js +214 -0
  68. package/dist/foundation/resident-tabs/protocol.d.ts +12 -0
  69. package/dist/foundation/resident-tabs/protocol.js +195 -0
  70. package/dist/foundation/resident-tabs/recovery.d.ts +26 -0
  71. package/dist/foundation/resident-tabs/recovery.js +205 -0
  72. package/dist/foundation/resident-tabs/release-holds.d.ts +27 -0
  73. package/dist/foundation/resident-tabs/release-holds.js +77 -0
  74. package/dist/foundation/resident-tabs/routing.d.ts +11 -0
  75. package/dist/foundation/resident-tabs/routing.js +10 -0
  76. package/dist/foundation/resident-tabs/session-sync.d.ts +14 -0
  77. package/dist/foundation/resident-tabs/session-sync.js +37 -0
  78. package/dist/foundation/resident-tabs/settings.d.ts +14 -0
  79. package/dist/foundation/resident-tabs/settings.js +66 -0
  80. package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +18 -0
  81. package/dist/foundation/resident-tabs/terminal-host-artifact.js +53 -0
  82. package/dist/foundation/resident-tabs/zip-archive.d.ts +9 -0
  83. package/dist/foundation/resident-tabs/zip-archive.js +56 -0
  84. package/dist/foundation/session-writer-lock/index.d.ts +32 -0
  85. package/dist/foundation/session-writer-lock/index.js +201 -0
  86. package/dist/foundation/startup/startup-descriptor.js +2 -2
  87. package/dist/foundation/terminal-cleanup/index.d.ts +1 -1
  88. package/dist/foundation/terminal-cleanup/index.js +1 -1
  89. package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +1 -0
  90. package/dist/foundation/terminal-cleanup/terminal-reset.js +1 -0
  91. package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
  92. package/dist/integrations/pi/components/owned-editor-ux.js +2 -2
  93. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +4 -4
  94. package/dist/integrations/pi/components/resources/builtin-themes.js +4 -4
  95. package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
  96. package/dist/integrations/pi/components/shell-presenters-info.d.ts +4 -2
  97. package/dist/integrations/pi/components/shell-presenters-info.js +38 -4
  98. package/dist/integrations/pi/components/shell-shared-facade.d.ts +1 -1
  99. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  100. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +17 -4
  101. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  102. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  103. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  104. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  105. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  106. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  107. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  108. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  109. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  110. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  111. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  112. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  113. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  114. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  115. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  116. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  117. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  118. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +2 -2
  119. package/dist/integrations/pi/components/upstream/components/status-indicator.js +3 -3
  120. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  121. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  122. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  123. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  124. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +0 -2
  125. package/dist/integrations/pi/components/upstream/components/tool-execution.js +41 -20
  126. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  127. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  128. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  129. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  130. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +2 -2
  131. package/dist/integrations/pi/components/upstream/history/editor-core.js +5 -5
  132. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  133. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  134. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  135. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  136. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  137. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  138. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  139. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  140. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  141. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  142. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +3 -0
  143. package/dist/integrations/pi/components/upstream/theme/theme.js +24 -1
  144. package/dist/integrations/pi/engine/adapter.d.ts +1 -0
  145. package/dist/integrations/pi/engine/adapter.js +4 -0
  146. package/dist/integrations/pi/engine/pi-settings-metadata.json +2 -2
  147. package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
  148. package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
  149. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  150. package/dist/integrations/pi/engine/runtime-integration.js +102 -29
  151. package/dist/integrations/pi/engine/session-runtime.d.ts +0 -6
  152. package/dist/integrations/pi/engine/session-runtime.js +8 -3
  153. package/dist/integrations/pi/engine/session-selection.d.ts +3 -0
  154. package/dist/integrations/pi/engine/session-selection.js +45 -9
  155. package/dist/integrations/pi/engine/workflow-contexts.js +10 -1
  156. package/dist/integrations/pi/startup-public.js +152 -222
  157. package/dist/integrations/pi/startup-public.manifest.json +266 -177
  158. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  159. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  160. package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
  161. package/dist/native/darwin-arm64/manifest.json +1 -1
  162. package/dist/native/darwin-arm64/terminal-host +0 -0
  163. package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +10500 -0
  164. package/dist/native/darwin-arm64/terminal-host.manifest.json +44 -0
  165. package/dist/native/darwin-x64/a1_session_lock.node +0 -0
  166. package/dist/native/darwin-x64/manifest.json +20 -0
  167. package/dist/native/darwin-x64/process-guardian +0 -0
  168. package/dist/native/darwin-x64/terminal-host +0 -0
  169. package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10737 -0
  170. package/dist/native/darwin-x64/terminal-host.manifest.json +44 -0
  171. package/dist/native/linux-x64/a1_session_lock.node +0 -0
  172. package/dist/native/linux-x64/manifest.json +1 -1
  173. package/dist/native/linux-x64/terminal-host +0 -0
  174. package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +11226 -0
  175. package/dist/native/linux-x64/terminal-host.manifest.json +44 -0
  176. package/dist/native/win32-x64/a1_session_lock.node +0 -0
  177. package/dist/native/win32-x64/manifest.json +2 -2
  178. package/dist/native/win32-x64/process-guardian.exe +0 -0
  179. package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10757 -0
  180. package/dist/native/win32-x64/terminal-host.exe +0 -0
  181. package/dist/native/win32-x64/terminal-host.manifest.json +44 -0
  182. package/dist/product-identity.d.ts +1 -1
  183. package/dist/product-identity.js +1 -1
  184. package/dist/product-identity.json +5 -0
  185. package/dist/runtime-payload-inventory.json +5349 -6035
  186. package/dist/ui/components/line-input.d.ts +1 -1
  187. package/dist/ui/components/prompt-input.d.ts +7 -5
  188. package/dist/ui/components/prompt-input.js +7 -7
  189. package/dist/ui/components/transcript-viewport.d.ts +6 -3
  190. package/dist/ui/components/transcript-viewport.js +13 -15
  191. package/dist/ui/settings/declarations.d.ts +25 -1
  192. package/dist/ui/settings/declarations.js +21 -1
  193. package/dist/ui/settings/index.d.ts +4 -2
  194. package/dist/ui/settings/index.js +2 -1
  195. package/dist/ui/settings/manager.d.ts +10 -3
  196. package/dist/ui/settings/manager.js +90 -30
  197. package/dist/ui/settings/migrations.d.ts +6 -0
  198. package/dist/ui/settings/migrations.js +44 -0
  199. package/dist/ui/settings/profile-document.d.ts +26 -0
  200. package/dist/ui/settings/profile-document.js +164 -0
  201. package/docs/architecture/evidence/resident-tabs/linux-x64.md +37 -0
  202. package/docs/architecture/evidence/resident-tabs/macos-arm64.md +36 -0
  203. package/docs/architecture/evidence/resident-tabs/macos-x64.md +35 -0
  204. package/docs/architecture/evidence/resident-tabs/windows-x64.md +36 -0
  205. package/docs/architecture/internal-naming.md +3 -1
  206. package/docs/architecture/process-guardian-provenance.md +2 -0
  207. package/docs/architecture/resident-tabs-requirements.md +18 -0
  208. package/docs/architecture/resident-tabs.md +594 -0
  209. package/docs/architecture/terminal-host-proof-gate.md +2 -0
  210. package/docs/architecture/terminal-host-provenance.md +13 -3
  211. package/docs/architecture/toolchain.md +1 -1
  212. package/docs/ci-release-runbook.md +2 -2
  213. package/docs/features/launch-profiles.md +11 -8
  214. package/docs/manual-resident-tabs-acceptance.md +51 -0
  215. package/docs/openspec-archive-automation.md +17 -16
  216. package/docs/validation.md +9 -1
  217. package/package.json +10 -4
@@ -0,0 +1,140 @@
1
+ import { resolveProductPaths } from "../foundation/lifecycle/index.js";
2
+ import { CohortStateStore } from "../foundation/release/index.js";
3
+ import { readResidentTabLaunchSettings, runTerminalHostMaintenance, writeDoctorBundle, } from "../foundation/resident-tabs/index.js";
4
+ import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../product-identity.js";
5
+ /** `a1 tabs`: inspect or stop resident tabs without attaching, and never start a host to report state. */
6
+ export async function runTabsCommand(request, options = {}) {
7
+ const environment = options.environment ?? process.env;
8
+ const stdout = options.stdout ?? (message => process.stdout.write(message));
9
+ const stderr = options.stderr ?? (message => process.stderr.write(message));
10
+ const paths = resolveProductPaths(environment);
11
+ const profileRoot = environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir;
12
+ const releaseRoots = await installedReleaseRoots(paths.dataDir, options.packageRoot);
13
+ const maintenance = options.maintenance ?? runTerminalHostMaintenance;
14
+ const report = await maintenance({
15
+ action: request.action === "doctor" ? "host-status" : request.action,
16
+ ...(request.tabId === undefined ? {} : { tabId: request.tabId }),
17
+ profileRoot,
18
+ dataDir: paths.dataDir,
19
+ runtimeDir: paths.runtimeDir,
20
+ releaseRoots,
21
+ environment,
22
+ });
23
+ if (request.action === "doctor") {
24
+ if (!report.stateDir) {
25
+ stderr(`${PRODUCT_TEXT.diagnostic(`tabs doctor could not locate resident state: ${report.message ?? report.host}`)}\n`);
26
+ return 1;
27
+ }
28
+ const settings = await readResidentTabLaunchSettings(environment.PI_CODING_AGENT_DIR ?? paths.configDir, PRODUCT_IDENTITY.commandName, paths.configDir, "a1");
29
+ const bundle = await (options.doctor ?? writeDoctorBundle)({
30
+ stateDir: report.stateDir,
31
+ hostStatus: report,
32
+ releaseRoots,
33
+ settings: { tabsMax: settings.tabsMax, tabsAutoName: settings.tabsAutoName },
34
+ });
35
+ if (request.json)
36
+ stdout(`${JSON.stringify({ schema: "a1-tabs-doctor-v1", path: bundle.path, bytes: bundle.bytes, dropped: bundle.dropped })}\n`);
37
+ else
38
+ stdout(`Wrote ${bundle.path}\n`);
39
+ return 0;
40
+ }
41
+ if (request.json) {
42
+ const { exitCode: _exitCode, ...printed } = report;
43
+ stdout(`${JSON.stringify(printed)}\n`);
44
+ }
45
+ else {
46
+ const text = describe(request, report);
47
+ if (report.exitCode === 0)
48
+ stdout(text);
49
+ else
50
+ stderr(text);
51
+ }
52
+ return report.exitCode === 0 ? 0 : 1;
53
+ }
54
+ async function installedReleaseRoots(dataDir, packageRoot) {
55
+ const roots = [];
56
+ try {
57
+ const state = await new CohortStateStore(dataDir).read();
58
+ for (const id of [state.references.active, state.references.rollback]) {
59
+ if (id && state.releases[id])
60
+ roots.push(state.releases[id].releaseRoot);
61
+ }
62
+ roots.push(...Object.values(state.releases).map(record => record.releaseRoot));
63
+ }
64
+ catch {
65
+ // Rationale: unreadable release state still leaves the running package's own host.
66
+ }
67
+ if (packageRoot)
68
+ roots.push(packageRoot);
69
+ return [...new Set(roots)];
70
+ }
71
+ const NO_TABS = "No resident tabs are running.\n";
72
+ function describe(request, report) {
73
+ const command = PRODUCT_TEXT.commandName;
74
+ if (report.host === "none")
75
+ return NO_TABS;
76
+ if (report.host === "unavailable")
77
+ return `${PRODUCT_TEXT.diagnostic(`tabs could not run maintenance: ${report.message ?? "terminal host unavailable"}`)}\n`;
78
+ if (report.host === "unresponsive") {
79
+ return `The resident host (pid ${report.pid ?? "unknown"}) is not responding. Run \`${command} tabs host stop\` to stop it.\n`;
80
+ }
81
+ if (report.host === "incompatible") {
82
+ return `${PRODUCT_TEXT.diagnostic(report.message ?? "running tabs use another version")} Run \`${command} tabs host stop\` from that version.\n`;
83
+ }
84
+ if (report.host === "unverifiable" || report.host === "refused") {
85
+ return `${PRODUCT_TEXT.diagnostic(report.message ?? "the resident host could not be verified; nothing was changed")}\n`;
86
+ }
87
+ const result = report.result ?? {};
88
+ if (request.action === "list")
89
+ return listing(result);
90
+ if (request.action === "host-status")
91
+ return status(result);
92
+ if (request.action === "host-stop") {
93
+ if (report.host === "stopped")
94
+ return "Stopped every tab and the resident host.\n";
95
+ return `Could not stop every tab: ${remaining(result)}. The host is still running.\n`;
96
+ }
97
+ if (result.accepted === false && typeof result.message === "string") {
98
+ return `${PRODUCT_TEXT.diagnostic(`tabs stop: ${result.message === "unknown tab" ? `no tab ${request.tabId ?? ""}` : result.message}`)}\n`;
99
+ }
100
+ const stopped = Array.isArray(result.stopped) ? result.stopped.length : 0;
101
+ if (result.accepted === false)
102
+ return `Could not stop: ${remaining(result)}.\n`;
103
+ if (request.action === "stop")
104
+ return `Stopped tab ${request.tabId ?? ""}; its session can be resumed.\n`;
105
+ return stopped === 0 ? NO_TABS : `Stopped ${stopped} tab${stopped === 1 ? "" : "s"}; their sessions can be resumed.\n`;
106
+ }
107
+ function remaining(result) {
108
+ return Array.isArray(result.remaining) && result.remaining.length > 0 ? result.remaining.join(", ") : "unknown tabs";
109
+ }
110
+ function listing(result) {
111
+ const tabs = Array.isArray(result.tabs) ? result.tabs : [];
112
+ if (tabs.length === 0)
113
+ return NO_TABS;
114
+ const rows = [
115
+ ["ID", "NAME", "STATUS", "CWD", "RELEASE", "HOLDER"],
116
+ ...tabs.map(tab => [tab.tabId, tab.name, tab.status, tab.cwd, tab.release, tab.holderPid ?? "-"].map(value => String(value ?? "-"))),
117
+ ];
118
+ const widths = rows[0].map((_, column) => Math.max(...rows.map(row => row[column].length)));
119
+ return `${rows.map(row => row.map((cell, column) => column === row.length - 1 ? cell : cell.padEnd(widths[column])).join(" ")).join("\n")}\n`;
120
+ }
121
+ function status(result) {
122
+ const limits = (result.limits ?? {});
123
+ const counters = (result.counters ?? {});
124
+ const byStatus = (result.tabsByStatus ?? {});
125
+ const lines = [
126
+ `pid: ${String(result.pid ?? "-")}`,
127
+ `build: ${String(result.build ?? "-")}`,
128
+ `release: ${String(result.release ?? "-")}`,
129
+ `generation: ${String(result.generation ?? "-")}`,
130
+ `epoch: ${String(result.epoch ?? "-")}`,
131
+ `uptime: ${String(result.uptimeSeconds ?? "-")}s`,
132
+ `detachment: ${String(result.detachmentMode ?? "unknown")}${result.detachmentVerified === false ? " (not verified)" : ""}`,
133
+ `tabs: ${String(result.tabs ?? 0)}${Object.keys(byStatus).length > 0 ? ` (${Object.entries(byStatus).map(([key, value]) => `${key} ${String(value)}`).join(", ")})` : ""}`,
134
+ `clients: ${String(result.clients ?? 0)}`,
135
+ `limits: ${Object.entries(limits).map(([key, value]) => `${key} ${String(value)}`).join(", ")}`,
136
+ `counters: ${Object.entries(counters).map(([key, value]) => `${key} ${String(value)}`).join(", ")}`,
137
+ ...(Array.isArray(result.logs) ? result.logs.map(path => `log: ${String(path)}`) : []),
138
+ ];
139
+ return `${lines.join("\n")}\n`;
140
+ }
@@ -10,6 +10,8 @@ import type { OwnedUiApplicationPort, PresentationTerminalPort } from "../contra
10
10
  import type { AgentSettingsPort } from "../contracts/agent-engine/index.js";
11
11
  import type { OwnedUiPromptSuggestionGeneratorPort, UiAccentColor } from "../contracts/owned-ui/index.js";
12
12
  import type { ReleaseNoteCatalog } from "../features/owned-ui/release-notes.js";
13
+ import type { TabBridgeClient } from "../foundation/resident-tabs/bridge-client.js";
14
+ import { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
13
15
  /**
14
16
  * The session backend composition wires: the owned-UI port, the prompt-suggestion generator, and
15
17
  * the agent settings port. `settingsPort` stays outside the owned-UI contract because its type
@@ -17,6 +19,7 @@ import type { ReleaseNoteCatalog } from "../features/owned-ui/release-notes.js";
17
19
  */
18
20
  export interface ComposedSessionBackend extends PiSessionPresenterBackend, OwnedUiPromptSuggestionGeneratorPort {
19
21
  settingsPort(): AgentSettingsPort | null;
22
+ generateResidentTabName(instruction: string, maxTokens: number, signal: AbortSignal): Promise<string | null>;
20
23
  }
21
24
  /** The process-level engine host as composition drives it: sessions, the live accent, and disposal. */
22
25
  export interface ComposedEngineHost {
@@ -38,6 +41,8 @@ export interface OwnedUiCompositionOptions {
38
41
  * session settings-free, which is what the pinned comparison paths use.
39
42
  */
40
43
  readonly profileId?: string;
44
+ /** Hide resident-only tab controls for the explicit direct single-agent launch. */
45
+ readonly hideTabSettings?: boolean;
41
46
  /**
42
47
  * Whether A1's product-specific surfaces are reachable. Comparison profiles
43
48
  * use the same composition with those surfaces withheld.
@@ -55,11 +60,32 @@ export interface OwnedUiCompositionOptions {
55
60
  readonly checkForNewerRelease?: (options: StartupReleaseCheckOptions) => Promise<AvailableRelease | null>;
56
61
  /** Exact native process inspection injected by the shipped entry; absent SDK seams do not publish claims. */
57
62
  readonly inspectProcess?: (pid: number) => Promise<NativeProcessIdentity | null>;
63
+ /** Private credentials were consumed by the entry before composition or extensions loaded. */
64
+ readonly tab?: {
65
+ readonly bridge: TabBridgeClient;
66
+ readonly dataDir: string;
67
+ readonly incarnation: number;
68
+ };
69
+ /** One prompt-adjacent notice explaining why a resident launch fell back. */
70
+ readonly residentFallbackNotice?: string;
58
71
  }
59
72
  export interface OwnedUiComposition {
60
73
  readonly application: OwnedUiApplicationPort;
61
74
  /** Present when a profile was supplied, so the caller can resolve settings before start. */
62
75
  readonly settings: OwnedSettingsManager | null;
63
76
  }
77
+ export declare function residentStartupPresentation(resident: boolean, fallbackNotice?: string): {
78
+ readonly startup?: {
79
+ readonly quiet?: boolean;
80
+ readonly notices?: readonly [{
81
+ readonly kind: "warning";
82
+ readonly message: string;
83
+ }];
84
+ };
85
+ };
64
86
  export declare function composeOwnedUiApplication(options?: OwnedUiCompositionOptions): Promise<OwnedUiApplicationPort>;
65
87
  export declare function composeOwnedUi(options?: OwnedUiCompositionOptions): Promise<OwnedUiComposition>;
88
+ export declare function bindResidentTabBridge(tabBridge: TabBridgeClient, backend: Pick<ComposedSessionBackend, "identity" | "session" | "workflows" | "generateResidentTabName">, recovery?: Pick<ResidentTabRecovery, "settled"> | null, options?: {
89
+ readonly autoName?: boolean;
90
+ readonly onVisibility?: (visible: boolean) => void;
91
+ }): Promise<() => void>;
@@ -4,7 +4,7 @@ import { PRODUCT_IDENTITY } from "../product-identity.js";
4
4
  import { PromptHistoryService } from "../features/prompt-history/service.js";
5
5
  import { PromptImageSidecar } from "../features/prompt-history/image-sidecar.js";
6
6
  import { resolvePromptHistoryPath } from "../features/prompt-history/paths.js";
7
- import { resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
7
+ import { resolveLaunchProfilePaths, resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
8
8
  import { resolveProductPaths } from "../foundation/lifecycle/paths.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";
@@ -18,7 +18,14 @@ import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentat
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
+ import { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
22
+ import { deriveResidentTabStatus } from "../features/resident-tab/status-deriver.js";
21
23
  const SESSION_RUNTIME_REFRESH_MS = 30_000;
24
+ export function residentStartupPresentation(resident, fallbackNotice) {
25
+ if (resident)
26
+ return { startup: { quiet: true } };
27
+ return fallbackNotice === undefined ? {} : { startup: { notices: [{ kind: "warning", message: fallbackNotice }] } };
28
+ }
22
29
  export async function composeOwnedUiApplication(options = {}) {
23
30
  return (await composeOwnedUi(options)).application;
24
31
  }
@@ -82,17 +89,33 @@ export async function composeOwnedUi(options = {}) {
82
89
  sessionRuntimeRefreshTimer.unref();
83
90
  };
84
91
  const productPaths = resolveProductPaths();
92
+ const profilePaths = resolveLaunchProfilePaths();
85
93
  // Rationale: the manager reads the engine's settings port lazily, after the session exists.
86
94
  let backend;
87
95
  const settings = options.profileId === undefined
88
96
  ? null
89
97
  : new OwnedSettingsManager({
90
- configDir: resolveProductPaths().configDir,
98
+ profileDir: profilePaths.agentProfile,
91
99
  profileId: options.profileId,
100
+ namespace: PRODUCT_IDENTITY.commandName,
101
+ legacyConfigDir: productPaths.configDir,
92
102
  agentProvider: () => backend.settingsPort(),
103
+ ...(options.hideTabSettings ? { hiddenOwnedSettingIds: ["tabsMax", "tabsAutoName"] } : {}),
93
104
  hiddenAgentSettingIds: ["fullscreenWheelScrollLines"],
94
105
  agentSettingLabelOverrides: { fullscreenCopyOnSelect: "Copy on select" },
95
106
  });
107
+ const tabBridge = options.tab?.bridge ?? null;
108
+ if (tabBridge !== null)
109
+ await tabBridge.start().catch(() => false);
110
+ const projectTrustPrompt = options.projectTrustPrompt === undefined ? undefined : async (request) => {
111
+ tabBridge?.status("needs-input", false, 1, 0);
112
+ try {
113
+ return await options.projectTrustPrompt(request);
114
+ }
115
+ finally {
116
+ tabBridge?.status("starting");
117
+ }
118
+ };
96
119
  // Invariant: process-wide engine state has one owner. The host installs the HTTP dispatcher, applies
97
120
  // the theme singletons, and announces startup notices once; every session is created through it.
98
121
  const host = await (options.createEngineHost ?? (async (hostOptions) => createPiEngineHost(hostOptions)))({
@@ -105,21 +128,49 @@ export async function composeOwnedUi(options = {}) {
105
128
  packageBorderProjection: ownedSurfaces,
106
129
  apply: { base: applyConfiguredPiTheme, accentColor: setPiAccentColor, packageBorderProjection: setPiPackageBorderProjectionEnabled },
107
130
  },
108
- ...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
131
+ ...(projectTrustPrompt === undefined ? {} : { projectTrustPrompt }),
109
132
  });
133
+ const sessionForkPrompt = options.sessionForkPrompt === undefined ? undefined : async (request) => {
134
+ tabBridge?.status("needs-input", false, 1, 0);
135
+ try {
136
+ return await options.sessionForkPrompt(request);
137
+ }
138
+ finally {
139
+ tabBridge?.status("starting");
140
+ }
141
+ };
110
142
  try {
111
143
  backend = await host.create({
112
144
  cwd,
113
145
  ...(ownedSurfaces ? { repositoryContextReader: repositoryContextReaderFor(undefined) } : {}),
114
146
  ...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
115
147
  ...(options.sessionSelection === undefined ? {} : { sessionSelection: options.sessionSelection }),
116
- ...(options.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: options.sessionForkPrompt }),
148
+ ...(sessionForkPrompt === undefined ? {} : { sessionForkPrompt }),
117
149
  });
118
150
  }
119
151
  catch (error) {
120
152
  await host.dispose();
121
153
  throw error;
122
154
  }
155
+ let recoveryDegraded = false;
156
+ let presentRecoveryDegraded = (_degraded) => { };
157
+ const tabRecovery = options.tab === undefined ? null : new ResidentTabRecovery(options.tab.dataDir, {
158
+ onDegraded: degraded => {
159
+ recoveryDegraded = degraded;
160
+ options.tab.bridge.degraded(degraded);
161
+ presentRecoveryDegraded(degraded);
162
+ },
163
+ });
164
+ const recoveredTabInput = await tabRecovery?.initialize(currentBackendSessionFile(backend), backend.identity.sessionId);
165
+ if (tabBridge !== null && settings !== null && ownedSurfaces)
166
+ tabBridge.accent(settings.value("accentColor"));
167
+ let residentTabVisible = true;
168
+ let applyResidentTabVisibility = () => { };
169
+ let removeControllerBoundaryInput = () => { };
170
+ const unsubscribeTabBridge = tabBridge === null ? () => { } : await bindResidentTabBridge(tabBridge, backend, tabRecovery, {
171
+ autoName: settings?.value("tabsAutoName") ?? true,
172
+ onVisibility: visible => { residentTabVisible = visible; applyResidentTabVisibility(); },
173
+ });
123
174
  let releaseNotes = null;
124
175
  let releaseNotesFailure;
125
176
  if (ownedSurfaces) {
@@ -142,7 +193,11 @@ export async function composeOwnedUi(options = {}) {
142
193
  }));
143
194
  const unsubscribeAccent = settings === null || !ownedSurfaces
144
195
  ? () => { }
145
- : settings.onChange(current => host.setAccentColor(current.value("accentColor")));
196
+ : settings.onChange(current => {
197
+ const accent = current.value("accentColor");
198
+ host.setAccentColor(accent);
199
+ tabBridge?.accent(accent);
200
+ });
146
201
  // Rationale: Routes open only after shell construction.
147
202
  const references = {
148
203
  changelog: async (input) => {
@@ -228,13 +283,25 @@ export async function composeOwnedUi(options = {}) {
228
283
  },
229
284
  }),
230
285
  ...(ownedSurfaces ? { sessionLayout: "custom-viewport" } : {}),
286
+ ...(tabBridge === null || tabRecovery === null ? {} : { residentTab: {
287
+ requestDetach: () => tabBridge.requestDetach(),
288
+ reportStatus: status => tabBridge.status(status),
289
+ stallNoticeSeconds: 300,
290
+ admit: input => tabRecovery.admit({ ...input, incarnation: options.tab.incarnation }),
291
+ dispatched: () => tabRecovery.dispatched(),
292
+ correlated: (submissionId, entryId) => tabRecovery.correlated(submissionId, entryId),
293
+ checkpoint: text => tabRecovery.checkpoint(text),
294
+ gracefulStop: sessionFile => tabRecovery.gracefulStop(sessionFile),
295
+ ...(recoveredTabInput === undefined ? {} : { recovered: recoveredTabInput }),
296
+ } }),
231
297
  },
232
298
  presentation: {
233
299
  ...(options.terminal === undefined ? {} : { terminal: createPiTerminalBridge(options.terminal) }),
300
+ ...residentStartupPresentation(tabBridge !== null, options.residentFallbackNotice),
234
301
  ...(clipboardDiagnostics === null ? {} : { input: { onEvent: event => clipboardDiagnostics.runtime(event) } }),
235
302
  ...(viewportSettings === null ? {} : { viewportSettings }),
236
303
  ...(backgroundSettings === null ? {} : { backgroundSettings }),
237
- ...(settings === null || !ownedSurfaces ? {} : {
304
+ ...(settings === null || !ownedSurfaces || tabBridge !== null ? {} : {
238
305
  quitOutro: { snapshot: () => quitOutroSettingsSnapshot(settings), interactive: process.stdout.isTTY === true },
239
306
  }),
240
307
  },
@@ -254,7 +321,21 @@ export async function composeOwnedUi(options = {}) {
254
321
  // Invariant: one terminal host owns the process terminal; the session presenter reaches it only through
255
322
  // the host and paints once attached.
256
323
  terminalHost = createdTerminalHost = new OwnedUiTerminalHost(sessionTerminalHostOptions(presenterOptions));
324
+ if (tabBridge !== null) {
325
+ applyResidentTabVisibility = () => terminalHost.setPresentationVisible(residentTabVisible);
326
+ removeControllerBoundaryInput = terminalHost.runtime.addPreInputListener(data => ({
327
+ data: tabBridge.consumeControllerBoundaryInput(data),
328
+ }));
329
+ }
257
330
  presenter = new OwnedUiSessionPresenter(terminalHost, presenterOptions);
331
+ presentRecoveryDegraded = degraded => {
332
+ if (!degraded)
333
+ return;
334
+ presenter.root.appendWorkflowStatus("draft recovery degraded");
335
+ terminalHost.runtime.requestRender();
336
+ };
337
+ if (recoveryDegraded)
338
+ presentRecoveryDegraded(true);
258
339
  terminalHost.attach(presenter);
259
340
  }
260
341
  catch (error) {
@@ -294,6 +375,7 @@ export async function composeOwnedUi(options = {}) {
294
375
  exitNotice = import("../app/session-shell/exit-notice.js")
295
376
  .then(module => module.armExitNotice(backend, exitNoticePath), () => null);
296
377
  presenter.start();
378
+ applyResidentTabVisibility();
297
379
  void announceNewerRelease().catch(() => undefined);
298
380
  },
299
381
  flush: () => backend.session.flushEvents(),
@@ -308,6 +390,7 @@ export async function composeOwnedUi(options = {}) {
308
390
  finally {
309
391
  // Invariant: the shared spares outlive every shell and stop only after the last one is disposed.
310
392
  clipboardServices?.dispose();
393
+ removeControllerBoundaryInput();
311
394
  unsubscribeAccent();
312
395
  sessionRuntimeDisposed = true;
313
396
  if (sessionRuntimeRefreshTimer !== null)
@@ -323,6 +406,8 @@ export async function composeOwnedUi(options = {}) {
323
406
  await releaseNoteClaim?.release();
324
407
  suggestionDiagnostics?.dispose();
325
408
  clipboardDiagnostics?.dispose();
409
+ unsubscribeTabBridge();
410
+ tabBridge?.close();
326
411
  // Invariant: the host outlives the application; disposing it cancels host-level probes last.
327
412
  await host.dispose();
328
413
  }
@@ -330,6 +415,91 @@ export async function composeOwnedUi(options = {}) {
330
415
  };
331
416
  return { application, settings };
332
417
  }
418
+ export async function bindResidentTabBridge(tabBridge, backend, recovery = null, options = {}) {
419
+ await tabBridge.start().catch(() => false);
420
+ tabBridge.status("starting");
421
+ const currentSessionFile = () => currentBackendSessionFile(backend);
422
+ let pendingAutoName = null;
423
+ const announceSession = () => {
424
+ const name = backend.session.view().status.footer?.sessionName ?? null;
425
+ const nameSource = name === pendingAutoName ? "auto" : name === null || /^agent(?: \d+)?$/i.test(name) ? "default" : "user";
426
+ tabBridge.session(currentSessionFile(), name, nameSource);
427
+ };
428
+ let working = false;
429
+ let lastTurnFailed = false;
430
+ let autoNameAttempted = false;
431
+ const reportStatus = (lifecycle) => {
432
+ const view = backend.session.view();
433
+ const pendingRequests = view.dialog == null ? 0 : 1;
434
+ const queuedInput = view.editor?.queuedSubmissions.length ?? 0;
435
+ tabBridge.status(deriveResidentTabStatus({
436
+ lifecycle: lifecycle ?? view.lifecycle,
437
+ pendingRequests,
438
+ queuedInput,
439
+ ...(lastTurnFailed ? { stopReason: "error" } : {}),
440
+ }), false, pendingRequests, queuedInput);
441
+ };
442
+ announceSession();
443
+ const unsubscribeCommands = typeof tabBridge.onCommand === "function" ? tabBridge.onCommand(command => {
444
+ if (command.type === "visibility") {
445
+ options.onVisibility?.(command.visible);
446
+ }
447
+ else if (command.type === "rename") {
448
+ void backend.workflows.executeWorkflow({ command: "name", argument: command.name }).catch(() => { });
449
+ }
450
+ else if (command.type === "stop") {
451
+ void backend.session.dispose().catch(() => { });
452
+ }
453
+ }) : () => { };
454
+ const unsubscribeEvents = backend.session.onEvent(event => {
455
+ if (event.type === "session-lifecycle") {
456
+ const wasWorking = working;
457
+ working = event.lifecycle === "busy";
458
+ if (working)
459
+ lastTurnFailed = false;
460
+ reportStatus(event.lifecycle);
461
+ if (wasWorking && !working && recovery !== null) {
462
+ void recovery.settled(currentSessionFile()).catch(() => tabBridge.degraded(true));
463
+ }
464
+ if (wasWorking && !working && options.autoName === true && !autoNameAttempted) {
465
+ autoNameAttempted = true;
466
+ const view = backend.session.view();
467
+ const currentName = view.status.footer?.sessionName;
468
+ const prompt = view.transcript.find(block => block.kind === "user")?.text.trim();
469
+ if (prompt && (currentName == null || /^agent(?: \d+)?$/i.test(currentName))) {
470
+ void import("../features/resident-tab/index.js").then(({ generateAutoName }) => generateAutoName(prompt, request => backend.generateResidentTabName(request.prompt, request.maxTokens, request.signal))).then(async (name) => {
471
+ pendingAutoName = name;
472
+ const result = await backend.workflows.executeWorkflow({ command: "name", argument: name });
473
+ if (result.outcome === "completed")
474
+ tabBridge.session(currentSessionFile(), name, "auto");
475
+ else if (pendingAutoName === name)
476
+ pendingAutoName = null;
477
+ }).catch(() => { pendingAutoName = null; });
478
+ }
479
+ }
480
+ }
481
+ if (event.type === "agent-run-settled") {
482
+ lastTurnFailed = !event.successful && !event.aborted;
483
+ tabBridge.prompt(event.aborted);
484
+ reportStatus();
485
+ }
486
+ if (event.type === "session-view") {
487
+ announceSession();
488
+ reportStatus();
489
+ }
490
+ });
491
+ return () => {
492
+ unsubscribeCommands();
493
+ unsubscribeEvents();
494
+ };
495
+ }
496
+ function currentBackendSessionFile(backend) {
497
+ const direct = backend.identity.currentSessionFile?.();
498
+ if (direct !== undefined && direct !== null)
499
+ return direct;
500
+ const resume = backend.identity.currentSessionResumeMetadata();
501
+ return resume === null ? null : `${resume.sessionDir}/${resume.sessionId}`;
502
+ }
333
503
  async function readPackageVersion() {
334
504
  const manifest = JSON.parse(await readFile(new URL("../../package.json", import.meta.url), "utf8"));
335
505
  const version = manifest?.version;
@@ -36,6 +36,7 @@ export interface OwnedUiSessionIdentityPort {
36
36
  /** Actual session replacements, excluding delivery-only invalidation of callbacks. */
37
37
  readonly sessionBindingGeneration: number;
38
38
  readonly disposed: boolean;
39
+ currentSessionFile(): string | null;
39
40
  currentSessionResumeMetadata(): OwnedUiSessionResumeMetadata | null;
40
41
  }
41
42
  /** The session lifecycle, its view, its event stream, and command admission. */
@@ -3,7 +3,7 @@ export type { DevelopmentLaunchEnvironment } from "./development-launch.js";
3
3
  export { PI_PROFILE_RESOURCE_DIRECTORIES, initializeProductProfile } from "./initialize-profile.js";
4
4
  export type { InitializedProfile } from "./initialize-profile.js";
5
5
  export { interactiveLaunchIntent, parseSessionSelection } from "./intent.js";
6
- export type { InteractiveLaunchIntent, LaunchIntent, LaunchProfileId, SessionSelection } from "./intent.js";
6
+ export type { InteractiveLaunchIntent, InteractiveLaunchMode, LaunchIntent, LaunchProfileId, SessionSelection } from "./intent.js";
7
7
  export { configurationRootForProfile, resolveLaunchProfilePaths, resolvePromptHistoryDataDir } from "./profile-paths.js";
8
8
  export type { LaunchProfilePathOptions, LaunchProfilePaths } from "./profile-paths.js";
9
9
  export { prepareInteractiveLaunch } from "./prepare-launch.js";
@@ -3,10 +3,12 @@ import type { LaunchProfileId } from "../../foundation/lifecycle/index.js";
3
3
  export { parseSessionSelection } from "../../foundation/lifecycle/index.js";
4
4
  export type { SessionSelection } from "../../foundation/lifecycle/index.js";
5
5
  export type { LaunchProfileId } from "../../foundation/lifecycle/index.js";
6
+ export type InteractiveLaunchMode = "tabs" | "solo";
6
7
  export interface InteractiveLaunchIntent {
7
8
  readonly kind: "interactive";
8
9
  readonly profileId: LaunchProfileId;
10
+ readonly launchMode: InteractiveLaunchMode;
9
11
  readonly sessionSelection?: SessionSelection;
10
12
  }
11
13
  export type LaunchIntent = InteractiveLaunchIntent;
12
- export declare function interactiveLaunchIntent(profileId: LaunchProfileId, sessionSelection?: SessionSelection): InteractiveLaunchIntent;
14
+ export declare function interactiveLaunchIntent(profileId: LaunchProfileId, sessionSelection?: SessionSelection, launchMode?: InteractiveLaunchMode): InteractiveLaunchIntent;
@@ -1,8 +1,11 @@
1
1
  import { sessionSelectionArguments } from "../../foundation/lifecycle/index.js";
2
2
  export { parseSessionSelection } from "../../foundation/lifecycle/index.js";
3
- export function interactiveLaunchIntent(profileId, sessionSelection) {
4
- if (sessionSelection !== undefined && profileId !== "a1")
5
- throw new Error("session selection requires the normal A1 profile");
3
+ export function interactiveLaunchIntent(profileId, sessionSelection, launchMode = profileId === "a1" ? "tabs" : "solo") {
4
+ if (sessionSelection !== undefined && (profileId !== "a1" || launchMode === "solo")) {
5
+ throw new Error("session selection requires the normal tabbed A1 launch");
6
+ }
7
+ if (profileId === "pi" && launchMode !== "solo")
8
+ throw new Error("the Pi comparison launch is always solo");
6
9
  sessionSelectionArguments(sessionSelection);
7
- return Object.freeze({ kind: "interactive", profileId, ...(sessionSelection === undefined ? {} : { sessionSelection }) });
10
+ return Object.freeze({ kind: "interactive", profileId, launchMode, ...(sessionSelection === undefined ? {} : { sessionSelection }) });
8
11
  }