tide-commander 1.148.0 → 1.150.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 (89) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +6 -3
  3. package/dist/assets/{BossLogsModal-CZMHgMBE.js → BossLogsModal-DvczRUvP.js} +1 -1
  4. package/dist/assets/BossSpawnModal-B2autFKk.js +1 -0
  5. package/dist/assets/{ControlsModal-DJ8Scm16.js → ControlsModal-BaREMQxc.js} +1 -1
  6. package/dist/assets/{DockerLogsModal-Vb4gqdFz.js → DockerLogsModal-DZ0udMCo.js} +1 -1
  7. package/dist/assets/{EmbeddedEditor-D4sHERAj.js → EmbeddedEditor-CEjr0ZSZ.js} +1 -1
  8. package/dist/assets/{GmailOAuthSetup-COIYIbJS.js → GmailOAuthSetup-76BP8J8C.js} +1 -1
  9. package/dist/assets/{GoogleOAuthSetup-DJS59Soi.js → GoogleOAuthSetup-D45GImPI.js} +1 -1
  10. package/dist/assets/{IframeModal-CFysTkn9.js → IframeModal-De63RfJZ.js} +1 -1
  11. package/dist/assets/{IntegrationsPanel-BK8g9ceY.js → IntegrationsPanel-DYrw1Sqk.js} +2 -2
  12. package/dist/assets/{LogViewerModal-DRxR0HYq.js → LogViewerModal-DW-2lpln.js} +1 -1
  13. package/dist/assets/{MonitoringModal-CL7UMOdX.js → MonitoringModal-B0-XXyd3.js} +1 -1
  14. package/dist/assets/{PM2LogsModal-I3aouUlw.js → PM2LogsModal-DOfOhkCY.js} +1 -1
  15. package/dist/assets/{RestoreArchivedAreaModal-PWAg6w6b.js → RestoreArchivedAreaModal-DNGmIJmM.js} +1 -1
  16. package/dist/assets/Scene2DCanvas-BW1E76CQ.js +1 -0
  17. package/dist/assets/SceneManager-DAvc9-Wp.js +104 -0
  18. package/dist/assets/{SkillsPanel-EQ74sIa6.js → SkillsPanel-Br-gLCLA.js} +3 -3
  19. package/dist/assets/{SlackMultiInstanceSetup-CLMUteI0.js → SlackMultiInstanceSetup-DG_bIfDu.js} +1 -1
  20. package/dist/assets/SpawnModal-lWM8OuZW.js +1 -0
  21. package/dist/assets/{StatisticsModal-D6MgA_o0.js → StatisticsModal-LvA0Fcvn.js} +1 -1
  22. package/dist/assets/{SubordinateAssignmentModal-C6n0pMn3.js → SubordinateAssignmentModal-8YxNXcR3.js} +1 -1
  23. package/dist/assets/{TriggerManagerPanel-Umd8iUg1.js → TriggerManagerPanel-DftIuKUG.js} +1 -1
  24. package/dist/assets/{WorkflowEditorPanel-CxjBxs9O.js → WorkflowEditorPanel-oLwyO_C6.js} +1 -1
  25. package/dist/assets/{area-logos-CThPeqIM.js → area-logos-mcpQqUMZ.js} +1 -1
  26. package/dist/assets/grok.ico +0 -0
  27. package/dist/assets/grok.png +0 -0
  28. package/dist/assets/index-Ay7dKyeR.js +1 -0
  29. package/dist/assets/index-BMw7AAAK.js +5 -0
  30. package/dist/assets/{index-Cz-ypmob.js → index-BQpd34Mc.js} +1 -1
  31. package/dist/assets/{index-BUN7MVf5.js → index-C6-68CXL.js} +3 -3
  32. package/dist/assets/index-CIon0DLf.js +2 -0
  33. package/dist/assets/{index-maZSt2YW.js → index-Cwt-E2td.js} +5 -5
  34. package/dist/assets/{index-DelocTaH.js → index-DFVroqA5.js} +1 -1
  35. package/dist/assets/index-DR6_WeF_.js +1 -0
  36. package/dist/assets/{index-h3Cuh2Zn.js → index-O0t07OB_.js} +2 -2
  37. package/dist/assets/main-D5sqeuwB.js +286 -0
  38. package/dist/assets/main-JBkwGXfE.css +1 -0
  39. package/dist/assets/{web-CfvEypdU.js → web-BkrshujV.js} +1 -1
  40. package/dist/assets/{web-CNjTPilF.js → web-DPETNqOf.js} +1 -1
  41. package/dist/assets/{web-bcYaAwP6.js → web-vG1Smf_N.js} +1 -1
  42. package/dist/index.html +2 -2
  43. package/dist/locales/de/config.json +3 -1
  44. package/dist/locales/en/config.json +2 -0
  45. package/dist/locales/en/terminal.json +6 -0
  46. package/dist/locales/en/tools.json +4 -2
  47. package/dist/locales/es/config.json +2 -0
  48. package/dist/locales/fr/config.json +3 -1
  49. package/dist/locales/hi/config.json +3 -1
  50. package/dist/locales/it/config.json +3 -1
  51. package/dist/locales/ja/config.json +3 -1
  52. package/dist/locales/pt/config.json +3 -1
  53. package/dist/locales/ru/config.json +3 -1
  54. package/dist/locales/zh-CN/config.json +3 -1
  55. package/dist/src/packages/server/claude/backend.js +114 -10
  56. package/dist/src/packages/server/claude/runner/process-lifecycle.js +62 -0
  57. package/dist/src/packages/server/claude/runner/stdout-pipeline.js +148 -7
  58. package/dist/src/packages/server/claude/runner/watchdog.js +12 -0
  59. package/dist/src/packages/server/claude/session-loader.js +295 -0
  60. package/dist/src/packages/server/codex/json-event-parser.js +149 -4
  61. package/dist/src/packages/server/data/index.js +1 -0
  62. package/dist/src/packages/server/grok/backend.js +235 -0
  63. package/dist/src/packages/server/grok/index.js +4 -0
  64. package/dist/src/packages/server/grok/json-event-parser.js +153 -0
  65. package/dist/src/packages/server/grok/session-watcher.js +493 -0
  66. package/dist/src/packages/server/opencode/json-event-parser.js +184 -15
  67. package/dist/src/packages/server/routes/agents.js +19 -11
  68. package/dist/src/packages/server/runtime/grok-runtime-provider.js +11 -0
  69. package/dist/src/packages/server/runtime/index.js +1 -0
  70. package/dist/src/packages/server/services/agent-service.js +26 -5
  71. package/dist/src/packages/server/services/grok-usage-service.js +374 -0
  72. package/dist/src/packages/server/services/runtime-command-execution.js +4 -2
  73. package/dist/src/packages/server/services/runtime-events.js +46 -7
  74. package/dist/src/packages/server/services/runtime-service.js +19 -3
  75. package/dist/src/packages/server/utils/tool-formatting.js +40 -12
  76. package/dist/src/packages/server/websocket/handlers/agent-handler.js +70 -17
  77. package/dist/src/packages/server/websocket/listeners/runtime-listeners.js +73 -14
  78. package/dist/src/packages/shared/agent-types.js +10 -0
  79. package/package.json +1 -1
  80. package/dist/assets/BossSpawnModal-C6DxqtKP.js +0 -1
  81. package/dist/assets/Scene2DCanvas-DYPHkU3i.js +0 -1
  82. package/dist/assets/SceneManager-QqKygyaw.js +0 -104
  83. package/dist/assets/SpawnModal-BOeStihN.js +0 -1
  84. package/dist/assets/index-DOzFBkSH.js +0 -1
  85. package/dist/assets/index-DP7kd91h.js +0 -1
  86. package/dist/assets/index-DRyMARFk.js +0 -5
  87. package/dist/assets/index-DuWmOZdY.js +0 -2
  88. package/dist/assets/main-C2zgPci-.js +0 -256
  89. package/dist/assets/main-C3JO2TRI.css +0 -1
