@gajae-code/coding-agent 0.9.5 → 0.10.0

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 (194) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/cli/classify-install-target.d.ts +0 -10
  3. package/dist/types/cli/daemon-cli.d.ts +2 -0
  4. package/dist/types/cli/initial-message.d.ts +1 -1
  5. package/dist/types/cli/malloc-env-guard.d.ts +65 -0
  6. package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
  7. package/dist/types/cli/notify-cli.d.ts +3 -1
  8. package/dist/types/cli/session-picker.d.ts +4 -3
  9. package/dist/types/cli/update-cli.d.ts +22 -3
  10. package/dist/types/cli.d.ts +4 -0
  11. package/dist/types/commands/daemon.d.ts +6 -2
  12. package/dist/types/commands/launch.d.ts +1 -0
  13. package/dist/types/commands/notify.d.ts +6 -0
  14. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  15. package/dist/types/commit/analysis/summary.d.ts +1 -1
  16. package/dist/types/commit/changelog/generate.d.ts +1 -1
  17. package/dist/types/commit/changelog/index.d.ts +2 -2
  18. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  19. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  20. package/dist/types/config/keybindings.d.ts +5 -0
  21. package/dist/types/config/model-profile-activation.d.ts +1 -0
  22. package/dist/types/config/settings-schema.d.ts +47 -7
  23. package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
  24. package/dist/types/coordinator-mcp/server.d.ts +67 -6
  25. package/dist/types/daemon/control-types.d.ts +10 -0
  26. package/dist/types/daemon/operator-contract.d.ts +47 -0
  27. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
  28. package/dist/types/discovery/helpers.d.ts +1 -1
  29. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  30. package/dist/types/gjc-runtime/launch-tmux.d.ts +26 -7
  31. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +34 -3
  32. package/dist/types/gjc-runtime/tmux-common.d.ts +6 -0
  33. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
  34. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +309 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +49 -17
  36. package/dist/types/internal-urls/local-protocol.d.ts +13 -9
  37. package/dist/types/main.d.ts +66 -2
  38. package/dist/types/modes/acp/acp-agent.d.ts +19 -2
  39. package/dist/types/modes/acp/acp-mode.d.ts +12 -0
  40. package/dist/types/modes/components/assistant-message.d.ts +1 -1
  41. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  42. package/dist/types/modes/components/gajae-pet-widget.d.ts +59 -0
  43. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  44. package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
  45. package/dist/types/modes/components/pet-selector.d.ts +11 -0
  46. package/dist/types/modes/components/session-selector.d.ts +22 -9
  47. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  48. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  49. package/dist/types/modes/components/user-message.d.ts +2 -1
  50. package/dist/types/modes/controllers/event-controller.d.ts +4 -0
  51. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  52. package/dist/types/modes/interactive-mode.d.ts +24 -2
  53. package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
  54. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
  55. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
  56. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
  57. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
  58. package/dist/types/modes/theme/defaults/index.d.ts +568 -568
  59. package/dist/types/modes/types.d.ts +28 -2
  60. package/dist/types/modes/utils/abort-message.d.ts +1 -1
  61. package/dist/types/modes/utils/irc-message.d.ts +38 -0
  62. package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
  63. package/dist/types/notifications/config.d.ts +34 -0
  64. package/dist/types/notifications/daemon-paths.d.ts +7 -0
  65. package/dist/types/notifications/index.d.ts +27 -0
  66. package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
  67. package/dist/types/notifications/notification-service.d.ts +144 -0
  68. package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
  69. package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
  70. package/dist/types/notifications/telegram-daemon.d.ts +130 -3
  71. package/dist/types/notifications/telegram-reference.d.ts +19 -2
  72. package/dist/types/notifications/topic-registry.d.ts +24 -4
  73. package/dist/types/registry/agent-registry.d.ts +2 -0
  74. package/dist/types/rlm/index.d.ts +1 -1
  75. package/dist/types/sdk.d.ts +2 -0
  76. package/dist/types/secrets/obfuscator.d.ts +1 -1
  77. package/dist/types/session/agent-session.d.ts +26 -2
  78. package/dist/types/session/cache-economics.d.ts +79 -0
  79. package/dist/types/session/provider-safety-stop.d.ts +5 -0
  80. package/dist/types/session/session-manager.d.ts +124 -4
  81. package/dist/types/session/session-storage.d.ts +155 -13
  82. package/dist/types/setup/credential-auto-import.d.ts +2 -2
  83. package/dist/types/task/executor.d.ts +3 -0
  84. package/dist/types/task/receipt.d.ts +3 -1
  85. package/dist/types/task/types.d.ts +8 -0
  86. package/dist/types/tools/ask-answer-registry.d.ts +5 -0
  87. package/dist/types/tools/ask.d.ts +25 -1
  88. package/dist/types/tools/bash.d.ts +1 -1
  89. package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
  90. package/dist/types/tools/index.d.ts +58 -6
  91. package/dist/types/tools/telegram-send.d.ts +7 -0
  92. package/dist/types/utils/external-editor.d.ts +2 -0
  93. package/package.json +9 -9
  94. package/scripts/compile-args.ts +8 -2
  95. package/scripts/g004-tmux-smoke.ts +196 -36
  96. package/src/cli/daemon-cli.ts +19 -29
  97. package/src/cli/malloc-env-guard.test.ts +104 -0
  98. package/src/cli/malloc-env-guard.ts +149 -0
  99. package/src/cli/notify-cli.ts +78 -14
  100. package/src/cli/session-picker.ts +23 -40
  101. package/src/cli/update-cli.ts +56 -37
  102. package/src/cli.ts +50 -10
  103. package/src/commands/daemon.ts +19 -5
  104. package/src/commands/harness.ts +515 -47
  105. package/src/commands/launch.ts +11 -3
  106. package/src/commands/notify.ts +5 -1
  107. package/src/commands/session.ts +6 -1
  108. package/src/config/keybindings.ts +5 -0
  109. package/src/config/model-profile-activation.ts +1 -1
  110. package/src/config/model-profiles.ts +23 -23
  111. package/src/config/settings-schema.ts +41 -1
  112. package/src/coordinator-mcp/model-preset.ts +100 -0
  113. package/src/coordinator-mcp/server.ts +2653 -425
  114. package/src/daemon/control-types.ts +11 -0
  115. package/src/daemon/operator-contract.ts +121 -0
  116. package/src/deep-interview/render-middleware.ts +67 -2
  117. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
  118. package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
  119. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
  120. package/src/gjc-runtime/launch-tmux.ts +702 -135
  121. package/src/gjc-runtime/session-state-sidecar.ts +756 -135
  122. package/src/gjc-runtime/team-runtime.ts +6 -0
  123. package/src/gjc-runtime/tmux-common.ts +26 -7
  124. package/src/gjc-runtime/tmux-gc.ts +52 -9
  125. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +185 -0
  126. package/src/gjc-runtime/tmux-owner-isolation.ts +2060 -0
  127. package/src/gjc-runtime/tmux-sessions.ts +871 -68
  128. package/src/internal-urls/docs-index.generated.ts +9 -8
  129. package/src/internal-urls/local-protocol.ts +29 -10
  130. package/src/main.ts +298 -75
  131. package/src/modes/acp/acp-agent.ts +728 -98
  132. package/src/modes/acp/acp-mode.ts +29 -2
  133. package/src/modes/components/assistant-message.ts +32 -3
  134. package/src/modes/components/bash-execution.ts +43 -13
  135. package/src/modes/components/custom-editor.ts +5 -0
  136. package/src/modes/components/gajae-pet-widget.ts +360 -0
  137. package/src/modes/components/hook-selector.ts +12 -0
  138. package/src/modes/components/irc-sidebar.ts +206 -0
  139. package/src/modes/components/pet-selector.ts +55 -0
  140. package/src/modes/components/session-selector.ts +222 -221
  141. package/src/modes/components/settings-selector.ts +10 -0
  142. package/src/modes/components/tool-execution.ts +47 -6
  143. package/src/modes/components/user-message.ts +26 -9
  144. package/src/modes/controllers/command-controller.ts +28 -2
  145. package/src/modes/controllers/event-controller.ts +159 -36
  146. package/src/modes/controllers/extension-ui-controller.ts +36 -17
  147. package/src/modes/controllers/input-controller.ts +8 -2
  148. package/src/modes/controllers/selector-controller.ts +62 -15
  149. package/src/modes/interactive-mode.ts +183 -7
  150. package/src/modes/irc-observation-ledger.ts +127 -0
  151. package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
  152. package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
  153. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
  154. package/src/modes/types.ts +34 -2
  155. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  156. package/src/modes/utils/irc-message.ts +138 -0
  157. package/src/modes/utils/ui-helpers.ts +216 -50
  158. package/src/notifications/config.ts +44 -0
  159. package/src/notifications/daemon-paths.ts +8 -0
  160. package/src/notifications/index.ts +535 -75
  161. package/src/notifications/lifecycle-control-runtime.ts +930 -77
  162. package/src/notifications/notification-service.ts +710 -0
  163. package/src/notifications/rate-limit-pool.ts +46 -12
  164. package/src/notifications/telegram-daemon-cli.ts +2 -0
  165. package/src/notifications/telegram-daemon-control.ts +27 -2
  166. package/src/notifications/telegram-daemon.ts +635 -94
  167. package/src/notifications/telegram-reference.ts +73 -13
  168. package/src/notifications/topic-registry.ts +86 -9
  169. package/src/prompts/system/irc-peer-roster.md +1 -0
  170. package/src/prompts/system/subagent-system-prompt.md +1 -0
  171. package/src/prompts/system/system-prompt.md +1 -0
  172. package/src/prompts/tools/irc.md +1 -1
  173. package/src/registry/agent-registry.ts +3 -0
  174. package/src/sdk.ts +52 -1
  175. package/src/secrets/obfuscator.ts +4 -2
  176. package/src/session/agent-session.ts +396 -145
  177. package/src/session/cache-economics.ts +275 -0
  178. package/src/session/provider-safety-stop.ts +9 -0
  179. package/src/session/session-dump-format.ts +6 -0
  180. package/src/session/session-manager.ts +809 -39
  181. package/src/session/session-storage.ts +582 -38
  182. package/src/slash-commands/builtin-registry.ts +143 -9
  183. package/src/task/executor.ts +12 -3
  184. package/src/task/index.ts +8 -0
  185. package/src/task/receipt.ts +7 -2
  186. package/src/task/types.ts +26 -0
  187. package/src/tools/ask-answer-registry.ts +25 -0
  188. package/src/tools/ask.ts +355 -33
  189. package/src/tools/browser/tab-supervisor.ts +63 -6
  190. package/src/tools/browser.ts +8 -3
  191. package/src/tools/index.ts +81 -6
  192. package/src/tools/sqlite-reader.ts +8 -4
  193. package/src/tui/output-block.ts +18 -6
  194. package/src/utils/external-editor.ts +6 -1
