@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
@@ -0,0 +1,129 @@
1
+ import { logger } from "@sayknow-cli/utils";
2
+
3
+ /**
4
+ * Idle reaper for coordinator-managed SKC worker sessions.
5
+ *
6
+ * Every `skc_delegate_*` call that omits `session_id` starts a fresh tmux worker
7
+ * session. Nothing in the coordinator ever tore those down, so completed/crashed
8
+ * sessions accumulated (RAM + worktrees) until something killed them by hand.
9
+ *
10
+ * This is the automatic backstop (defense-in-depth): a periodic sweep force-closes
11
+ * sessions that are (a) ephemeral — coordinator delegate-created, never a user's
12
+ * registered resident session, (b) not mid-turn, and (c) idle past a TTL.
13
+ *
14
+ * The controller is pure + fully injectable (clock / list / reap side-effects) so
15
+ * it is unit-testable without tmux, the filesystem, or wall-clock waits. The
16
+ * scheduling mirrors the proven resource-gc controller: a recursive setTimeout
17
+ * with a generation guard so a stop()+start() can never leak a duplicate timer.
18
+ */
19
+
20
+ export const DEFAULT_SESSION_IDLE_TTL_MS = 30 * 60_000; // 30 min idle → reap
21
+ export const DEFAULT_SESSION_SWEEP_INTERVAL_MS = 5 * 60_000; // sweep every 5 min
22
+ export const MIN_SESSION_IDLE_TTL_MS = 60_000; // never reap a <1min-idle session
23
+ export const MIN_SESSION_SWEEP_INTERVAL_MS = 30_000;
24
+
25
+ /** Minimal projection of a coordinator session the reaper needs to decide. */
26
+ export interface ReapableSession {
27
+ sessionId: string;
28
+ /** True only for coordinator delegate-created sessions. User-registered resident sessions are false and never reaped. */
29
+ ephemeral: boolean;
30
+ /** Epoch ms of last observed activity (turn update / session-state write / session creation). */
31
+ lastActivityMs: number;
32
+ /** True while a turn is active — never reap mid-turn. */
33
+ hasActiveTurn: boolean;
34
+ }
35
+
36
+ export interface SessionReaperPolicy {
37
+ idleTtlMs: number;
38
+ sweepIntervalMs: number;
39
+ }
40
+
41
+ /**
42
+ * Pure selection: which sessions are safe to reap at `now`.
43
+ * A session is reapable iff ephemeral AND not mid-turn AND idle ≥ (clamped) TTL.
44
+ */
45
+ export function selectReapableSessions(
46
+ sessions: readonly ReapableSession[],
47
+ now: number,
48
+ idleTtlMs: number,
49
+ ): ReapableSession[] {
50
+ const ttl = Math.max(MIN_SESSION_IDLE_TTL_MS, idleTtlMs);
51
+ return sessions.filter(s => s.ephemeral && !s.hasActiveTurn && now - s.lastActivityMs >= ttl);
52
+ }
53
+
54
+ /** Injectable side-effects so the controller runs in tests without tmux/fs/real time. */
55
+ export interface SessionReaperDeps {
56
+ listSessions: () => Promise<ReapableSession[]>;
57
+ reapSession: (sessionId: string) => Promise<void>;
58
+ now: () => number;
59
+ }
60
+
61
+ export interface SessionReaper {
62
+ /** Run one sweep; returns the number of sessions successfully reaped. */
63
+ sweepOnce: () => Promise<number>;
64
+ start: () => void;
65
+ stop: () => void;
66
+ readonly running: boolean;
67
+ }
68
+
69
+ export function createSessionReaper(deps: SessionReaperDeps, policy: SessionReaperPolicy): SessionReaper {
70
+ const idleTtlMs = Math.max(MIN_SESSION_IDLE_TTL_MS, policy.idleTtlMs);
71
+ const sweepIntervalMs = Math.max(MIN_SESSION_SWEEP_INTERVAL_MS, policy.sweepIntervalMs);
72
+ let timer: ReturnType<typeof setTimeout> | null = null;
73
+ let generation = 0;
74
+ let inProgress = false;
75
+
76
+ async function sweepOnce(): Promise<number> {
77
+ if (inProgress) return 0; // never overlap sweeps
78
+ inProgress = true;
79
+ try {
80
+ const sessions = await deps.listSessions();
81
+ const targets = selectReapableSessions(sessions, deps.now(), idleTtlMs);
82
+ let reaped = 0;
83
+ for (const session of targets) {
84
+ try {
85
+ await deps.reapSession(session.sessionId);
86
+ reaped += 1;
87
+ } catch (err) {
88
+ // One wedged session must not abort the rest of the sweep.
89
+ logger.warn(
90
+ `session-reaper: failed to reap ${session.sessionId}: ${err instanceof Error ? err.message : String(err)}`,
91
+ );
92
+ }
93
+ }
94
+ return reaped;
95
+ } finally {
96
+ inProgress = false;
97
+ }
98
+ }
99
+
100
+ function schedule(gen: number): void {
101
+ timer = setTimeout(() => {
102
+ if (gen !== generation) return; // stale timer from a prior start()
103
+ void sweepOnce().finally(() => {
104
+ if (gen === generation) schedule(gen);
105
+ });
106
+ }, sweepIntervalMs);
107
+ // The reaper must never keep the coordinator process alive by itself.
108
+ (timer as { unref?: () => void }).unref?.();
109
+ }
110
+
111
+ return {
112
+ sweepOnce,
113
+ start(): void {
114
+ if (timer) return; // already running
115
+ generation += 1;
116
+ schedule(generation);
117
+ },
118
+ stop(): void {
119
+ generation += 1; // invalidate any in-flight scheduled tick
120
+ if (timer) {
121
+ clearTimeout(timer);
122
+ timer = null;
123
+ }
124
+ },
125
+ get running(): boolean {
126
+ return timer !== null;
127
+ },
128
+ };
129
+ }
@@ -47,6 +47,15 @@ export interface DaemonOperationOptions {
47
47
  spawnIfStopped?: boolean;
48
48
  }
