@sayknow-cli/coding-agent 0.3.12 → 0.3.15

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 (248) hide show
  1. package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
  2. package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
  3. package/dist/types/cli/classify-install-target.d.ts +0 -10
  4. package/dist/types/cli/daemon-cli.d.ts +2 -0
  5. package/dist/types/cli/initial-message.d.ts +1 -1
  6. package/dist/types/cli/malloc-env-guard.d.ts +65 -0
  7. package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
  8. package/dist/types/cli/notify-cli.d.ts +3 -1
  9. package/dist/types/cli/session-picker.d.ts +4 -3
  10. package/dist/types/cli/update-cli.d.ts +22 -3
  11. package/dist/types/cli.d.ts +5 -0
  12. package/dist/types/commands/daemon.d.ts +6 -2
  13. package/dist/types/commands/launch.d.ts +1 -0
  14. package/dist/types/commands/notify.d.ts +6 -0
  15. package/dist/types/commands/web-search.d.ts +1 -1
  16. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
  17. package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
  18. package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
  19. package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
  20. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  21. package/dist/types/commit/analysis/summary.d.ts +1 -1
  22. package/dist/types/commit/changelog/generate.d.ts +3 -3
  23. package/dist/types/commit/changelog/index.d.ts +2 -2
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  26. package/dist/types/commit/shared-llm.d.ts +9 -9
  27. package/dist/types/config/keybindings.d.ts +134 -129
  28. package/dist/types/config/model-profile-activation.d.ts +1 -0
  29. package/dist/types/config/model-registry.d.ts +35 -35
  30. package/dist/types/config/models-config-schema.d.ts +111 -111
  31. package/dist/types/config/settings-schema.d.ts +56 -16
  32. package/dist/types/config/settings.d.ts +2 -0
  33. package/dist/types/coordinator/contract.d.ts +1 -1
  34. package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
  35. package/dist/types/coordinator-mcp/policy.d.ts +3 -0
  36. package/dist/types/coordinator-mcp/server.d.ts +79 -6
  37. package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
  38. package/dist/types/daemon/control-types.d.ts +10 -0
  39. package/dist/types/daemon/operator-contract.d.ts +47 -0
  40. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
  41. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
  42. package/dist/types/discovery/helpers.d.ts +1 -1
  43. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  44. package/dist/types/edit/modes/patch.d.ts +2 -2
  45. package/dist/types/extensibility/extensions/types.d.ts +3 -1
  46. package/dist/types/goals/tools/goal-tool.d.ts +6 -6
  47. package/dist/types/i18n/messages/en.d.ts +8 -8
  48. package/dist/types/internal-urls/local-protocol.d.ts +13 -9
  49. package/dist/types/lsp/index.d.ts +9 -9
  50. package/dist/types/lsp/types.d.ts +9 -9
  51. package/dist/types/main.d.ts +66 -2
  52. package/dist/types/modes/acp/acp-agent.d.ts +19 -2
  53. package/dist/types/modes/acp/acp-mode.d.ts +12 -0
  54. package/dist/types/modes/components/assistant-message.d.ts +1 -1
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  56. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  57. package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
  58. package/dist/types/modes/components/pet-selector.d.ts +11 -0
  59. package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
  60. package/dist/types/modes/components/session-selector.d.ts +22 -9
  61. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  62. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  63. package/dist/types/modes/components/status-line.d.ts +0 -10
  64. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  65. package/dist/types/modes/components/user-message.d.ts +2 -1
  66. package/dist/types/modes/controllers/event-controller.d.ts +4 -0
  67. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  68. package/dist/types/modes/interactive-mode.d.ts +24 -2
  69. package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
  70. package/dist/types/modes/print-mode.d.ts +3 -4
  71. package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
  72. package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
  73. package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
  74. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
  75. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
  76. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
  77. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
  78. package/dist/types/modes/theme/defaults/index.d.ts +568 -568
  79. package/dist/types/modes/types.d.ts +28 -2
  80. package/dist/types/modes/utils/abort-message.d.ts +1 -1
  81. package/dist/types/modes/utils/context-usage.d.ts +4 -19
  82. package/dist/types/modes/utils/irc-message.d.ts +38 -0
  83. package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
  84. package/dist/types/notifications/config.d.ts +34 -0
  85. package/dist/types/notifications/daemon-paths.d.ts +7 -0
  86. package/dist/types/notifications/index.d.ts +27 -0
  87. package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
  88. package/dist/types/notifications/notification-service.d.ts +144 -0
  89. package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
  90. package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
  91. package/dist/types/notifications/telegram-daemon.d.ts +130 -3
  92. package/dist/types/notifications/telegram-reference.d.ts +19 -2
  93. package/dist/types/notifications/topic-registry.d.ts +24 -4
  94. package/dist/types/registry/agent-registry.d.ts +2 -0
  95. package/dist/types/rlm/index.d.ts +1 -1
  96. package/dist/types/sdk.d.ts +2 -0
  97. package/dist/types/secrets/obfuscator.d.ts +1 -1
  98. package/dist/types/session/agent-session.d.ts +40 -3
  99. package/dist/types/session/cache-economics.d.ts +79 -0
  100. package/dist/types/session/context-estimation.d.ts +31 -0
  101. package/dist/types/session/provider-safety-stop.d.ts +5 -0
  102. package/dist/types/session/session-manager.d.ts +124 -4
  103. package/dist/types/session/session-storage.d.ts +155 -13
  104. package/dist/types/setup/credential-auto-import.d.ts +2 -2
  105. package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
  106. package/dist/types/skc-runtime/state-schema.d.ts +19 -19
  107. package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
  108. package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
  109. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
  110. package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
  111. package/dist/types/task/agents.d.ts +2 -2
  112. package/dist/types/task/executor.d.ts +3 -0
  113. package/dist/types/task/receipt.d.ts +3 -1
  114. package/dist/types/task/types.d.ts +35 -27
  115. package/dist/types/tools/ask-answer-registry.d.ts +5 -0
  116. package/dist/types/tools/bash.d.ts +1 -1
  117. package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
  118. package/dist/types/tools/browser.d.ts +28 -28
  119. package/dist/types/tools/computer.d.ts +20 -20
  120. package/dist/types/tools/cron.d.ts +2 -2
  121. package/dist/types/tools/debug.d.ts +40 -40
  122. package/dist/types/tools/gh.d.ts +10 -10
  123. package/dist/types/tools/image-gen.d.ts +2 -2
  124. package/dist/types/tools/index.d.ts +58 -6
  125. package/dist/types/tools/monitor.d.ts +6 -6
  126. package/dist/types/tools/skill-discovery.d.ts +4 -4
  127. package/dist/types/tools/subagent.d.ts +12 -12
  128. package/dist/types/tools/telegram-send.d.ts +7 -0
  129. package/dist/types/tools/todo-write.d.ts +8 -8
  130. package/dist/types/utils/external-editor.d.ts +2 -0
  131. package/dist/types/web/insane/bridge.d.ts +9 -9
  132. package/dist/types/web/search/index.d.ts +4 -4
  133. package/package.json +8 -8
  134. package/scripts/g004-tmux-smoke.ts +196 -36
  135. package/src/cli/daemon-cli.ts +19 -29
  136. package/src/cli/malloc-env-guard.test.ts +104 -0
  137. package/src/cli/malloc-env-guard.ts +149 -0
  138. package/src/cli/notify-cli.ts +78 -14
  139. package/src/cli/session-picker.ts +23 -40
  140. package/src/cli/update-cli.ts +56 -37
  141. package/src/cli.ts +51 -10
  142. package/src/commands/daemon.ts +19 -5
  143. package/src/commands/harness.ts +515 -47
  144. package/src/commands/launch.ts +11 -3
  145. package/src/commands/notify.ts +5 -1
  146. package/src/commands/session.ts +6 -1
  147. package/src/config/keybindings.ts +5 -0
  148. package/src/config/model-profile-activation.ts +1 -1
  149. package/src/config/model-profiles.ts +14 -14
  150. package/src/config/settings-schema.ts +41 -1
  151. package/src/config/settings.ts +96 -45
  152. package/src/coordinator/contract.ts +1 -0
  153. package/src/coordinator-mcp/model-preset.ts +100 -0
  154. package/src/coordinator-mcp/policy.ts +30 -0
  155. package/src/coordinator-mcp/server.ts +2958 -430
  156. package/src/coordinator-mcp/session-reaper.ts +129 -0
  157. package/src/daemon/control-types.ts +11 -0
  158. package/src/daemon/operator-contract.ts +121 -0
  159. package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
  160. package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
  161. package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
  162. package/src/extensibility/extensions/runner.ts +1 -1
  163. package/src/extensibility/extensions/types.ts +3 -1
  164. package/src/internal-urls/docs-index.generated.ts +10 -9
  165. package/src/internal-urls/local-protocol.ts +29 -10
  166. package/src/main.ts +301 -80
  167. package/src/modes/acp/acp-agent.ts +730 -100
  168. package/src/modes/acp/acp-mode.ts +29 -2
  169. package/src/modes/components/assistant-message.ts +32 -3
  170. package/src/modes/components/bash-execution.ts +43 -13
  171. package/src/modes/components/custom-editor.ts +5 -0
  172. package/src/modes/components/footer.ts +3 -3
  173. package/src/modes/components/hook-selector.ts +12 -0
  174. package/src/modes/components/irc-sidebar.ts +206 -0
  175. package/src/modes/components/pet-selector.ts +55 -0
  176. package/src/modes/components/sayknow-pet-widget.ts +360 -0
  177. package/src/modes/components/session-selector.ts +222 -221
  178. package/src/modes/components/settings-selector.ts +10 -0
  179. package/src/modes/components/status-line/segments.ts +14 -7
  180. package/src/modes/components/status-line/types.ts +1 -1
  181. package/src/modes/components/status-line.ts +4 -171
  182. package/src/modes/components/tool-execution.ts +47 -6
  183. package/src/modes/components/user-message.ts +26 -9
  184. package/src/modes/controllers/command-controller.ts +28 -2
  185. package/src/modes/controllers/event-controller.ts +159 -36
  186. package/src/modes/controllers/extension-ui-controller.ts +37 -18
  187. package/src/modes/controllers/input-controller.ts +8 -2
  188. package/src/modes/controllers/selector-controller.ts +62 -15
  189. package/src/modes/interactive-mode.ts +199 -7
  190. package/src/modes/irc-observation-ledger.ts +127 -0
  191. package/src/modes/print-mode.ts +228 -92
  192. package/src/modes/rpc/rpc-client.ts +58 -1
  193. package/src/modes/rpc/rpc-mode.ts +286 -85
  194. package/src/modes/rpc/rpc-types.ts +15 -1
  195. package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
  196. package/src/modes/shared/agent-wire/command-validation.ts +10 -0
  197. package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
  198. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  199. package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
  200. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
  201. package/src/modes/types.ts +34 -2
  202. package/src/modes/utils/context-usage.ts +56 -92
  203. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  204. package/src/modes/utils/irc-message.ts +138 -0
  205. package/src/modes/utils/ui-helpers.ts +216 -50
  206. package/src/notifications/config.ts +44 -0
  207. package/src/notifications/daemon-paths.ts +8 -0
  208. package/src/notifications/index.ts +535 -75
  209. package/src/notifications/lifecycle-control-runtime.ts +930 -77
  210. package/src/notifications/notification-service.ts +710 -0
  211. package/src/notifications/rate-limit-pool.ts +46 -12
  212. package/src/notifications/telegram-daemon-control.ts +27 -2
  213. package/src/notifications/telegram-daemon.ts +635 -94
  214. package/src/notifications/telegram-reference.ts +73 -13
  215. package/src/notifications/topic-registry.ts +86 -9
  216. package/src/prompts/system/irc-peer-roster.md +1 -0
  217. package/src/prompts/system/subagent-system-prompt.md +1 -0
  218. package/src/prompts/system/system-prompt.md +1 -0
  219. package/src/prompts/tools/irc.md +1 -1
  220. package/src/registry/agent-registry.ts +3 -0
  221. package/src/sdk.ts +52 -1
  222. package/src/secrets/obfuscator.ts +4 -2
  223. package/src/session/agent-session.ts +670 -217
  224. package/src/session/cache-economics.ts +275 -0
  225. package/src/session/context-estimation.ts +91 -0
  226. package/src/session/provider-safety-stop.ts +9 -0
  227. package/src/session/session-dump-format.ts +6 -0
  228. package/src/session/session-manager.ts +809 -39
  229. package/src/session/session-storage.ts +582 -38
  230. package/src/skc-runtime/session-state-sidecar.ts +756 -135
  231. package/src/skc-runtime/tmux-common.ts +26 -7
  232. package/src/skc-runtime/tmux-gc.ts +52 -9
  233. package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
  234. package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
  235. package/src/skc-runtime/tmux-sessions.ts +871 -68
  236. package/src/slash-commands/builtin-registry.ts +153 -9
  237. package/src/slash-commands/helpers/context-report.ts +26 -4
  238. package/src/task/executor.ts +12 -3
  239. package/src/task/index.ts +8 -0
  240. package/src/task/receipt.ts +7 -2
  241. package/src/task/types.ts +26 -0
  242. package/src/tools/ask-answer-registry.ts +25 -0
  243. package/src/tools/browser/tab-supervisor.ts +63 -6
  244. package/src/tools/browser.ts +8 -3
  245. package/src/tools/index.ts +81 -6
  246. package/src/tools/sqlite-reader.ts +8 -4
  247. package/src/tui/output-block.ts +18 -6
  248. package/src/utils/external-editor.ts +6 -1