@@ -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 `gjc 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 `gjc 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 `gjc notify` (or edit notifications.telegram in your config).",
56
+ "If the running daemon is stale or unwanted, stop it with `gjc daemon stop --force`, then rerun `gjc 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,4 +1,15 @@
1
- import { type Component, Container, Markdown, Spacer, Text } from "@gajae-code/tui";
1
+ import {
2
+ type Component,
3
+ Container,
4
+ isViewportAnchorSourceRenderer,
5
+ Markdown,
6
+ Spacer,
7
+ Text,
8
+ type ViewportAnchorProvider,
9
+ type ViewportAnchorRender,
10
+ type ViewportAnchorSource,
11
+ type ViewportAnchorSourceRenderer,
12
+ } from "@gajae-code/tui";
2
13
  import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
3
14
 
4
15
  interface RoundQuestionModel {
@@ -48,6 +59,60 @@ interface ThresholdModel {
48
59
 
49
60
  type DeepInterviewModel = RoundQuestionModel | TopologyQuestionModel | ProgressModel | ThresholdModel;
50
61
 
62
+ class DeepInterviewSemanticContainer extends Container implements ViewportAnchorProvider, ViewportAnchorSourceRenderer {
63
+ #nextSource = 0;
64
+
65
+ override addChild(component: Component): void {
66
+ super.addChild(component);
67
+ if (isViewportAnchorSourceRenderer(component)) {
68
+ this.setViewportAnchorSource(component, { id: `deep-interview-part:${this.#nextSource++}` });
69
+ }
70
+ }
71
+
72
+ override renderWithViewportAnchors(width: number): ViewportAnchorRender {
73
+ const lines = super.renderWithViewportAnchors(width).lines;
74
+ return { lines, anchors: lines.map(() => null) };
75
+ }
76
+
77
+ renderWithViewportAnchorSource(width: number, source: ViewportAnchorSource): ViewportAnchorRender {
78
+ const rendered = super.renderWithViewportAnchors(width);
79
+ const extents = new Map<string, { graphemeEnd: number; cellEnd: number }>();
80
+ for (const anchor of rendered.anchors) {
81
+ if (anchor === null) continue;
82
+ const extent = extents.get(anchor.id);
83
+ if (extent) {
84
+ extent.graphemeEnd = Math.max(extent.graphemeEnd, anchor.graphemeEnd);
85
+ extent.cellEnd = Math.max(extent.cellEnd, anchor.cellEnd);
86
+ } else {
87
+ extents.set(anchor.id, { graphemeEnd: anchor.graphemeEnd, cellEnd: anchor.cellEnd });
88
+ }
89
+ }
90
+ const offsets = new Map<string, { grapheme: number; cell: number }>();
91
+ let grapheme = 0;
92
+ let cell = 0;
93
+ for (const [id, extent] of extents) {
94
+ offsets.set(id, { grapheme, cell });
95
+ grapheme += extent.graphemeEnd;
96
+ cell += extent.cellEnd;
97
+ }
98
+ return {
99
+ lines: rendered.lines,
100
+ anchors: rendered.anchors.map(anchor => {
101
+ if (anchor === null) return null;
102
+ const offset = offsets.get(anchor.id);
103
+ if (!offset) throw new Error(`Missing deep-interview anchor offset for ${anchor.id}`);
104
+ return {
105
+ id: source.id,
106
+ graphemeStart: offset.grapheme + anchor.graphemeStart,
107
+ graphemeEnd: offset.grapheme + anchor.graphemeEnd,
108
+ cellStart: offset.cell + anchor.cellStart,
109
+ cellEnd: offset.cell + anchor.cellEnd,
110
+ };
111
+ }),
112
+ };
113
+ }
114
+ }
115
+
51
116
  function normalizeText(text: string): string {
52
117
  if (typeof text !== "string") return "";
53
118
  return text.trim().replaceAll("\r\n", "\n");
@@ -258,7 +323,7 @@ function renderPipeSummary(title: string, value: string | undefined): string | u
258
323
  }
259
324
 
260
325
  function renderModel(model: DeepInterviewModel, uiTheme: Theme): Component {
261
- const container = new Container();
326
+ const container = new DeepInterviewSemanticContainer();
262
327
  if (model.kind === "round-question") {
263
328
  const meta = [
264
329
  `Round ${model.round}`,
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Model definitions for Grok CLI's API.
3
3
  */
4
+ import { Effort } from '@gajae-code/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 '@gajae-code/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,