49
49
 
50
+ export interface DaemonRecovery {
51
+ /** Machine-readable recovery category for automation branching. */
52
+ reason: "ownership_mismatch";
53
+ /** One-line human summary of why the operation could not proceed. */
54
+ summary: string;
55
+ /** Ordered, copy-pasteable remediation steps. */
56
+ steps: string[];
57
+ }
58
+
50
59
  export interface DaemonOperationResult {
51
60
  kind: DaemonKind;
52
61
  action: Exclude<DaemonAction, "list">;
@@ -55,6 +64,8 @@ export interface DaemonOperationResult {
55
64
  after?: DaemonStatus;
56
65
  warnings: string[];
57
66
  message: string;
67
+ /** Present when the operation was refused with an actionable recovery path (e.g. ownership mismatch). */
68
+ recovery?: DaemonRecovery;
58
69
  }
59
70
 
60
71
  export interface BuiltInDaemonController {
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Shared operator contract for the `skc daemon` command surface.
3
+ *
4
+ * One source of truth for the beginner-safe operational vocabulary so the
5
+ * guided human surface and the machine-readable JSON never drift:
6
+ * - the terse action verbs plus operator aliases (e.g. `restart` -> `reload`),
7
+ * - process exit codes,
8
+ * - concise vs. `--verbose` human rendering of statuses and results, and
9
+ * - actionable recovery guidance for a token/chat ownership mismatch.
10
+ *
11
+ * The full roots/debug payload lives behind `--verbose` (human) or `--json`
12
+ * (automation); the default human output stays a concise per-daemon result.
13
+ */
14
+
15
+ import type { DaemonAction, DaemonOperationResult, DaemonRecovery, DaemonStatus } from "./control-types";
16
+
17
+ /** Canonical daemon actions accepted as the leading verb. */
18
+ export const DAEMON_CANONICAL_ACTIONS: readonly DaemonAction[] = ["list", "status", "stop", "reload"];
19
+
20
+ /**
21
+ * Operator-friendly aliases resolved to a canonical action. `restart` is the
22
+ * routine "reload if running, otherwise start" verb; it maps onto `reload`,
23
+ * whose default already spawns a fresh owner when none is running.
24
+ */
25
+ export const DAEMON_ACTION_ALIASES: Readonly<Record<string, DaemonAction>> = {
26
+ restart: "reload",
27
+ };
28
+
29
+ /** Every token accepted in the leading action position (canonical + aliases). */
30
+ export const DAEMON_ACTION_TOKENS: readonly string[] = [
31
+ ...DAEMON_CANONICAL_ACTIONS,
32
+ ...Object.keys(DAEMON_ACTION_ALIASES),
33
+ ];
34
+
35
+ /** Resolve a leading verb (canonical or alias) to its canonical action. */
36
+ export function resolveDaemonAction(token: string | undefined): DaemonAction | undefined {
37
+ if (token === undefined) return undefined;
38
+ if ((DAEMON_CANONICAL_ACTIONS as readonly string[]).includes(token)) return token as DaemonAction;
39
+ return DAEMON_ACTION_ALIASES[token];
40
+ }
41
+
42
+ /** Exit codes for `skc daemon`. Backward compatible: success 0, any failure 1. */
43
+ export const DAEMON_EXIT = { ok: 0, failure: 1 } as const;
44
+
45
+ /** Human-facing headline when a spawn/reload is refused by a live foreign identity. */
46
+ export const OWNERSHIP_MISMATCH_MESSAGE =
47
+ "refused: a live Telegram daemon with a different bot token or chat already owns this workspace";
48
+
49
+ /** Structured, actionable recovery for a token/chat ownership mismatch. */
50
+ export function ownershipMismatchRecovery(): DaemonRecovery {
51
+ return {
52
+ reason: "ownership_mismatch",
53
+ summary: "A live Telegram daemon owned by a different bot token or chat already holds this workspace.",
54
+ steps: [
55
+ "Confirm the intended bot token and chat with `skc notify` (or edit notifications.telegram in your config).",
56
+ "If the running daemon is stale or unwanted, stop it with `skc daemon stop --force`, then rerun `skc daemon restart`.",
57
+ "If it is the correct daemon, no action is needed — this workspace attaches automatically once the identities match.",
58
+ ],
59
+ };
60
+ }
61
+
62
+ function timestamp(ms: number | undefined): string | undefined {
63
+ if (ms === undefined) return undefined;
64
+ const date = new Date(ms);
65
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
66
+ }
67
+
68
+ /**
69
+ * Render one daemon status. Concise by default (single head line + any
70
+ * actionable warning); `--verbose` adds runtime detail and the full roots list.
71
+ */
72
+ export function formatDaemonStatus(status: DaemonStatus, opts: { verbose?: boolean } = {}): string {
73
+ const lines: string[] = [];
74
+ if (!status.configured) {
75
+ lines.push(`${status.kind}: not configured`);
76
+ if (status.runtime.warning) lines.push(` warning: ${status.runtime.warning}`);
77
+ return lines.join("\n");
78
+ }
79
+
80
+ const meta: string[] = [];
81
+ if (status.pid !== undefined) meta.push(`pid ${status.pid}`);
82
+ if (status.ownerId) meta.push(`owner ${status.ownerId}`);
83
+ const rootCount = status.rootCount ?? status.roots?.length ?? 0;
84
+ if (rootCount > 0) meta.push(`${rootCount} root${rootCount === 1 ? "" : "s"}`);
85
+
86
+ let head = `${status.kind}: ${status.health}`;
87
+ if (meta.length > 0) head += ` (${meta.join(", ")})`;
88
+ if (status.detail) head += ` — ${status.detail}`;
89
+ lines.push(head);
90
+ if (status.runtime.warning) lines.push(` warning: ${status.runtime.warning}`);
91
+
92
+ if (opts.verbose) {
93
+ lines.push(` runtime: ${status.runtime.mode} (${status.runtime.execPath})`);
94
+ const started = timestamp(status.startedAt);
95
+ if (started) lines.push(` started: ${started}`);
96
+ const heartbeat = timestamp(status.heartbeatAt);
97
+ if (heartbeat) lines.push(` heartbeat: ${heartbeat}`);
98
+ const roots = status.roots ?? [];
99
+ lines.push(` roots: ${rootCount}`);
100
+ for (const root of roots) lines.push(` - ${root}`);
101
+ }
102
+
103
+ return lines.join("\n");
104
+ }
105
+
106
+ /**
107
+ * Render one operation result. Keeps the stable `<kind> <action>: ok|failed —
108
+ * <message>` head line, then any warnings, then actionable recovery steps.
109
+ */
110
+ export function formatDaemonResult(result: DaemonOperationResult): string {
111
+ const lines = [`${result.kind} ${result.action}: ${result.ok ? "ok" : "failed"} — ${result.message}`];
112
+ for (const warning of result.warnings) lines.push(` warning: ${warning}`);
113
+ if (result.recovery) {
114
+ lines.push(` ${result.recovery.summary}`);
115
+ lines.push(" to recover:");
116
+ result.recovery.steps.forEach((step, i) => {
117
+ lines.push(` ${i + 1}. ${step}`);
118
+ });
119
+ }
120
+ return lines.join("\n");
121
+ }
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Model definitions for Grok CLI's API.
3
3
  */
4
+ import { Effort } from '@sayknow-cli/ai/model-thinking';
4
5
 
5
6
  // ─── Cost constants ($/M tokens) ──────────────────────────────────────────────
6
7
 
7
8
  const COST_BUILD = { input: 1, output: 2, cacheRead: 0.2, cacheWrite: 0.2 };
8
9
  const COST_COMPOSER_FAST = { input: 3, output: 15, cacheRead: 0.5, cacheWrite: 0 };
9
10
  const COST_43 = { input: 1.25, output: 2.5, cacheRead: 0.2, cacheWrite: 0 };
11
+ const COST_45 = { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 };
10
12
  const COST_420 = { input: 2, output: 6, cacheRead: 0.2, cacheWrite: 0 };
11
13
 
12
14
  // ─── Model type ───────────────────────────────────────────────────────────────
@@ -24,6 +26,8 @@ export interface GrokCliModelConfig {
24
26
  };
25
27
  contextWindow: number;
26
28
  maxTokens: number;
29
+ /** Highest reasoning effort supported by this model. */
30
+ maxReasoningEffort?: Effort;
27
31
  /** Models that don't support reasoning.effort get a thinkingLevelMap. */
28
32
  thinkingLevelMap?: Record<string, string | null>;
29
33
  }
@@ -69,6 +73,18 @@ const FALLBACK_MODELS: GrokCliModelConfig[] = [
69
73
  contextWindow: 1_000_000,
70
74
  maxTokens: 30_000,
71
75
  },
76
+ {
77
+ // Official metadata and pricing: https://docs.x.ai/developers/models/grok-4.5
78
+ id: 'grok-4.5',
79
+ name: 'Grok 4.5',
80
+ reasoning: true,
81
+ input: ['text', 'image'],
82
+ cost: COST_45,
83
+ contextWindow: 500_000,
84
+ maxTokens: 30_000,
85
+ // https://docs.x.ai/developers/model-capabilities/text/reasoning caps Grok 4.5 at high.
86
+ maxReasoningEffort: Effort.High,
87
+ },
72
88
  {
73
89
  id: 'grok-4.20-0309-reasoning',
74
90
  name: 'Grok 4.20 Reasoning',
@@ -106,15 +122,37 @@ const FALLBACK_MODELS: GrokCliModelConfig[] = [
106
122
  },
107
123
  ];
108
124
 
109
- const EFFORT_CAPABLE_PREFIXES = ['grok-3-mini', 'grok-4.20-multi-agent', 'grok-4.3'];
125
+ // Official aliases: https://docs.x.ai/developers/models/grok-4.5
126
+ const MODEL_ALIASES: Readonly<Record<string, string>> = {
127
+ 'grok-4.5-latest': 'grok-4.5',
128
+ 'grok-build-latest': 'grok-4.5',
129
+ };
110
130
 
111
- export function supportsReasoningEffort(modelId: string): boolean {
131
+ function getModelName(modelId: string): string {
112
132
  const parts = modelId.split('/');
113
- const name = (parts.at(-1) ?? modelId).toLowerCase();
133
+ return (parts.at(-1) ?? modelId).toLowerCase();
134
+ }
135
+
136
+ function getCanonicalModelName(modelId: string): string {
137
+ const name = getModelName(modelId);
138
+ return MODEL_ALIASES[name] ?? name;
139
+ }
140
+
141
+ export function getMaxReasoningEffort(modelId: string): Effort | undefined {
142
+ const name = getCanonicalModelName(modelId);
143
+ return FALLBACK_MODELS.find(
144
+ (model) => name === model.id.toLowerCase() || name.startsWith(`${model.id.toLowerCase()}-`),
145
+ )?.maxReasoningEffort;
146
+ }
147
+
148
+ const EFFORT_CAPABLE_PREFIXES = ['grok-3-mini', 'grok-4.20-multi-agent', 'grok-4.3', 'grok-4.5'];
149
+
150
+ export function supportsReasoningEffort(modelId: string): boolean {
151
+ const name = getCanonicalModelName(modelId);
114
152
  if (!EFFORT_CAPABLE_PREFIXES.some((prefix) => name.startsWith(prefix))) {
115
153
  return false;
116
154
  }
117
- const model = resolveModels().find((entry) => entry.id.toLowerCase() === name);
155
+ const model = resolveModels().find((entry) => getCanonicalModelName(entry.id) === name);
118
156
  if (model) {
119
157
  if (!model.reasoning) return false;
120
158
  if (!model.thinkingLevelMap) return true;
@@ -139,17 +177,19 @@ export function resolveModels(): GrokCliModelConfig[] {
139
177
  .filter(Boolean);
140
178
  if (env.length === 0) return FALLBACK_MODELS;
141
179
 
142
- const byId = new Map(FALLBACK_MODELS.map((m) => [m.id, m]));
143
- return env.map(
144
- (id) =>
145
- byId.get(id) ?? {
146
- id,
147
- name: id,
148
- reasoning: true,
149
- input: ['text'] as ('text' | 'image')[],
150
- cost: COST_BUILD,
151
- contextWindow: 1_000_000,
152
- maxTokens: 30_000,
153
- },
154
- );
180
+ const byId = new Map(FALLBACK_MODELS.map((model) => [model.id.toLowerCase(), model]));
181
+ return env.map((id) => {
182
+ const model = byId.get(getCanonicalModelName(id));
183
+ if (model) return model.id === id ? model : { ...model, id };
184
+
185
+ return {
186
+ id,
187
+ name: id,
188
+ reasoning: true,
189
+ input: ['text'] as ('text' | 'image')[],
190
+ cost: COST_BUILD,
191
+ contextWindow: 1_000_000,
192
+ maxTokens: 30_000,
193
+ };
194
+ });
155
195
  }
@@ -22,7 +22,8 @@
22
22
  import { existsSync, readFileSync, realpathSync } from 'node:fs';
23
23
  import { extname, isAbsolute, resolve, sep } from 'node:path';
24
24
  import { fileURLToPath } from 'node:url';
25
- import { supportsReasoningEffort } from '../models/catalog.js';
25
+ import { Effort, THINKING_EFFORTS } from '@sayknow-cli/ai/model-thinking';
26
+ import { getMaxReasoningEffort, supportsReasoningEffort } from '../models/catalog.js';
26
27
 
27
28
  // ─── Content text extraction ─────────────────────────────────────────────────
28
29
 
@@ -266,6 +267,18 @@ function stripUnsupportedTopLevelFields(next: Record<string, unknown>): void {
266
267
  delete next.truncation;
267
268
  }
268
269
 
270
+ function normalizeReasoningEffort(modelId: string, value: unknown): unknown {
271
+ const effort = value === Effort.Minimal ? Effort.Low : value;
272
+ if (typeof effort !== 'string') return effort;
273
+
274
+ const maxEffort = getMaxReasoningEffort(modelId);
275
+ if (maxEffort === undefined) return effort;
276
+
277
+ const effortIndex = THINKING_EFFORTS.indexOf(effort as Effort);
278
+ const maxEffortIndex = THINKING_EFFORTS.indexOf(maxEffort);
279
+ return effortIndex > maxEffortIndex ? maxEffort : effort;
280
+ }
281
+
269
282
  // ─── Main sanitization ────────────────────────────────────────────────────────
270
283
 
271
284
  /**
@@ -334,7 +347,7 @@ export function sanitizePayload(
334
347
  if (supportsReasoningEffort(modelId)) {
335
348
  const reasoning = next.reasoning as Record<string, unknown> | undefined;
336
349
  if (reasoning) {
337
- const effort = reasoning.effort === 'minimal' ? 'low' : reasoning.effort;
350
+ const effort = normalizeReasoningEffort(modelId, reasoning.effort);
338
351
  next.reasoning = reasoning.summary !== undefined ? { effort } : { ...reasoning, effort };
339
352
  }
340
353
  } else {
@@ -36,7 +36,11 @@ export default function registerGrokCli(api: ExtensionAPI) {
36
36
  name: m.name,
37
37
  reasoning: m.reasoning,
38
38
  thinking: m.reasoning
39
- ? { minLevel: Effort.Low, maxLevel: Effort.XHigh, mode: 'effort' }
39
+ ? {
40
+ minLevel: Effort.Low,
41
+ maxLevel: m.maxReasoningEffort ?? Effort.XHigh,
42
+ mode: 'effort',
43
+ }
40
44
  : undefined,
41
45
  input: m.input,
42
46
  cost: m.cost,
@@ -484,7 +484,7 @@ export class ExtensionRunner {
484
484
  abort: () => this.#abortFn(),
485
485
  hasPendingMessages: () => this.#hasPendingMessagesFn(),
486
486
  shutdown: () => this.#shutdownHandler(),
487
- getSystemPrompt: () => this.#getSystemPromptFn(),
487
+ getSystemPrompt: () => [...this.#getSystemPromptFn()],
488
488
  hasQueuedMessages: () => this.#hasPendingMessagesFn(), // deprecated alias
489
489
  workflowGate: this.#getWorkflowGateFn(),
490
490
  };
@@ -273,11 +273,13 @@ export interface ExtensionUIContext {
273
273
  // ============================================================================
274
274
 
275
275
  export interface ContextUsage {
276
- /** Estimated context tokens, or null if unknown (e.g. right after compaction, before next LLM response). */
276
+ /** Context tokens, or null if unknown (e.g. right after compaction, before next LLM response). */
277
277
  tokens: number | null;
278
278
  contextWindow: number;
279
279
  /** Context usage as percentage of context window, or null if tokens is unknown. */
280
280
  percent: number | null;
281
+ /** Provenance: "provider_anchor" = anchored on provider-reported usage (+ heuristic trailing delta); "heuristic" = no provider anchor available, full estimate; "unknown" = post-compaction, no post-compaction provider response yet. */
282
+ source: "provider_anchor" | "heuristic" | "unknown";
281
283
  }
282
284
 
283
285
  export interface CompactOptions {