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.
- package/CHANGELOG.md +33 -0
- package/README.md +6 -3
- package/dist/assets/{BossLogsModal-CZMHgMBE.js → BossLogsModal-DvczRUvP.js} +1 -1
- package/dist/assets/BossSpawnModal-B2autFKk.js +1 -0
- package/dist/assets/{ControlsModal-DJ8Scm16.js → ControlsModal-BaREMQxc.js} +1 -1
- package/dist/assets/{DockerLogsModal-Vb4gqdFz.js → DockerLogsModal-DZ0udMCo.js} +1 -1
- package/dist/assets/{EmbeddedEditor-D4sHERAj.js → EmbeddedEditor-CEjr0ZSZ.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-COIYIbJS.js → GmailOAuthSetup-76BP8J8C.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-DJS59Soi.js → GoogleOAuthSetup-D45GImPI.js} +1 -1
- package/dist/assets/{IframeModal-CFysTkn9.js → IframeModal-De63RfJZ.js} +1 -1
- package/dist/assets/{IntegrationsPanel-BK8g9ceY.js → IntegrationsPanel-DYrw1Sqk.js} +2 -2
- package/dist/assets/{LogViewerModal-DRxR0HYq.js → LogViewerModal-DW-2lpln.js} +1 -1
- package/dist/assets/{MonitoringModal-CL7UMOdX.js → MonitoringModal-B0-XXyd3.js} +1 -1
- package/dist/assets/{PM2LogsModal-I3aouUlw.js → PM2LogsModal-DOfOhkCY.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-PWAg6w6b.js → RestoreArchivedAreaModal-DNGmIJmM.js} +1 -1
- package/dist/assets/Scene2DCanvas-BW1E76CQ.js +1 -0
- package/dist/assets/SceneManager-DAvc9-Wp.js +104 -0
- package/dist/assets/{SkillsPanel-EQ74sIa6.js → SkillsPanel-Br-gLCLA.js} +3 -3
- package/dist/assets/{SlackMultiInstanceSetup-CLMUteI0.js → SlackMultiInstanceSetup-DG_bIfDu.js} +1 -1
- package/dist/assets/SpawnModal-lWM8OuZW.js +1 -0
- package/dist/assets/{StatisticsModal-D6MgA_o0.js → StatisticsModal-LvA0Fcvn.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-C6n0pMn3.js → SubordinateAssignmentModal-8YxNXcR3.js} +1 -1
- package/dist/assets/{TriggerManagerPanel-Umd8iUg1.js → TriggerManagerPanel-DftIuKUG.js} +1 -1
- package/dist/assets/{WorkflowEditorPanel-CxjBxs9O.js → WorkflowEditorPanel-oLwyO_C6.js} +1 -1
- package/dist/assets/{area-logos-CThPeqIM.js → area-logos-mcpQqUMZ.js} +1 -1
- package/dist/assets/grok.ico +0 -0
- package/dist/assets/grok.png +0 -0
- package/dist/assets/index-Ay7dKyeR.js +1 -0
- package/dist/assets/index-BMw7AAAK.js +5 -0
- package/dist/assets/{index-Cz-ypmob.js → index-BQpd34Mc.js} +1 -1
- package/dist/assets/{index-BUN7MVf5.js → index-C6-68CXL.js} +3 -3
- package/dist/assets/index-CIon0DLf.js +2 -0
- package/dist/assets/{index-maZSt2YW.js → index-Cwt-E2td.js} +5 -5
- package/dist/assets/{index-DelocTaH.js → index-DFVroqA5.js} +1 -1
- package/dist/assets/index-DR6_WeF_.js +1 -0
- package/dist/assets/{index-h3Cuh2Zn.js → index-O0t07OB_.js} +2 -2
- package/dist/assets/main-D5sqeuwB.js +286 -0
- package/dist/assets/main-JBkwGXfE.css +1 -0
- package/dist/assets/{web-CfvEypdU.js → web-BkrshujV.js} +1 -1
- package/dist/assets/{web-CNjTPilF.js → web-DPETNqOf.js} +1 -1
- package/dist/assets/{web-bcYaAwP6.js → web-vG1Smf_N.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/locales/de/config.json +3 -1
- package/dist/locales/en/config.json +2 -0
- package/dist/locales/en/terminal.json +6 -0
- package/dist/locales/en/tools.json +4 -2
- package/dist/locales/es/config.json +2 -0
- package/dist/locales/fr/config.json +3 -1
- package/dist/locales/hi/config.json +3 -1
- package/dist/locales/it/config.json +3 -1
- package/dist/locales/ja/config.json +3 -1
- package/dist/locales/pt/config.json +3 -1
- package/dist/locales/ru/config.json +3 -1
- package/dist/locales/zh-CN/config.json +3 -1
- package/dist/src/packages/server/claude/backend.js +114 -10
- package/dist/src/packages/server/claude/runner/process-lifecycle.js +62 -0
- package/dist/src/packages/server/claude/runner/stdout-pipeline.js +148 -7
- package/dist/src/packages/server/claude/runner/watchdog.js +12 -0
- package/dist/src/packages/server/claude/session-loader.js +295 -0
- package/dist/src/packages/server/codex/json-event-parser.js +149 -4
- package/dist/src/packages/server/data/index.js +1 -0
- package/dist/src/packages/server/grok/backend.js +235 -0
- package/dist/src/packages/server/grok/index.js +4 -0
- package/dist/src/packages/server/grok/json-event-parser.js +153 -0
- package/dist/src/packages/server/grok/session-watcher.js +493 -0
- package/dist/src/packages/server/opencode/json-event-parser.js +184 -15
- package/dist/src/packages/server/routes/agents.js +19 -11
- package/dist/src/packages/server/runtime/grok-runtime-provider.js +11 -0
- package/dist/src/packages/server/runtime/index.js +1 -0
- package/dist/src/packages/server/services/agent-service.js +26 -5
- package/dist/src/packages/server/services/grok-usage-service.js +374 -0
- package/dist/src/packages/server/services/runtime-command-execution.js +4 -2
- package/dist/src/packages/server/services/runtime-events.js +46 -7
- package/dist/src/packages/server/services/runtime-service.js +19 -3
- package/dist/src/packages/server/utils/tool-formatting.js +40 -12
- package/dist/src/packages/server/websocket/handlers/agent-handler.js +70 -17
- package/dist/src/packages/server/websocket/listeners/runtime-listeners.js +73 -14
- package/dist/src/packages/shared/agent-types.js +10 -0
- package/package.json +1 -1
- package/dist/assets/BossSpawnModal-C6DxqtKP.js +0 -1
- package/dist/assets/Scene2DCanvas-DYPHkU3i.js +0 -1
- package/dist/assets/SceneManager-QqKygyaw.js +0 -104
- package/dist/assets/SpawnModal-BOeStihN.js +0 -1
- package/dist/assets/index-DOzFBkSH.js +0 -1
- package/dist/assets/index-DP7kd91h.js +0 -1
- package/dist/assets/index-DRyMARFk.js +0 -5
- package/dist/assets/index-DuWmOZdY.js +0 -2
- package/dist/assets/main-C2zgPci-.js +0 -256
- 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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
184
|
+
const part = event.part;
|
|
185
|
+
const text = part?.text;
|
|
80
186
|
if (!text)
|
|
81
187
|
return [];
|
|
82
|
-
|
|
83
|
-
this.
|
|
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:
|
|
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
|
|
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
|
-
//
|
|
949
|
-
//
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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(),
|