@@ -145,37 +145,56 @@ export class LocalProtocolHandler implements ProtocolHandler {
145
145
  readonly immutable = false;
146
146
 
147
147
  static #override: LocalProtocolOptions | undefined;
148
+ static #ownedOverrides: Array<{ options: LocalProtocolOptions }> = [];
148
149
 
149
150
  /**
150
- * Install a process-global override that wins over the AgentRegistry-based
151
- * derivation. Used by SDK consumers that wire `localProtocolOptions` on
152
- * `createAgentSession` and by subagents that share their parent's root.
151
+ * Install an explicit local-protocol mapping owned by the caller.
152
+ *
153
+ * The most recently installed live mapping wins. The returned disposer removes
154
+ * only this registration and is safe to call more than once.
155
+ */
156
+ static installOverride(value: LocalProtocolOptions): () => void {
157
+ const registration = { options: value };
158
+ LocalProtocolHandler.#ownedOverrides.push(registration);
159
+ let disposed = false;
160
+ return () => {
161
+ if (disposed) return;
162
+ disposed = true;
163
+ const index = LocalProtocolHandler.#ownedOverrides.indexOf(registration);
164
+ if (index !== -1) LocalProtocolHandler.#ownedOverrides.splice(index, 1);
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Install a process-global test override that wins over owned and registry
170
+ * mappings. Prefer {@link installOverride} for lifecycle-bound production use.
153
171
  */
154
172
  static setOverride(value: LocalProtocolOptions | undefined): void {
155
173
  LocalProtocolHandler.#override = value;
156
174
  }
157
175
 
158
- /** Reset the process-global override. Test-only. */
176
+ /** Reset all process-global local-protocol overrides. Test-only. */
159
177
  static resetOverrideForTests(): void {
160
178
  LocalProtocolHandler.#override = undefined;
179
+ LocalProtocolHandler.#ownedOverrides = [];
161
180
  }
162
181
 
163
182
  /**
164
183
  * Returns the active local-protocol options.
165
184
  *
166
185
  * Resolution order:
167
- * 1. Explicit override installed via {@link setOverride} (used by subagents
168
- * that share their parent's root and by SDK consumers with a custom
169
- * artifacts/session id mapping).
170
- * 2. The main session in `AgentRegistry.global()`. Its `SessionManager`
171
- * supplies both `getArtifactsDir` and `getSessionId`.
186
+ * 1. Direct test override installed via {@link setOverride}.
187
+ * 2. The most recently installed live owned override.
188
+ * 3. A live main session in `AgentRegistry.global()`.
172
189
  */
173
190
  static resolveOptions(): LocalProtocolOptions | undefined {
174
191
  const override = LocalProtocolHandler.#override;
175
192
  if (override) return override;
193
+ const ownedOverride = LocalProtocolHandler.#ownedOverrides.at(-1)?.options;
194
+ if (ownedOverride) return ownedOverride;
176
195
  const main = AgentRegistry.global()
177
196
  .list()
178
- .find(ref => ref.kind === "main");
197
+ .find(ref => ref.kind === "main" && ref.session && (ref.status === "running" || ref.status === "idle"));
179
198
  const sessionManager = main?.session?.sessionManager;
180
199
  if (!sessionManager) return undefined;
181
200
  return {
package/src/main.ts CHANGED
@@ -35,7 +35,9 @@ import { BUNDLED_GROK_BUILD_EXTENSION_ID, getBundledGrokBuildExtensionFactory }
35
35
  import { initializeWithSettings } from "./discovery";
36
36
  import { exportFromFile } from "./export/html";
37
37
  import type { ExtensionUIContext } from "./extensibility/extensions/types";
38
+ import type { SessionSelectionResult } from "./modes/components/session-selector";
38
39
  import type { InteractiveMode } from "./modes/interactive-mode";
40
+ import type { PrintModeOptions } from "./modes/print-mode";
39
41
  import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
40
42
  import type { SubmittedUserInput } from "./modes/types";
41
43
  import { applyCliRuntimeApiKeyOverride } from "./runtime-api-key";
@@ -49,9 +51,17 @@ import {
49
51
  } from "./sdk";
50
52
  import type { AgentSession } from "./session/agent-session";
51
53
  import type { AuthStorage } from "./session/auth-storage";
52
- import { resolveResumableSession, type SessionInfo, SessionManager } from "./session/session-manager";
54
+ import {
55
+ type ResumeSessionIdentity,
56
+ resolveResumableSession,
57
+ type SessionInfo,
58
+ SessionManager,
59
+ type StrictSessionOpenResult,
60
+ } from "./session/session-manager";
53
61
  import { runStartupCredentialAutoImportIfNeeded } from "./setup/credential-auto-import";
54
62
  import { formatModelOnboardingGuidance } from "./setup/model-onboarding-guidance";
63
+ import { persistCoordinatorRuntimeInputReady } from "./skc-runtime/session-state-sidecar";
64
+ import { isTmuxOwnerIsolationCliArgv, runTmuxOwnerIsolationCliFromStdin } from "./skc-runtime/tmux-owner-isolation-cli";
55
65
  import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
56
66
  import { resolvePromptInput } from "./system-prompt";
57
67
  import { persistTaskTokenLog, resolveTaskTokenLogDir, taskTokenLogFromUsage } from "./task/token-log";
@@ -60,9 +70,6 @@ import { getDisplayChangelogEntries, getInstalledVersionChangelogEntry, getNewEn
60
70
  import type { EventBus } from "./utils/event-bus";
61
71
 
62
72
  async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
63
- if (!settings.get("startup.checkUpdate")) {
64
- return;
65
- }
66
73
  try {
67
74
  const response = await fetch("https://registry.npmjs.org/@sayknow-cli/coding-agent/latest");
68
75
  if (!response.ok) return undefined;
@@ -80,6 +87,69 @@ async function checkForNewVersion(currentVersion: string): Promise<string | unde
80
87
  }
81
88
  }
82
89
 
90
+ export type StartupUpdateRoute = "interactive" | "print" | "text" | "json" | "rpc" | "rpc-ui" | "acp" | "bridge";
91
+
92
+ export function classifyStartupUpdateRoute(
93
+ parsed: Pick<Args, "print" | "mode">,
94
+ autoPrint: boolean,
95
+ ): StartupUpdateRoute {
96
+ if (!parsed.print && !autoPrint && parsed.mode === undefined) {
97
+ return "interactive";
98
+ }
99
+ if (parsed.print) {
100
+ return "print";
101
+ }
102
+ return parsed.mode ?? "text";
103
+ }
104
+
105
+ /** Coordinates the non-blocking update check around the interactive UI lifecycle. */
106
+ export class StartupUpdateOrchestrator {
107
+ #versionCheckPromise: Promise<string | undefined> | undefined;
108
+ readonly #route: StartupUpdateRoute;
109
+ readonly #enabled: () => boolean;
110
+ readonly #check: () => Promise<string | undefined>;
111
+
112
+ constructor(route: StartupUpdateRoute, enabled: () => boolean, check: () => Promise<string | undefined>) {
113
+ this.#route = route;
114
+ this.#enabled = enabled;
115
+ this.#check = check;
116
+ }
117
+
118
+ startBeforeInteractiveInitialization(): void {
119
+ if (this.#route !== "interactive" || !this.#enabled() || this.#versionCheckPromise) {
120
+ return;
121
+ }
122
+ try {
123
+ this.#versionCheckPromise = this.#check().catch(() => undefined);
124
+ } catch {
125
+ this.#versionCheckPromise = Promise.resolve(undefined);
126
+ }
127
+ }
128
+
129
+ attachAfterInteractiveInitialization(notify: (version: string) => void): void {
130
+ this.#versionCheckPromise
131
+ ?.then(version => {
132
+ if (version && this.#enabled()) {
133
+ notify(version);
134
+ }
135
+ })
136
+ .catch(() => {});
137
+ }
138
+ }
139
+
140
+ export interface StartupUpdateInteractiveMode {
141
+ init: () => Promise<void>;
142
+ showNewVersionNotification: (version: string) => void;
143
+ }
144
+
145
+ export async function initializeInteractiveModeWithStartupUpdate(
146
+ mode: StartupUpdateInteractiveMode,
147
+ startupUpdate: StartupUpdateOrchestrator,
148
+ ): Promise<void> {
149
+ await mode.init();
150
+ startupUpdate.attachAfterInteractiveInitialization(version => mode.showNewVersionNotification(version));
151
+ }
152
+
83
153
  const RPC_DEFAULTED_SETTING_PATHS: SettingPath[] = [
84
154
  "todo.enabled",
85
155
  "todo.reminders",
@@ -314,12 +384,58 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
314
384
  };
315
385
  }
316
386
 
317
- async function runInteractiveMode(
387
+ interface InteractiveModeFactoryOptions {
388
+ session: AgentSession;
389
+ version: string;
390
+ changelogMarkdown: string | undefined;
391
+ setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
392
+ lspServers: LspStartupServerInfo[] | undefined;
393
+ mcpManager: MCPManager | undefined;
394
+ eventBus?: EventBus;
395
+ }
396
+
397
+ type CreateInteractiveMode = (options: InteractiveModeFactoryOptions) => InteractiveMode;
398
+
399
+ type ResumePickerTerminalCheck = () => boolean;
400
+ type ListForResumePickerReadOnly = (cwd: string, sessionDir?: string) => Promise<SessionInfo[]>;
401
+ type SelectResumeSession = (sessions: SessionInfo[]) => Promise<SessionSelectionResult>;
402
+ type OpenExistingSessionStrict = (
403
+ identity: ResumeSessionIdentity,
404
+ sessionDir?: string,
405
+ ) => Promise<StrictSessionOpenResult>;
406
+
407
+ export const BARE_RESUME_CONFLICT_ERROR =
408
+ "--resume without a session cannot be combined with --continue, --fork, or --no-session.";
409
+ export const BARE_RESUME_INTERACTIVE_ERROR = "--resume requires an interactive terminal; use --resume <id>.";
410
+ export const BARE_RESUME_OPEN_ERROR = "Could not open the selected session. Use --resume <id>.";
411
+
412
+ function isBareResume(parsed: Args): boolean {
413
+ return (
414
+ parsed.resume === true &&
415
+ parsed.version !== true &&
416
+ parsed.listModels === undefined &&
417
+ parsed.export === undefined
418
+ );
419
+ }
420
+
421
+ function hasBareResumeConflict(parsed: Args): boolean {
422
+ return parsed.continue === true || parsed.fork !== undefined || parsed.noSession === true;
423
+ }
424
+
425
+ function isNormalLocalInteractiveRoute(parsed: Args): boolean {
426
+ return parsed.mode === undefined && parsed.print !== true;
427
+ }
428
+
429
+ function hasResumePickerTerminal(): boolean {
430
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
431
+ }
432
+
433
+ export async function runInteractiveMode(
318
434
  session: AgentSession,
319
435
  version: string,
320
436
  changelogMarkdown: string | undefined,
321
437
  notifs: (InteractiveModeNotify | null)[],
322
- versionCheckPromise: Promise<string | undefined>,
438
+ startupUpdate: StartupUpdateOrchestrator,
323
439
  initialMessages: string[],
324
440
  setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
325
441
  lspServers: LspStartupServerInfo[] | undefined,
@@ -327,30 +443,36 @@ async function runInteractiveMode(
327
443
  eventBus?: EventBus,
328
444
  initialMessage?: string,
329
445
  initialImages?: ImageContent[],
446
+ createInteractiveMode?: CreateInteractiveMode,
447
+ resumeAction?: "continue-tail" | "open-idle",
330
448
  ): Promise<void> {
331
- const { InteractiveMode } = await import("./modes/interactive-mode");
332
- const mode = new InteractiveMode(
333
- session,
334
- version,
335
- changelogMarkdown,
336
- setExtensionUIContext,
337
- lspServers,
338
- mcpManager,
339
- eventBus,
340
- );
341
-
342
- await mode.init();
449
+ const mode = createInteractiveMode
450
+ ? createInteractiveMode({
451
+ session,
452
+ version,
453
+ changelogMarkdown,
454
+ setExtensionUIContext,
455
+ lspServers,
456
+ mcpManager,
457
+ eventBus,
458
+ })
459
+ : new (await import("./modes/interactive-mode")).InteractiveMode(
460
+ session,
461
+ version,
462
+ changelogMarkdown,
463
+ setExtensionUIContext,
464
+ lspServers,
465
+ mcpManager,
466
+ eventBus,
467
+ );
343
468
 
344
- versionCheckPromise
345
- .then(newVersion => {
346
- if (!settings.get("startup.checkUpdate")) {
347
- return;
348
- }
349
- if (newVersion) {
350
- mode.showNewVersionNotification(newVersion);
351
- }
352
- })
353
- .catch(() => {});
469
+ await initializeInteractiveModeWithStartupUpdate(mode, startupUpdate);
470
+ try {
471
+ await persistCoordinatorRuntimeInputReady();
472
+ } catch (error) {
473
+ logger.warn("Failed to persist coordinator runtime input readiness", { error: String(error) });
474
+ throw error;
475
+ }
354
476
 
355
477
  mode.renderInitialMessages(undefined, { preserveExistingChat: true });
356
478
 
@@ -367,6 +489,16 @@ async function runInteractiveMode(
367
489
  }
368
490
  }
369
491
 
492
+ const hasStartupInput = initialMessage !== undefined || initialMessages.length > 0;
493
+ if (!hasStartupInput && resumeAction === "continue-tail") {
494
+ try {
495
+ await session.continuePersistedHistory();
496
+ } catch (error: unknown) {
497
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
498
+ mode.showError(errorMessage);
499
+ }
500
+ }
501
+
370
502
  if (initialMessage !== undefined) {
371
503
  try {
372
504
  await session.prompt(initialMessage, { images: initialImages });
@@ -454,6 +586,9 @@ export async function createSessionManager(
454
586
  cwd: string,
455
587
  activeSettings: Settings = settings,
456
588
  ): Promise<SessionManager | undefined> {
589
+ if (parsed.resume === true) {
590
+ return undefined;
591
+ }
457
592
  if (parsed.fork) {
458
593
  if (parsed.noSession) {
459
594
  throw new Error("--fork requires session persistence");
@@ -747,13 +882,38 @@ export interface RlmPreset {
747
882
  onSessionCreated?: (session: AgentSession) => void | Promise<void>;
748
883
  }
749
884
 
750
- interface RunRootCommandDependencies {
885
+ type RunRpcMode = (
886
+ session: AgentSession,
887
+ setToolUIContext?: CreateAgentSessionResult["setToolUIContext"],
888
+ options?: { listen?: string },
889
+ ) => Promise<void>;
890
+ type RunBridgeMode = (
891
+ session: AgentSession,
892
+ setToolUIContext?: CreateAgentSessionResult["setToolUIContext"],
893
+ ) => Promise<void>;
894
+ type RunPrintMode = (session: AgentSession, options: PrintModeOptions) => Promise<void>;
895
+
896
+ export interface RunRootCommandDependencies {
751
897
  createAgentSession?: typeof createAgentSession;
752
898
  discoverAuthStorage?: typeof discoverAuthStorage;
753
899
  runAcpMode?: (createSession: AcpSessionFactory) => Promise<void>;
754
900
  settings?: Settings;
755
901
  rlmPreset?: RlmPreset;
756
902
  suppressProcessExit?: boolean;
903
+ startupUpdate?: { check: () => Promise<string | undefined> };
904
+ initTheme?: typeof initTheme;
905
+ readPipedInput?: typeof readPipedInput;
906
+ runStartupCredentialAutoImportIfNeeded?: typeof runStartupCredentialAutoImportIfNeeded;
907
+ getChangelogForDisplay?: typeof getChangelogForDisplay;
908
+ runRpcMode?: RunRpcMode;
909
+ runBridgeMode?: RunBridgeMode;
910
+ createInteractiveMode?: CreateInteractiveMode;
911
+ runPrintMode?: RunPrintMode;
912
+ isResumePickerTerminal?: ResumePickerTerminalCheck;
913
+ listForResumePickerReadOnly?: ListForResumePickerReadOnly;
914
+ selectResumeSession?: SelectResumeSession;
915
+ openExistingSessionStrict?: OpenExistingSessionStrict;
916
+ initializeSettings?: typeof Settings.init;
757
917
  }
758
918
 
759
919
  export async function runRootCommand(
@@ -761,14 +921,73 @@ export async function runRootCommand(
761
921
  rawArgs: string[],
762
922
  deps: RunRootCommandDependencies = {},
763
923
  ): Promise<void> {
764
- logger.startTiming();
924
+ const parsedArgs = parsed;
925
+ let initialThemeInitialized = false;
926
+ let autoChdirApplied = false;
927
+ let bareResumeSessionManager: SessionManager | undefined;
928
+ let bareResumeAction: "continue-tail" | "open-idle" | undefined;
929
+
930
+ if (isBareResume(parsedArgs)) {
931
+ if (hasBareResumeConflict(parsedArgs)) {
932
+ process.stderr.write(`${BARE_RESUME_CONFLICT_ERROR}\n`);
933
+ if (!deps.suppressProcessExit) process.exitCode = 1;
934
+ return;
935
+ }
936
+ if (!isNormalLocalInteractiveRoute(parsedArgs) || !(deps.isResumePickerTerminal ?? hasResumePickerTerminal)()) {
937
+ process.stderr.write(`${BARE_RESUME_INTERACTIVE_ERROR}\n`);
938
+ if (!deps.suppressProcessExit) process.exitCode = 1;
939
+ return;
940
+ }
765
941
 
766
- // Initialize theme early with defaults (CLI commands need symbols)
767
- // Will be re-initialized with user preferences later
768
- await logger.time("initTheme:initial", initTheme);
942
+ logger.startTiming();
943
+ await logger.time("initTheme:initial", deps.initTheme ?? initTheme);
944
+ initialThemeInitialized = true;
769
945
 
770
- const parsedArgs = parsed;
771
- await logger.time("maybeAutoChdir", maybeAutoChdir, parsedArgs);
946
+ await logger.time("maybeAutoChdir", maybeAutoChdir, parsedArgs);
947
+ autoChdirApplied = true;
948
+ const resumeCwd = getProjectDir();
949
+ const sessions = await (deps.listForResumePickerReadOnly ?? SessionManager.listForResumePickerReadOnly)(
950
+ resumeCwd,
951
+ parsedArgs.sessionDir,
952
+ );
953
+ if (sessions.length === 0) {
954
+ process.stdout.write(`${chalk.dim("No sessions found")}\n`);
955
+ return;
956
+ }
957
+ const selection = await (deps.selectResumeSession ?? selectSession)(sessions);
958
+ if (selection.kind === "cancelled") {
959
+ return;
960
+ }
961
+ let opened: StrictSessionOpenResult;
962
+ try {
963
+ opened = await (deps.openExistingSessionStrict ?? SessionManager.openExistingStrict)(
964
+ selection.identity,
965
+ parsedArgs.sessionDir,
966
+ );
967
+ } catch {
968
+ process.stderr.write(`${BARE_RESUME_OPEN_ERROR}\n`);
969
+ if (!deps.suppressProcessExit) process.exitCode = 1;
970
+ return;
971
+ }
972
+ if (opened.kind === "error") {
973
+ process.stderr.write(`${BARE_RESUME_OPEN_ERROR}\n`);
974
+ if (!deps.suppressProcessExit) process.exitCode = 1;
975
+ return;
976
+ }
977
+ bareResumeSessionManager = opened.manager;
978
+ bareResumeAction = selection.action;
979
+ }
980
+
981
+ if (!initialThemeInitialized) {
982
+ logger.startTiming();
983
+ // Initialize theme early with defaults (CLI commands need symbols).
984
+ // It is re-initialized with user preferences later.
985
+ await logger.time("initTheme:initial", deps.initTheme ?? initTheme);
986
+ }
987
+
988
+ if (!autoChdirApplied) {
989
+ await logger.time("maybeAutoChdir", maybeAutoChdir, parsedArgs);
990
+ }
772
991
 
773
992
  const notifs: (InteractiveModeNotify | null)[] = [];
774
993
 
@@ -821,7 +1040,8 @@ export async function runRootCommand(
821
1040
  }
822
1041
 
823
1042
  const cwd = getProjectDir();
824
- const settingsInstance = deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd }));
1043
+ const settingsInstance =
1044
+ deps.settings ?? (await logger.time("settings:init", deps.initializeSettings ?? Settings.init, { cwd }));
825
1045
  if (
826
1046
  parsedArgs.mode === "rpc" ||
827
1047
  parsedArgs.mode === "rpc-ui" ||
@@ -844,7 +1064,7 @@ export async function runRootCommand(
844
1064
  Bun.env.PI_NO_TITLE = "1";
845
1065
  }
846
1066
  const { pipedInput, fileText, fileImages } = await logger.time("prepareInitialMessage", async () => {
847
- const pipedInput = await readPipedInput();
1067
+ const pipedInput = await (deps.readPipedInput ?? readPipedInput)();
848
1068
  if (parsedArgs.fileArgs.length === 0) {
849
1069
  return { pipedInput, fileText: undefined, fileImages: undefined };
850
1070
  }
@@ -860,7 +1080,13 @@ export async function runRootCommand(
860
1080
  stdinContent: pipedInput,
861
1081
  });
862
1082
  const autoPrint = pipedInput !== undefined && !parsedArgs.print && parsedArgs.mode === undefined;
863
- const isInteractive = !parsedArgs.print && !autoPrint && parsedArgs.mode === undefined;
1083
+ const startupUpdateRoute = classifyStartupUpdateRoute(parsedArgs, autoPrint);
1084
+ const startupUpdate = new StartupUpdateOrchestrator(
1085
+ startupUpdateRoute,
1086
+ () => settingsInstance.get("startup.checkUpdate"),
1087
+ deps.startupUpdate?.check ?? (() => checkForNewVersion(VERSION)),
1088
+ );
1089
+ const isInteractive = startupUpdateRoute === "interactive";
864
1090
  const mode = parsedArgs.mode || "text";
865
1091
 
866
1092
  // Initialize discovery system with settings for provider persistence
@@ -882,7 +1108,7 @@ export async function runRootCommand(
882
1108
 
883
1109
  await logger.time(
884
1110
  "initTheme:final",
885
- initTheme,
1111
+ deps.initTheme ?? initTheme,
886
1112
  isInteractive,
887
1113
  settingsInstance.get("symbolPreset"),
888
1114
  settingsInstance.get("colorBlindMode"),
@@ -891,11 +1117,15 @@ export async function runRootCommand(
891
1117
  );
892
1118
 
893
1119
  const credentialAutoImportNotice = isInteractive
894
- ? await logger.time("credentialAutoImport", runStartupCredentialAutoImportIfNeeded, {
895
- authStorage,
896
- modelRegistry,
897
- agentDir: settingsInstance.getAgentDir(),
898
- })
1120
+ ? await logger.time(
1121
+ "credentialAutoImport",
1122
+ deps.runStartupCredentialAutoImportIfNeeded ?? runStartupCredentialAutoImportIfNeeded,
1123
+ {
1124
+ authStorage,
1125
+ modelRegistry,
1126
+ agentDir: settingsInstance.getAgentDir(),
1127
+ },
1128
+ )
899
1129
  : undefined;
900
1130
 
901
1131
  let scopedModels: ScopedModel[] = [];
@@ -913,29 +1143,11 @@ export async function runRootCommand(
913
1143
  );
914
1144
  }
915
1145
 
916
- // Create session manager based on CLI flags
917
- let sessionManager = await logger.time(
918
- "createSessionManager",
919
- createSessionManager,
920
- parsedArgs,
921
- cwd,
922
- settingsInstance,
923
- );
924
-
925
- // Handle --resume (no value): show session picker
926
- if (parsedArgs.resume === true && !parsedArgs.fork) {
927
- const sessions = await logger.time("SessionManager.list", SessionManager.list, cwd, parsedArgs.sessionDir);
928
- if (sessions.length === 0) {
929
- process.stdout.write(`${chalk.dim("No sessions found")}\n`);
930
- return;
931
- }
932
- const selectedPath = await logger.time("selectSession", selectSession, sessions);
933
- if (!selectedPath) {
934
- process.stdout.write(`${chalk.dim("No session selected")}\n`);
935
- return;
936
- }
937
- sessionManager = await SessionManager.open(selectedPath);
938
- }
1146
+ // Create session manager based on CLI flags. A bare resume was strictly opened
1147
+ // before startup discovery, so it never reaches create-or-open behavior here.
1148
+ const sessionManager =
1149
+ bareResumeSessionManager ??
1150
+ (await logger.time("createSessionManager", createSessionManager, parsedArgs, cwd, settingsInstance));
939
1151
 
940
1152
  // Restore the resumed session's working directory so the HUD branch, the
941
1153
  // project path, and the agent's tools all match where the session was
@@ -1117,8 +1329,9 @@ export async function runRootCommand(
1117
1329
 
1118
1330
  if (mode === "rpc" || mode === "rpc-ui") {
1119
1331
  const { RpcListenRefusedError, runRpcMode } = await import("./modes/rpc/rpc-mode");
1332
+ const runRpc = deps.runRpcMode ?? runRpcMode;
1120
1333
  try {
1121
- await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, {
1334
+ await runRpc(session, mode === "rpc-ui" ? setToolUIContext : undefined, {
1122
1335
  listen: parsedArgs.rpcListen,
1123
1336
  });
1124
1337
  } catch (error) {
@@ -1131,11 +1344,15 @@ export async function runRootCommand(
1131
1344
  process.exit(1);
1132
1345
  }
1133
1346
  } else if (mode === "bridge") {
1134
- const { runBridgeMode } = await import("./modes/bridge/bridge-mode");
1135
- await runBridgeMode(session, setToolUIContext);
1347
+ const runBridge = deps.runBridgeMode ?? (await import("./modes/bridge/bridge-mode")).runBridgeMode;
1348
+ await runBridge(session, setToolUIContext);
1136
1349
  } else if (isInteractive) {
1137
- const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
1138
- const changelogMarkdown = await logger.time("main:getChangelogForDisplay", getChangelogForDisplay, parsedArgs);
1350
+ startupUpdate.startBeforeInteractiveInitialization();
1351
+ const changelogMarkdown = await logger.time(
1352
+ "main:getChangelogForDisplay",
1353
+ deps.getChangelogForDisplay ?? getChangelogForDisplay,
1354
+ parsedArgs,
1355
+ );
1139
1356
 
1140
1357
  const scopedModelsForDisplay = sessionOptions.scopedModels ?? scopedModels;
1141
1358
  if (scopedModelsForDisplay.length > 0) {
@@ -1161,7 +1378,7 @@ export async function runRootCommand(
1161
1378
  VERSION,
1162
1379
  changelogMarkdown,
1163
1380
  notifs,
1164
- versionCheckPromise,
1381
+ startupUpdate,
1165
1382
  parsedArgs.messages,
1166
1383
  setToolUIContext,
1167
1384
  lspServers,
@@ -1169,10 +1386,12 @@ export async function runRootCommand(
1169
1386
  eventBus,
1170
1387
  initialMessage,
1171
1388
  initialImages,
1389
+ deps.createInteractiveMode,
1390
+ bareResumeAction,
1172
1391
  );
1173
1392
  } else {
1174
- const { runPrintMode } = await import("./modes/print-mode");
1175
- await runPrintMode(session, {
1393
+ const runPrint = deps.runPrintMode ?? (await import("./modes/print-mode")).runPrintMode;
1394
+ await runPrint(session, {
1176
1395
  mode,
1177
1396
  messages: parsedArgs.messages,
1178
1397
  initialMessage,
@@ -1182,18 +1401,20 @@ export async function runRootCommand(
1182
1401
  if ($env.PI_TIMING) {
1183
1402
  logger.printTimings();
1184
1403
  }
1404
+ stopThemeWatcher();
1185
1405
  if (!deps.suppressProcessExit) {
1186
- await session.dispose();
1187
- stopThemeWatcher();
1188
- await postmortem.quit(0);
1189
- } else {
1190
- stopThemeWatcher();
1406
+ const exitCode = typeof process.exitCode === "number" ? process.exitCode : 0;
1407
+ await postmortem.quit(exitCode);
1191
1408
  }
1192
1409
  }
1193
1410
  }
1194
1411
  }
1195
1412
 
1196
1413
  export async function main(args: string[]): Promise<void> {
1414
+ if (isTmuxOwnerIsolationCliArgv(args)) {
1415
+ await runTmuxOwnerIsolationCliFromStdin();
1416
+ return;
1417
+ }
1197
1418
  const { runCli } = await import("./cli");
1198
1419
  await runCli(args.length === 0 ? ["launch"] : args);
1199
1420
  }