@@ -1,6 +1,16 @@
1
1
  /**
2
2
  * OpenCode JSON Event Parser
3
- * Maps OpenCode NDJSON events to StandardEvent format
3
+ * Maps OpenCode NDJSON events to StandardEvent format.
4
+ *
5
+ * Streaming model (mirrors Claude/Grok):
6
+ * - `message.part.delta` → append delta with isStreaming:true + stable partID uuid
7
+ * - progressive `text` / `reasoning` snapshots (same part.id, growing text) →
8
+ * emit only the new suffix as a streaming chunk
9
+ * - final text (part.time.end set, or step_finish) → isStreaming:false same uuid
10
+ *
11
+ * Note: current `opencode run --format json` often emits a single complete `text`
12
+ * event per turn (no token deltas). This parser still assigns stable uuids and is
13
+ * ready for delta / progressive events when the CLI surfaces them.
4
14
  */
5
15
  import { createLogger } from '../utils/logger.js';
6
16
  const log = createLogger('OpencodeParser');
@@ -34,6 +44,12 @@ export class OpencodeJsonEventParser {
34
44
  textEventEmittedInTurn = false;
35
45
  lastThinkingContent;
36
46
  thinkingEventEmittedInTurn = false;
47
+ /** Full text seen so far per part id — used to derive deltas from snapshots. */
48
+ streamedTextByPart = new Map();
49
+ streamedThinkingByPart = new Map();
50
+ /** Open text/thinking stream uuids awaiting isStreaming:false finalize. */
51
+ openTextStreamUuids = new Set();
52
+ openThinkingStreamUuids = new Set();
37
53
  parseEvent(rawEvent) {
38
54
  const event = rawEvent;
39
55
  if (!event || typeof event !== 'object' || !event.type) {
@@ -51,42 +67,166 @@ export class OpencodeJsonEventParser {
51
67
  return this.parseToolUse(event);
52
68
  case 'step_finish':
53
69
  return this.parseStepFinish(event);
70
+ // Server/SDK event names — surface token deltas when present.
71
+ case 'message.part.delta':
72
+ return this.parseMessagePartDelta(event);
73
+ case 'message.part.updated':
74
+ return this.parseMessagePartUpdated(event);
54
75
  default:
55
76
  log.log(`parseEvent: unknown event type '${event.type}' - skipping`);
56
77
  return [];
57
78
  }
58
79
  }
59
80
  parseStepStart(event) {
81
+ // New step — allow streams again.
82
+ this.openTextStreamUuids.clear();
83
+ this.openThinkingStreamUuids.clear();
84
+ this.streamedTextByPart.clear();
85
+ this.streamedThinkingByPart.clear();
60
86
  return [{
61
87
  type: 'init',
62
88
  sessionId: event.sessionID,
63
89
  }];
64
90
  }
65
- parseText(event) {
66
- const text = event.part?.text;
67
- if (!text)
91
+ streamUuid(kind, partId, messageId) {
92
+ if (partId)
93
+ return `opencode-${kind}-${partId}`;
94
+ if (messageId)
95
+ return `opencode-${kind}-msg-${messageId}`;
96
+ return `opencode-${kind}-anon`;
97
+ }
98
+ /**
99
+ * Emit streaming text from a full snapshot: only the new suffix is sent as a
100
+ * delta so the client can append. Returns [] when the snapshot is unchanged.
101
+ */
102
+ emitProgressive(kind, fullText, partId, messageId, isFinal) {
103
+ const map = kind === 'text' ? this.streamedTextByPart : this.streamedThinkingByPart;
104
+ const openSet = kind === 'text' ? this.openTextStreamUuids : this.openThinkingStreamUuids;
105
+ const key = partId || messageId || 'anon';
106
+ const prev = map.get(key) || '';
107
+ const uuid = this.streamUuid(kind, partId, messageId);
108
+ if (kind === 'text') {
109
+ this.lastTextContent = fullText;
110
+ this.textEventEmittedInTurn = true;
111
+ }
112
+ else {
113
+ this.lastThinkingContent = fullText;
114
+ this.thinkingEventEmittedInTurn = true;
115
+ }
116
+ // Unchanged snapshot (common re-broadcast) — only finalize if needed.
117
+ if (fullText === prev) {
118
+ if (isFinal && openSet.has(uuid)) {
119
+ openSet.delete(uuid);
120
+ return [{
121
+ type: kind === 'text' ? 'text' : 'thinking',
122
+ text: fullText,
123
+ isStreaming: false,
124
+ uuid,
125
+ }];
126
+ }
68
127
  return [];
69
- // Track last text for resultText in step_complete (boss delegation parsing)
70
- this.lastTextContent = text;
71
- this.textEventEmittedInTurn = true;
128
+ }
129
+ map.set(key, fullText);
130
+ // Growing prefix → emit suffix as streaming chunk.
131
+ if (fullText.startsWith(prev) && !isFinal) {
132
+ const delta = fullText.slice(prev.length);
133
+ if (!delta)
134
+ return [];
135
+ openSet.add(uuid);
136
+ return [{
137
+ type: kind === 'text' ? 'text' : 'thinking',
138
+ text: delta,
139
+ isStreaming: true,
140
+ uuid,
141
+ }];
142
+ }
143
+ // Non-prefix rewrite or final: replace with full text.
144
+ if (isFinal) {
145
+ openSet.delete(uuid);
146
+ return [{
147
+ type: kind === 'text' ? 'text' : 'thinking',
148
+ text: fullText,
149
+ isStreaming: false,
150
+ uuid,
151
+ }];
152
+ }
153
+ openSet.add(uuid);
154
+ // Client merge appends when isStreaming; for rewrites send full text with
155
+ // isStreaming:false then... no, that finalizes early. Prefer replace path:
156
+ // emit as streaming full text only if prev empty; else finalize+restart.
157
+ if (!prev) {
158
+ return [{
159
+ type: kind === 'text' ? 'text' : 'thinking',
160
+ text: fullText,
161
+ isStreaming: true,
162
+ uuid,
163
+ }];
164
+ }
165
+ // Rare rewrite: finalize with new full content.
166
+ openSet.delete(uuid);
72
167
  return [{
73
- type: 'text',
74
- text,
168
+ type: kind === 'text' ? 'text' : 'thinking',
169
+ text: fullText,
75
170
  isStreaming: false,
171
+ uuid,
76
172
  }];
77
173
  }
174
+ parseText(event) {
175
+ const part = event.part;
176
+ const text = part?.text;
177
+ if (!text)
178
+ return [];
179
+ // CLI format marks completion with time.end; absence → treat as progressive.
180
+ const isFinal = typeof part?.time?.end === 'number';
181
+ return this.emitProgressive('text', text, part?.id, part?.messageID, isFinal);
182
+ }
78
183
  parseReasoning(event) {
79
- const text = event.part?.text;
184
+ const part = event.part;
185
+ const text = part?.text;
80
186
  if (!text)
81
187
  return [];
82
- this.lastThinkingContent = text;
83
- this.thinkingEventEmittedInTurn = true;
188
+ const isFinal = typeof part?.time?.end === 'number';
189
+ return this.emitProgressive('thinking', text, part?.id, part?.messageID, isFinal);
190
+ }
191
+ parseMessagePartDelta(event) {
192
+ const props = event.properties || {};
193
+ const delta = props.delta ?? event.part?.delta;
194
+ const field = props.field || event.part?.field || 'text';
195
+ const partId = props.partID || event.part?.id;
196
+ const messageId = props.messageID || event.part?.messageID;
197
+ if (typeof delta !== 'string' || !delta)
198
+ return [];
199
+ const kind = field === 'reasoning' || field === 'thinking' ? 'thinking' : 'text';
200
+ const map = kind === 'text' ? this.streamedTextByPart : this.streamedThinkingByPart;
201
+ const openSet = kind === 'text' ? this.openTextStreamUuids : this.openThinkingStreamUuids;
202
+ const key = partId || messageId || 'anon';
203
+ const next = (map.get(key) || '') + delta;
204
+ map.set(key, next);
205
+ if (kind === 'text') {
206
+ this.lastTextContent = next;
207
+ this.textEventEmittedInTurn = true;
208
+ }
209
+ else {
210
+ this.lastThinkingContent = next;
211
+ this.thinkingEventEmittedInTurn = true;
212
+ }
213
+ const uuid = this.streamUuid(kind, partId, messageId);
214
+ openSet.add(uuid);
84
215
  return [{
85
- type: 'thinking',
86
- text,
87
- isStreaming: false,
216
+ type: kind === 'text' ? 'text' : 'thinking',
217
+ text: delta,
218
+ isStreaming: true,
219
+ uuid,
88
220
  }];
89
221
  }
222
+ parseMessagePartUpdated(event) {
223
+ const part = event.properties?.part || event.part;
224
+ if (!part?.text)
225
+ return [];
226
+ const kind = part.type === 'reasoning' || part.type === 'thinking' ? 'thinking' : 'text';
227
+ const isFinal = typeof part.time?.end === 'number';
228
+ return this.emitProgressive(kind, part.text, part.id, part.messageID, isFinal);
229
+ }
90
230
  parseToolUse(event) {
91
231
  const part = event.part;
92
232
  if (!part)
@@ -103,6 +243,7 @@ export class OpencodeJsonEventParser {
103
243
  };
104
244
  if (callId) {
105
245
  toolStart.toolUseId = callId;
246
+ toolStart.uuid = callId;
106
247
  }
107
248
  events.push(toolStart);
108
249
  // Emit tool_result if state is present (completed tool)
@@ -114,6 +255,7 @@ export class OpencodeJsonEventParser {
114
255
  };
115
256
  if (callId) {
116
257
  toolResult.toolUseId = callId;
258
+ toolResult.uuid = callId;
117
259
  }
118
260
  events.push(toolResult);
119
261
  }
@@ -125,6 +267,31 @@ export class OpencodeJsonEventParser {
125
267
  return [];
126
268
  const events = [];
127
269
  const tokens = part.tokens;
270
+ // Finalize any open streaming rows so the UI stops the "live" cursor.
271
+ for (const uuid of this.openTextStreamUuids) {
272
+ const text = this.lastTextContent || '';
273
+ if (text) {
274
+ events.push({
275
+ type: 'text',
276
+ text,
277
+ isStreaming: false,
278
+ uuid,
279
+ });
280
+ }
281
+ }
282
+ this.openTextStreamUuids.clear();
283
+ for (const uuid of this.openThinkingStreamUuids) {
284
+ const text = this.lastThinkingContent || '';
285
+ if (text) {
286
+ events.push({
287
+ type: 'thinking',
288
+ text,
289
+ isStreaming: false,
290
+ uuid,
291
+ });
292
+ }
293
+ }
294
+ this.openThinkingStreamUuids.clear();
128
295
  // Emit usage_snapshot if token info is available
129
296
  if (tokens) {
130
297
  events.push({
@@ -174,6 +341,8 @@ export class OpencodeJsonEventParser {
174
341
  this.textEventEmittedInTurn = false;
175
342
  this.lastThinkingContent = undefined;
176
343
  this.thinkingEventEmittedInTurn = false;
344
+ this.streamedTextByPart.clear();
345
+ this.streamedThinkingByPart.clear();
177
346
  return events;
178
347
  }
179
348
  }
@@ -22,6 +22,7 @@ import { getSystemPrompt, setSystemPrompt, clearSystemPrompt, isEchoPromptEnable
22
22
  import { markInstructionsDirtyForAll } from '../services/instruction-refresh.js';
23
23
  import { startAgentTerminal, stopAgentTerminal } from '../services/agent-terminal-service.js';
24
24
  import { buildClaudeUsageByAgentSummary, buildClaudeUsageByDaySummary, buildClaudeUsageSnapshot } from '../services/claude-usage-service.js';
25
+ import { buildGrokUsageSnapshot } from '../services/grok-usage-service.js';
25
26
  import { getBackupStatus, setBackupEnabled } from '../services/backup-service.js';
26
27
  const log = createLogger('Routes');
27
28
  const router = Router();
@@ -503,14 +504,17 @@ router.post('/bulk/change-model', async (req, res) => {
503
504
  else if (provider === 'opencode') {
504
505
  sanitized = agentService.sanitizeOpencodeModel(model);
505
506
  }
507
+ else if (provider === 'grok') {
508
+ sanitized = agentService.sanitizeGrokModel(model);
509
+ }
506
510
  if (!sanitized) {
507
511
  res.status(400).json({ error: `Invalid model "${model}" for provider "${provider}"` });
508
512
  return;
509
513
  }
510
- // Effort is Claude-only. `null` means "clear back to default"; undefined means "leave unchanged".
514
+ // Effort is Claude/Grok. `null` means "clear back to default"; undefined means "leave unchanged".
511
515
  const VALID_EFFORTS = new Set(['low', 'medium', 'high', 'xHigh', 'max']);
512
516
  let effortUpdate = { set: false };
513
- if (effort !== undefined && provider === 'claude') {
517
+ if (effort !== undefined && (provider === 'claude' || provider === 'grok')) {
514
518
  if (effort === null) {
515
519
  effortUpdate = { set: true, value: undefined };
516
520
  }
@@ -547,6 +551,8 @@ router.post('/bulk/change-model', async (req, res) => {
547
551
  modelUpdates.codexModel = sanitized;
548
552
  else if (provider === 'opencode')
549
553
  modelUpdates.opencodeModel = sanitized;
554
+ else if (provider === 'grok')
555
+ modelUpdates.grokModel = sanitized;
550
556
  if (effortUpdate.set)
551
557
  modelUpdates.effort = effortUpdate.value;
552
558
  agentService.updateAgent(agentId, modelUpdates);
@@ -943,30 +949,32 @@ router.delete('/:id', (req, res) => {
943
949
  }
944
950
  res.status(204).end();
945
951
  });
946
- // GET /api/agents/:id/usage - Claude usage snapshot for a single agent
952
+ // GET /api/agents/:id/usage - Claude or Grok usage snapshot for a single agent
947
953
  //
948
- // Mirrors what the Claude CLI's `/usage` slash command surfaces: local agent
949
- // stats plus the live rate-limit gauges fetched with the CLI's own OAuth
950
- // credentials. See services/claude-usage-service.ts for the source list.
954
+ // Claude: local agent stats + Anthropic OAuth rate-limit gauges (CLI `/usage`).
955
+ // Grok: local agent stats + CLI chat-proxy billing/credit gauges (CLI `/usage`).
951
956
  router.get('/:id/usage', async (req, res) => {
952
957
  const agent = agentService.getAgent(req.params.id);
953
958
  if (!agent) {
954
959
  res.status(404).json({ error: 'Agent not found' });
955
960
  return;
956
961
  }
957
- if (agent.provider !== 'claude') {
962
+ const provider = agent.provider ?? 'claude';
963
+ if (provider !== 'claude' && provider !== 'grok') {
958
964
  res.status(400).json({
959
- error: 'Usage data is only available for Claude agents',
960
- provider: agent.provider,
965
+ error: 'Usage data is only available for Claude and Grok agents',
966
+ provider,
961
967
  });
962
968
  return;
963
969
  }
964
970
  try {
965
- const snapshot = await buildClaudeUsageSnapshot(agent);
971
+ const snapshot = provider === 'grok'
972
+ ? await buildGrokUsageSnapshot(agent)
973
+ : await buildClaudeUsageSnapshot(agent);
966
974
  res.json(snapshot);
967
975
  }
968
976
  catch (err) {
969
- log.error('Failed to build Claude usage snapshot:', err);
977
+ log.error(`Failed to build ${provider} usage snapshot:`, err);
970
978
  res.status(500).json({ error: err?.message ?? 'Failed to build usage snapshot' });
971
979
  }
972
980
  });
@@ -0,0 +1,11 @@
1
+ import { ClaudeRunner } from '../claude/runner.js';
2
+ import { GrokBackend } from '../grok/backend.js';
3
+ class GrokRuntimeProvider {
4
+ name = 'grok';
5
+ createRunner(callbacks) {
6
+ return new ClaudeRunner(callbacks, new GrokBackend());
7
+ }
8
+ }
9
+ export function createGrokRuntimeProvider() {
10
+ return new GrokRuntimeProvider();
11
+ }
@@ -1,3 +1,4 @@
1
1
  export { createClaudeRuntimeProvider } from './claude-runtime-provider.js';
2
2
  export { createCodexRuntimeProvider } from './codex-runtime-provider.js';
3
3
  export { createOpencodeRuntimeProvider } from './opencode-runtime-provider.js';
4
+ export { createGrokRuntimeProvider } from './grok-runtime-provider.js';
@@ -5,7 +5,7 @@
5
5
  import * as fs from 'fs';
6
6
  import * as os from 'os';
7
7
  import * as path from 'path';
8
- import { CLAUDE_MODELS as CLAUDE_MODEL_METADATA } from '../../shared/agent-types.js';
8
+ import { CLAUDE_MODELS as CLAUDE_MODEL_METADATA, GROK_MODELS, DEFAULT_GROK_MODEL } from '../../shared/agent-types.js';
9
9
  import { loadAgents, saveAgents, saveAgentsAsync, getDataDir, loadAreas, saveAreas, loadSessionHistory, saveSessionHistory, addSessionHistoryEntry, getSessionHistoryForAgent } from '../data/index.js';
10
10
  import { listSessions, getSessionSummary, getProjectDir, loadSession, loadToolHistory, searchSession, } from '../claude/session-loader.js';
11
11
  import { loadSubagentHistory } from '../claude/subagent-history-loader.js';
@@ -16,11 +16,19 @@ const VALID_CLAUDE_MODELS = new Set(Object.keys(CLAUDE_MODEL_METADATA));
16
16
  const DEFAULT_CLAUDE_CONTEXT_LIMIT = 200000;
17
17
  const DEFAULT_CODEX_CONTEXT_LIMIT = 258400;
18
18
  const DEFAULT_OPENCODE_CONTEXT_LIMIT = 200000;
19
- function getDefaultContextLimit(provider, model) {
19
+ /** Matches live Grok Build signals.json contextWindowTokens (catalog may list 2M). */
20
+ const DEFAULT_GROK_CONTEXT_LIMIT = 500000;
21
+ function getDefaultContextLimit(provider, model, grokModel) {
20
22
  if (provider === 'codex')
21
23
  return DEFAULT_CODEX_CONTEXT_LIMIT;
22
24
  if (provider === 'opencode')
23
25
  return DEFAULT_OPENCODE_CONTEXT_LIMIT;
26
+ if (provider === 'grok') {
27
+ if (grokModel && GROK_MODELS[grokModel]?.contextWindow) {
28
+ return GROK_MODELS[grokModel].contextWindow;
29
+ }
30
+ return DEFAULT_GROK_CONTEXT_LIMIT;
31
+ }
24
32
  if (model && CLAUDE_MODEL_METADATA[model]) {
25
33
  return CLAUDE_MODEL_METADATA[model].contextWindow;
26
34
  }
@@ -61,6 +69,12 @@ export function sanitizeOpencodeModel(model) {
61
69
  const trimmed = model.trim();
62
70
  return trimmed.length > 0 ? trimmed : undefined;
63
71
  }
72
+ export function sanitizeGrokModel(model) {
73
+ if (typeof model !== 'string')
74
+ return undefined;
75
+ const trimmed = model.trim();
76
+ return trimmed.length > 0 ? trimmed : undefined;
77
+ }
64
78
  function findFileRecursively(rootDir, pattern) {
65
79
  if (!fs.existsSync(rootDir))
66
80
  return null;
@@ -215,6 +229,7 @@ export function initAgents() {
215
229
  codexModel: sanitizeCodexModel(stored.codexModel),
216
230
  codexConfig: stored.codexConfig,
217
231
  opencodeModel: sanitizeOpencodeModel(stored.opencodeModel),
232
+ grokModel: sanitizeGrokModel(stored.grokModel),
218
233
  // Boss field - fallback to checking class for backward compatibility
219
234
  isBoss: stored.isBoss ?? stored.class === 'boss',
220
235
  };
@@ -278,7 +293,7 @@ export function getAgent(id) {
278
293
  export function getAllAgents() {
279
294
  return Array.from(agents.values());
280
295
  }
281
- export async function createAgent(name, agentClass, cwd, position, sessionId, useChrome, permissionMode = 'bypass', initialSkillIds, isBoss, model, codexModel, customInstructions, provider = 'claude', codexConfig, effort, opencodeModel) {
296
+ export async function createAgent(name, agentClass, cwd, position, sessionId, useChrome, permissionMode = 'bypass', initialSkillIds, isBoss, model, codexModel, customInstructions, provider = 'claude', codexConfig, effort, opencodeModel, grokModel) {
282
297
  log.log('🎆 [CREATE_AGENT] Starting agent creation:', {
283
298
  name,
284
299
  agentClass,
@@ -290,6 +305,8 @@ export async function createAgent(name, agentClass, cwd, position, sessionId, us
290
305
  model,
291
306
  codexModel,
292
307
  codexConfig,
308
+ opencodeModel,
309
+ grokModel,
293
310
  customInstructions: customInstructions ? `${customInstructions.length} chars` : undefined,
294
311
  });
295
312
  const id = generateId();
@@ -301,6 +318,9 @@ export async function createAgent(name, agentClass, cwd, position, sessionId, us
301
318
  throw new Error(`Directory does not exist: ${cwd}`);
302
319
  }
303
320
  log.log(` ✅ Directory exists`);
321
+ const sanitizedGrokModel = provider === 'grok'
322
+ ? (sanitizeGrokModel(grokModel) || DEFAULT_GROK_MODEL)
323
+ : undefined;
304
324
  // Create agent object
305
325
  // SessionId can be provided to link to an existing Claude session
306
326
  const agent = {
@@ -318,13 +338,14 @@ export async function createAgent(name, agentClass, cwd, position, sessionId, us
318
338
  useChrome,
319
339
  permissionMode,
320
340
  model: sanitizeModelForProvider(provider, model),
321
- effort: provider === 'claude' ? effort : undefined,
341
+ effort: provider === 'claude' || provider === 'grok' ? effort : undefined,
322
342
  codexModel: provider === 'codex' ? sanitizeCodexModel(codexModel) : undefined,
323
343
  codexConfig,
324
344
  opencodeModel: provider === 'opencode' ? sanitizeOpencodeModel(opencodeModel) : undefined,
345
+ grokModel: sanitizedGrokModel,
325
346
  tokensUsed: 0,
326
347
  contextUsed: 0,
327
- contextLimit: getDefaultContextLimit(provider, sanitizeModelForProvider(provider, model)),
348
+ contextLimit: getDefaultContextLimit(provider, sanitizeModelForProvider(provider, model), sanitizedGrokModel),
328
349
  taskCount: 0, // Initialize task counter
329
350
  createdAt: Date.now(),
330
351
  lastActivity: Date.now(),