codemini-cli 0.6.3 → 0.6.5
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/codemini-web/dist/assets/{AboutDialog-jgqGjQgl.js → AboutDialog-BUp8EzDg.js} +2 -2
- package/codemini-web/dist/assets/CodeWikiPanel-Fp0VKdzo.js +1 -0
- package/codemini-web/dist/assets/ConfigDialog-DIpj779O.js +1 -0
- package/codemini-web/dist/assets/GitDiffDialog-ZLEuX8Qm.js +3 -0
- package/codemini-web/dist/assets/{MemoryDialog-BhxQgG0I.js → MemoryDialog-D2YbENVd.js} +3 -3
- package/codemini-web/dist/assets/MessageBubble-BIgpZsLn.js +12 -0
- package/codemini-web/dist/assets/PatchDiff-CvKNaHsw.js +230 -0
- package/codemini-web/dist/assets/ProjectSelector-DXIep3lE.js +1 -0
- package/codemini-web/dist/assets/{SkillDialog-DxS43NpR.js → SkillDialog-DjPF-XBx.js} +4 -4
- package/codemini-web/dist/assets/SoulDialog-BfIoKETs.js +1 -0
- package/codemini-web/dist/assets/chevron-right-CfNZHlyU.js +1 -0
- package/codemini-web/dist/assets/{chunk-BO2N2NFS-Budy_hfO.js → chunk-BO2N2NFS-DMUdjM9q.js} +6 -6
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-CQS1PAvD.js → highlighted-body-OFNGDK62-8ch0jz7Z.js} +1 -1
- package/codemini-web/dist/assets/index-BhMtCC8_.js +65 -0
- package/codemini-web/dist/assets/index-DRXwJ-n_.css +2 -0
- package/codemini-web/dist/assets/input-CYpdNDlR.js +1 -0
- package/codemini-web/dist/assets/lib-BXWizt13.js +1 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-KBEtMEB9.js +1 -0
- package/codemini-web/dist/assets/{pencil-Ce_LFiEh.js → pencil-BdA2cEeE.js} +1 -1
- package/codemini-web/dist/assets/{refresh-cw-BKL-AZu5.js → refresh-cw-CJGgUGiS.js} +1 -1
- package/codemini-web/dist/assets/select-BLOccU1M.js +1 -0
- package/codemini-web/dist/assets/{trash-2-KmAlCwXd.js → trash-2-CQzNOch5.js} +1 -1
- package/codemini-web/dist/index.html +2 -2
- package/codemini-web/lib/runtime-bridge.js +332 -296
- package/codemini-web/server.js +319 -243
- package/package.json +1 -1
- package/src/core/agent-loop.js +188 -100
- package/src/core/chat-runtime.js +676 -571
- package/src/core/config-store.js +9 -3
- package/src/core/git-oplog-change-tracker.js +468 -0
- package/src/core/non-git-backup.js +116 -0
- package/src/core/paths.js +123 -123
- package/src/core/session-store.js +148 -99
- package/src/core/tools.js +555 -434
- package/src/tui/chat-app.js +196 -56
- package/codemini-web/dist/assets/CodeWikiPanel-EPuoerNv.js +0 -1
- package/codemini-web/dist/assets/ConfigDialog-B5IGZCc9.js +0 -1
- package/codemini-web/dist/assets/GitDiffDialog-Bb_Tw5ZK.js +0 -222
- package/codemini-web/dist/assets/MessageBubble-wUff4GP4.js +0 -6
- package/codemini-web/dist/assets/ProjectSelector-C0leTf6f.js +0 -1
- package/codemini-web/dist/assets/SoulDialog-XDTEGWvH.js +0 -1
- package/codemini-web/dist/assets/chevron-right-Dbzw7YzA.js +0 -1
- package/codemini-web/dist/assets/index-D0EGtNPr.js +0 -65
- package/codemini-web/dist/assets/index-wOUf3WkN.css +0 -2
- package/codemini-web/dist/assets/input-CNQgbKe6.js +0 -1
- package/codemini-web/dist/assets/lib-BOngVP_M.js +0 -11
- package/codemini-web/dist/assets/lib-DrOTTm_N.js +0 -1
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-DrBu5KyC.js +0 -1
- package/codemini-web/dist/assets/select-BZXfigic.js +0 -1
package/src/core/chat-runtime.js
CHANGED
|
@@ -33,12 +33,22 @@ import { forgetMemory, listMemories, rememberMemory, searchMemories, captureToIn
|
|
|
33
33
|
import { runDreamConsolidation } from './dream-consolidate.js';
|
|
34
34
|
import { normalizePlanState } from './plan-state.js';
|
|
35
35
|
import { countActiveTodos, normalizeTodos } from './todo-state.js';
|
|
36
|
-
import {
|
|
37
|
-
attachReflectTargets,
|
|
38
|
-
buildReflectSkillDraft,
|
|
39
|
-
parseReflectScope,
|
|
40
|
-
writeReflectSkillDraft
|
|
41
|
-
} from './reflect-skill.js';
|
|
36
|
+
import {
|
|
37
|
+
attachReflectTargets,
|
|
38
|
+
buildReflectSkillDraft,
|
|
39
|
+
parseReflectScope,
|
|
40
|
+
writeReflectSkillDraft
|
|
41
|
+
} from './reflect-skill.js';
|
|
42
|
+
import {
|
|
43
|
+
beginGitOplogCapture,
|
|
44
|
+
captureGitOplogChanges,
|
|
45
|
+
createGitOplogChangeTracker,
|
|
46
|
+
listGitOplogChanges,
|
|
47
|
+
readGitOplogPatch,
|
|
48
|
+
undoGitOplogChange,
|
|
49
|
+
undoGitOplogChanges
|
|
50
|
+
} from './git-oplog-change-tracker.js';
|
|
51
|
+
import { createNonGitBackupManager } from './non-git-backup.js';
|
|
42
52
|
|
|
43
53
|
const STREAM_SAVE_DEBOUNCE_MS = 120;
|
|
44
54
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -83,283 +93,283 @@ function slugify(input) {
|
|
|
83
93
|
return base || 'untitled';
|
|
84
94
|
}
|
|
85
95
|
|
|
86
|
-
function nowStamp() {
|
|
87
|
-
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function numberFromPath(obj, pathParts) {
|
|
91
|
-
let current = obj;
|
|
92
|
-
for (const part of pathParts) {
|
|
93
|
-
if (!current || typeof current !== 'object') return null;
|
|
94
|
-
current = current[part];
|
|
95
|
-
}
|
|
96
|
-
const value = Number(current);
|
|
97
|
-
return Number.isFinite(value) ? Math.max(0, value) : null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function firstFiniteNumber(obj, paths) {
|
|
101
|
-
for (const pathParts of paths) {
|
|
102
|
-
const value = numberFromPath(obj, pathParts);
|
|
103
|
-
if (value != null) return value;
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function sumFiniteNumbers(obj, paths) {
|
|
109
|
-
let sum = 0;
|
|
110
|
-
let found = false;
|
|
111
|
-
for (const pathParts of paths) {
|
|
112
|
-
const value = numberFromPath(obj, pathParts);
|
|
113
|
-
if (value != null) {
|
|
114
|
-
sum += value;
|
|
115
|
-
found = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return found ? sum : null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function collectRawUsage(usage) {
|
|
122
|
-
if (!usage || typeof usage !== 'object') return [];
|
|
123
|
-
if (Array.isArray(usage.raw)) {
|
|
124
|
-
return usage.raw
|
|
125
|
-
.filter((item) => item && typeof item === 'object')
|
|
126
|
-
.map((item) => ({ ...item }));
|
|
127
|
-
}
|
|
128
|
-
return [{ ...usage }];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function normalizeModelUsage(usage) {
|
|
132
|
-
if (!usage || typeof usage !== 'object') return null;
|
|
133
|
-
const promptCacheHitTokens = firstFiniteNumber(usage, [
|
|
134
|
-
['prompt_cache_hit_tokens'],
|
|
135
|
-
['promptCacheHitTokens'],
|
|
136
|
-
['cache_hit_tokens'],
|
|
137
|
-
['cacheHitTokens']
|
|
138
|
-
]);
|
|
139
|
-
const promptCacheMissTokens = firstFiniteNumber(usage, [
|
|
140
|
-
['prompt_cache_miss_tokens'],
|
|
141
|
-
['promptCacheMissTokens'],
|
|
142
|
-
['cache_miss_tokens'],
|
|
143
|
-
['cacheMissTokens']
|
|
144
|
-
]);
|
|
145
|
-
const explicitInputTokens = firstFiniteNumber(usage, [
|
|
146
|
-
['prompt_tokens'],
|
|
147
|
-
['input_tokens'],
|
|
148
|
-
['inputTokens'],
|
|
149
|
-
['promptTokens'],
|
|
150
|
-
['prompt_token_count'],
|
|
151
|
-
['promptTokenCount'],
|
|
152
|
-
['input_token_count'],
|
|
153
|
-
['inputTokenCount'],
|
|
154
|
-
['input_total_tokens'],
|
|
155
|
-
['total_input_tokens'],
|
|
156
|
-
['usage', 'prompt_tokens'],
|
|
157
|
-
['usage', 'input_tokens'],
|
|
158
|
-
['usage_metadata', 'prompt_token_count'],
|
|
159
|
-
['usage_metadata', 'input_token_count'],
|
|
160
|
-
['usageMetadata', 'promptTokenCount'],
|
|
161
|
-
['usageMetadata', 'inputTokenCount'],
|
|
162
|
-
['token_usage', 'prompt_tokens'],
|
|
163
|
-
['token_usage', 'input_tokens'],
|
|
164
|
-
['tokenUsage', 'promptTokens'],
|
|
165
|
-
['tokenUsage', 'inputTokens'],
|
|
166
|
-
['tokens', 'input_tokens'],
|
|
167
|
-
['tokens', 'inputTokens'],
|
|
168
|
-
['tokens', 'prompt_tokens'],
|
|
169
|
-
['tokens', 'promptTokens'],
|
|
170
|
-
['billed_units', 'input_tokens'],
|
|
171
|
-
['billedUnits', 'inputTokens']
|
|
172
|
-
]);
|
|
173
|
-
const cacheReadInputTokens = firstFiniteNumber(usage, [
|
|
174
|
-
['cache_read_input_tokens'],
|
|
175
|
-
['cacheReadInputTokens'],
|
|
176
|
-
['cache_read_tokens'],
|
|
177
|
-
['cacheReadTokens']
|
|
178
|
-
]);
|
|
179
|
-
const outputTokens = firstFiniteNumber(usage, [
|
|
180
|
-
['completion_tokens'],
|
|
181
|
-
['output_tokens'],
|
|
182
|
-
['outputTokens'],
|
|
183
|
-
['completionTokens'],
|
|
184
|
-
['completion_token_count'],
|
|
185
|
-
['completionTokenCount'],
|
|
186
|
-
['output_token_count'],
|
|
187
|
-
['outputTokenCount'],
|
|
188
|
-
['candidates_token_count'],
|
|
189
|
-
['candidatesTokenCount'],
|
|
190
|
-
['usage', 'completion_tokens'],
|
|
191
|
-
['usage', 'output_tokens'],
|
|
192
|
-
['usage_metadata', 'candidates_token_count'],
|
|
193
|
-
['usage_metadata', 'output_token_count'],
|
|
194
|
-
['usageMetadata', 'candidatesTokenCount'],
|
|
195
|
-
['usageMetadata', 'outputTokenCount'],
|
|
196
|
-
['token_usage', 'completion_tokens'],
|
|
197
|
-
['token_usage', 'output_tokens'],
|
|
198
|
-
['tokenUsage', 'completionTokens'],
|
|
199
|
-
['tokenUsage', 'outputTokens'],
|
|
200
|
-
['tokens', 'output_tokens'],
|
|
201
|
-
['tokens', 'outputTokens'],
|
|
202
|
-
['tokens', 'completion_tokens'],
|
|
203
|
-
['tokens', 'completionTokens'],
|
|
204
|
-
['billed_units', 'output_tokens'],
|
|
205
|
-
['billedUnits', 'outputTokens']
|
|
206
|
-
]);
|
|
207
|
-
const explicitTotal = firstFiniteNumber(usage, [
|
|
208
|
-
['total_tokens'],
|
|
209
|
-
['totalTokens'],
|
|
210
|
-
['total_token_count'],
|
|
211
|
-
['totalTokenCount'],
|
|
212
|
-
['usage', 'total_tokens'],
|
|
213
|
-
['usage_metadata', 'total_token_count'],
|
|
214
|
-
['usageMetadata', 'totalTokenCount'],
|
|
215
|
-
['token_usage', 'total_tokens'],
|
|
216
|
-
['tokenUsage', 'totalTokens'],
|
|
217
|
-
['tokens', 'total_tokens'],
|
|
218
|
-
['tokens', 'totalTokens']
|
|
219
|
-
]);
|
|
220
|
-
const cachedInputTokens = firstFiniteNumber(usage, [
|
|
221
|
-
['prompt_tokens_details', 'cached_tokens'],
|
|
222
|
-
['input_tokens_details', 'cached_tokens'],
|
|
223
|
-
['promptTokensDetails', 'cachedTokens'],
|
|
224
|
-
['inputTokensDetails', 'cachedTokens'],
|
|
225
|
-
['cache_read_input_tokens'],
|
|
226
|
-
['cacheReadInputTokens'],
|
|
227
|
-
['cache_read_tokens'],
|
|
228
|
-
['cacheReadTokens'],
|
|
229
|
-
['cached_tokens'],
|
|
230
|
-
['cachedTokens'],
|
|
231
|
-
['cached_input_tokens'],
|
|
232
|
-
['cachedInputTokens'],
|
|
233
|
-
['cached_content_token_count'],
|
|
234
|
-
['cachedContentTokenCount'],
|
|
235
|
-
['usage', 'prompt_tokens_details', 'cached_tokens'],
|
|
236
|
-
['usage', 'input_tokens_details', 'cached_tokens'],
|
|
237
|
-
['usage_metadata', 'cached_content_token_count'],
|
|
238
|
-
['usageMetadata', 'cachedContentTokenCount'],
|
|
239
|
-
['token_usage', 'prompt_tokens_details', 'cached_tokens'],
|
|
240
|
-
['tokenUsage', 'promptTokensDetails', 'cachedTokens'],
|
|
241
|
-
['tokens', 'cached_tokens'],
|
|
242
|
-
['tokens', 'cachedTokens'],
|
|
243
|
-
['prompt_cache_hit_tokens'],
|
|
244
|
-
['promptCacheHitTokens'],
|
|
245
|
-
['cache_hit_tokens'],
|
|
246
|
-
['cacheHitTokens']
|
|
247
|
-
]);
|
|
248
|
-
const explicitCacheMissInputTokens = firstFiniteNumber(usage, [
|
|
249
|
-
['prompt_cache_miss_tokens'],
|
|
250
|
-
['promptCacheMissTokens'],
|
|
251
|
-
['cache_miss_tokens'],
|
|
252
|
-
['cacheMissTokens']
|
|
253
|
-
]);
|
|
254
|
-
const cacheWriteInputTokens = firstFiniteNumber(usage, [
|
|
255
|
-
['cache_creation_input_tokens'],
|
|
256
|
-
['cacheCreationInputTokens'],
|
|
257
|
-
['cache_write_input_tokens'],
|
|
258
|
-
['cacheWriteInputTokens'],
|
|
259
|
-
['cache_creation_tokens'],
|
|
260
|
-
['cacheCreationTokens'],
|
|
261
|
-
['usage', 'cache_creation_input_tokens'],
|
|
262
|
-
['usage', 'cache_write_input_tokens'],
|
|
263
|
-
['token_usage', 'cache_creation_input_tokens'],
|
|
264
|
-
['tokenUsage', 'cacheCreationInputTokens']
|
|
265
|
-
]) ?? sumFiniteNumbers(usage, [
|
|
266
|
-
['cache_creation', 'ephemeral_5m_input_tokens'],
|
|
267
|
-
['cache_creation', 'ephemeral_1h_input_tokens'],
|
|
268
|
-
['cacheCreation', 'ephemeral5mInputTokens'],
|
|
269
|
-
['cacheCreation', 'ephemeral1hInputTokens'],
|
|
270
|
-
['usage', 'cache_creation', 'ephemeral_5m_input_tokens'],
|
|
271
|
-
['usage', 'cache_creation', 'ephemeral_1h_input_tokens']
|
|
272
|
-
]);
|
|
273
|
-
const hasAnthropicSplitCacheInput = explicitInputTokens != null
|
|
274
|
-
&& (cacheReadInputTokens != null || cacheWriteInputTokens != null)
|
|
275
|
-
&& promptCacheHitTokens == null;
|
|
276
|
-
const cacheMissInputTokens = explicitCacheMissInputTokens ?? (
|
|
277
|
-
hasAnthropicSplitCacheInput
|
|
278
|
-
? Number(explicitInputTokens || 0) + Number(cacheWriteInputTokens || 0)
|
|
279
|
-
: null
|
|
280
|
-
);
|
|
281
|
-
const inputTokens = explicitInputTokens != null
|
|
282
|
-
? Number(explicitInputTokens || 0)
|
|
283
|
-
+ (hasAnthropicSplitCacheInput ? Number(cacheReadInputTokens || 0) + Number(cacheWriteInputTokens || 0) : 0)
|
|
284
|
-
: (
|
|
285
|
-
promptCacheHitTokens != null || promptCacheMissTokens != null
|
|
286
|
-
? Number(promptCacheHitTokens || 0) + Number(promptCacheMissTokens || 0)
|
|
287
|
-
: null
|
|
288
|
-
);
|
|
289
|
-
const reasoningOutputTokens = firstFiniteNumber(usage, [
|
|
290
|
-
['completion_tokens_details', 'reasoning_tokens'],
|
|
291
|
-
['output_tokens_details', 'reasoning_tokens'],
|
|
292
|
-
['completionTokensDetails', 'reasoningTokens'],
|
|
293
|
-
['outputTokensDetails', 'reasoningTokens'],
|
|
294
|
-
['reasoning_tokens'],
|
|
295
|
-
['reasoningTokens'],
|
|
296
|
-
['thoughts_token_count'],
|
|
297
|
-
['thoughtsTokenCount'],
|
|
298
|
-
['usage', 'completion_tokens_details', 'reasoning_tokens'],
|
|
299
|
-
['usage_metadata', 'thoughts_token_count'],
|
|
300
|
-
['usageMetadata', 'thoughtsTokenCount']
|
|
301
|
-
]);
|
|
302
|
-
const totalTokens = explicitTotal ?? (
|
|
303
|
-
inputTokens != null || outputTokens != null
|
|
304
|
-
? Number(inputTokens || 0) + Number(outputTokens || 0)
|
|
305
|
-
: null
|
|
306
|
-
);
|
|
307
|
-
if (
|
|
308
|
-
inputTokens == null &&
|
|
309
|
-
outputTokens == null &&
|
|
310
|
-
totalTokens == null &&
|
|
311
|
-
cachedInputTokens == null &&
|
|
312
|
-
cacheWriteInputTokens == null
|
|
313
|
-
) {
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
return {
|
|
317
|
-
inputTokens: Math.round(inputTokens || 0),
|
|
318
|
-
outputTokens: Math.round(outputTokens || 0),
|
|
319
|
-
totalTokens: Math.round(totalTokens || 0),
|
|
320
|
-
cachedInputTokens: Math.round(cachedInputTokens || 0),
|
|
321
|
-
cacheMissInputTokens: Math.round(cacheMissInputTokens || 0),
|
|
322
|
-
cacheWriteInputTokens: Math.round(cacheWriteInputTokens || 0),
|
|
323
|
-
reasoningOutputTokens: Math.round(reasoningOutputTokens || 0),
|
|
324
|
-
requests: 1,
|
|
325
|
-
raw: collectRawUsage(usage)
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function cloneModelUsage(usage) {
|
|
330
|
-
if (!usage || typeof usage !== 'object') return null;
|
|
331
|
-
return {
|
|
332
|
-
inputTokens: Math.max(0, Math.round(Number(usage.inputTokens || 0))),
|
|
333
|
-
outputTokens: Math.max(0, Math.round(Number(usage.outputTokens || 0))),
|
|
334
|
-
totalTokens: Math.max(0, Math.round(Number(usage.totalTokens || 0))),
|
|
335
|
-
cachedInputTokens: Math.max(0, Math.round(Number(usage.cachedInputTokens || 0))),
|
|
336
|
-
cacheMissInputTokens: Math.max(0, Math.round(Number(usage.cacheMissInputTokens || 0))),
|
|
337
|
-
cacheWriteInputTokens: Math.max(0, Math.round(Number(usage.cacheWriteInputTokens || 0))),
|
|
338
|
-
reasoningOutputTokens: Math.max(0, Math.round(Number(usage.reasoningOutputTokens || 0))),
|
|
339
|
-
requests: Math.max(0, Math.round(Number(usage.requests || 0))),
|
|
340
|
-
raw: Array.isArray(usage.raw) ? usage.raw.map((item) => ({ ...item })) : []
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function mergeModelUsage(left, right) {
|
|
345
|
-
const a = cloneModelUsage(left);
|
|
346
|
-
const b = cloneModelUsage(right);
|
|
347
|
-
if (!a) return b;
|
|
348
|
-
if (!b) return a;
|
|
349
|
-
return {
|
|
350
|
-
inputTokens: a.inputTokens + b.inputTokens,
|
|
351
|
-
outputTokens: a.outputTokens + b.outputTokens,
|
|
352
|
-
totalTokens: a.totalTokens + b.totalTokens,
|
|
353
|
-
cachedInputTokens: a.cachedInputTokens + b.cachedInputTokens,
|
|
354
|
-
cacheMissInputTokens: a.cacheMissInputTokens + b.cacheMissInputTokens,
|
|
355
|
-
cacheWriteInputTokens: a.cacheWriteInputTokens + b.cacheWriteInputTokens,
|
|
356
|
-
reasoningOutputTokens: a.reasoningOutputTokens + b.reasoningOutputTokens,
|
|
357
|
-
requests: a.requests + b.requests,
|
|
358
|
-
raw: [...a.raw, ...b.raw]
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function prioritizeByPreferredOrder(items, preferredOrder) {
|
|
96
|
+
function nowStamp() {
|
|
97
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function numberFromPath(obj, pathParts) {
|
|
101
|
+
let current = obj;
|
|
102
|
+
for (const part of pathParts) {
|
|
103
|
+
if (!current || typeof current !== 'object') return null;
|
|
104
|
+
current = current[part];
|
|
105
|
+
}
|
|
106
|
+
const value = Number(current);
|
|
107
|
+
return Number.isFinite(value) ? Math.max(0, value) : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function firstFiniteNumber(obj, paths) {
|
|
111
|
+
for (const pathParts of paths) {
|
|
112
|
+
const value = numberFromPath(obj, pathParts);
|
|
113
|
+
if (value != null) return value;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sumFiniteNumbers(obj, paths) {
|
|
119
|
+
let sum = 0;
|
|
120
|
+
let found = false;
|
|
121
|
+
for (const pathParts of paths) {
|
|
122
|
+
const value = numberFromPath(obj, pathParts);
|
|
123
|
+
if (value != null) {
|
|
124
|
+
sum += value;
|
|
125
|
+
found = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return found ? sum : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function collectRawUsage(usage) {
|
|
132
|
+
if (!usage || typeof usage !== 'object') return [];
|
|
133
|
+
if (Array.isArray(usage.raw)) {
|
|
134
|
+
return usage.raw
|
|
135
|
+
.filter((item) => item && typeof item === 'object')
|
|
136
|
+
.map((item) => ({ ...item }));
|
|
137
|
+
}
|
|
138
|
+
return [{ ...usage }];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function normalizeModelUsage(usage) {
|
|
142
|
+
if (!usage || typeof usage !== 'object') return null;
|
|
143
|
+
const promptCacheHitTokens = firstFiniteNumber(usage, [
|
|
144
|
+
['prompt_cache_hit_tokens'],
|
|
145
|
+
['promptCacheHitTokens'],
|
|
146
|
+
['cache_hit_tokens'],
|
|
147
|
+
['cacheHitTokens']
|
|
148
|
+
]);
|
|
149
|
+
const promptCacheMissTokens = firstFiniteNumber(usage, [
|
|
150
|
+
['prompt_cache_miss_tokens'],
|
|
151
|
+
['promptCacheMissTokens'],
|
|
152
|
+
['cache_miss_tokens'],
|
|
153
|
+
['cacheMissTokens']
|
|
154
|
+
]);
|
|
155
|
+
const explicitInputTokens = firstFiniteNumber(usage, [
|
|
156
|
+
['prompt_tokens'],
|
|
157
|
+
['input_tokens'],
|
|
158
|
+
['inputTokens'],
|
|
159
|
+
['promptTokens'],
|
|
160
|
+
['prompt_token_count'],
|
|
161
|
+
['promptTokenCount'],
|
|
162
|
+
['input_token_count'],
|
|
163
|
+
['inputTokenCount'],
|
|
164
|
+
['input_total_tokens'],
|
|
165
|
+
['total_input_tokens'],
|
|
166
|
+
['usage', 'prompt_tokens'],
|
|
167
|
+
['usage', 'input_tokens'],
|
|
168
|
+
['usage_metadata', 'prompt_token_count'],
|
|
169
|
+
['usage_metadata', 'input_token_count'],
|
|
170
|
+
['usageMetadata', 'promptTokenCount'],
|
|
171
|
+
['usageMetadata', 'inputTokenCount'],
|
|
172
|
+
['token_usage', 'prompt_tokens'],
|
|
173
|
+
['token_usage', 'input_tokens'],
|
|
174
|
+
['tokenUsage', 'promptTokens'],
|
|
175
|
+
['tokenUsage', 'inputTokens'],
|
|
176
|
+
['tokens', 'input_tokens'],
|
|
177
|
+
['tokens', 'inputTokens'],
|
|
178
|
+
['tokens', 'prompt_tokens'],
|
|
179
|
+
['tokens', 'promptTokens'],
|
|
180
|
+
['billed_units', 'input_tokens'],
|
|
181
|
+
['billedUnits', 'inputTokens']
|
|
182
|
+
]);
|
|
183
|
+
const cacheReadInputTokens = firstFiniteNumber(usage, [
|
|
184
|
+
['cache_read_input_tokens'],
|
|
185
|
+
['cacheReadInputTokens'],
|
|
186
|
+
['cache_read_tokens'],
|
|
187
|
+
['cacheReadTokens']
|
|
188
|
+
]);
|
|
189
|
+
const outputTokens = firstFiniteNumber(usage, [
|
|
190
|
+
['completion_tokens'],
|
|
191
|
+
['output_tokens'],
|
|
192
|
+
['outputTokens'],
|
|
193
|
+
['completionTokens'],
|
|
194
|
+
['completion_token_count'],
|
|
195
|
+
['completionTokenCount'],
|
|
196
|
+
['output_token_count'],
|
|
197
|
+
['outputTokenCount'],
|
|
198
|
+
['candidates_token_count'],
|
|
199
|
+
['candidatesTokenCount'],
|
|
200
|
+
['usage', 'completion_tokens'],
|
|
201
|
+
['usage', 'output_tokens'],
|
|
202
|
+
['usage_metadata', 'candidates_token_count'],
|
|
203
|
+
['usage_metadata', 'output_token_count'],
|
|
204
|
+
['usageMetadata', 'candidatesTokenCount'],
|
|
205
|
+
['usageMetadata', 'outputTokenCount'],
|
|
206
|
+
['token_usage', 'completion_tokens'],
|
|
207
|
+
['token_usage', 'output_tokens'],
|
|
208
|
+
['tokenUsage', 'completionTokens'],
|
|
209
|
+
['tokenUsage', 'outputTokens'],
|
|
210
|
+
['tokens', 'output_tokens'],
|
|
211
|
+
['tokens', 'outputTokens'],
|
|
212
|
+
['tokens', 'completion_tokens'],
|
|
213
|
+
['tokens', 'completionTokens'],
|
|
214
|
+
['billed_units', 'output_tokens'],
|
|
215
|
+
['billedUnits', 'outputTokens']
|
|
216
|
+
]);
|
|
217
|
+
const explicitTotal = firstFiniteNumber(usage, [
|
|
218
|
+
['total_tokens'],
|
|
219
|
+
['totalTokens'],
|
|
220
|
+
['total_token_count'],
|
|
221
|
+
['totalTokenCount'],
|
|
222
|
+
['usage', 'total_tokens'],
|
|
223
|
+
['usage_metadata', 'total_token_count'],
|
|
224
|
+
['usageMetadata', 'totalTokenCount'],
|
|
225
|
+
['token_usage', 'total_tokens'],
|
|
226
|
+
['tokenUsage', 'totalTokens'],
|
|
227
|
+
['tokens', 'total_tokens'],
|
|
228
|
+
['tokens', 'totalTokens']
|
|
229
|
+
]);
|
|
230
|
+
const cachedInputTokens = firstFiniteNumber(usage, [
|
|
231
|
+
['prompt_tokens_details', 'cached_tokens'],
|
|
232
|
+
['input_tokens_details', 'cached_tokens'],
|
|
233
|
+
['promptTokensDetails', 'cachedTokens'],
|
|
234
|
+
['inputTokensDetails', 'cachedTokens'],
|
|
235
|
+
['cache_read_input_tokens'],
|
|
236
|
+
['cacheReadInputTokens'],
|
|
237
|
+
['cache_read_tokens'],
|
|
238
|
+
['cacheReadTokens'],
|
|
239
|
+
['cached_tokens'],
|
|
240
|
+
['cachedTokens'],
|
|
241
|
+
['cached_input_tokens'],
|
|
242
|
+
['cachedInputTokens'],
|
|
243
|
+
['cached_content_token_count'],
|
|
244
|
+
['cachedContentTokenCount'],
|
|
245
|
+
['usage', 'prompt_tokens_details', 'cached_tokens'],
|
|
246
|
+
['usage', 'input_tokens_details', 'cached_tokens'],
|
|
247
|
+
['usage_metadata', 'cached_content_token_count'],
|
|
248
|
+
['usageMetadata', 'cachedContentTokenCount'],
|
|
249
|
+
['token_usage', 'prompt_tokens_details', 'cached_tokens'],
|
|
250
|
+
['tokenUsage', 'promptTokensDetails', 'cachedTokens'],
|
|
251
|
+
['tokens', 'cached_tokens'],
|
|
252
|
+
['tokens', 'cachedTokens'],
|
|
253
|
+
['prompt_cache_hit_tokens'],
|
|
254
|
+
['promptCacheHitTokens'],
|
|
255
|
+
['cache_hit_tokens'],
|
|
256
|
+
['cacheHitTokens']
|
|
257
|
+
]);
|
|
258
|
+
const explicitCacheMissInputTokens = firstFiniteNumber(usage, [
|
|
259
|
+
['prompt_cache_miss_tokens'],
|
|
260
|
+
['promptCacheMissTokens'],
|
|
261
|
+
['cache_miss_tokens'],
|
|
262
|
+
['cacheMissTokens']
|
|
263
|
+
]);
|
|
264
|
+
const cacheWriteInputTokens = firstFiniteNumber(usage, [
|
|
265
|
+
['cache_creation_input_tokens'],
|
|
266
|
+
['cacheCreationInputTokens'],
|
|
267
|
+
['cache_write_input_tokens'],
|
|
268
|
+
['cacheWriteInputTokens'],
|
|
269
|
+
['cache_creation_tokens'],
|
|
270
|
+
['cacheCreationTokens'],
|
|
271
|
+
['usage', 'cache_creation_input_tokens'],
|
|
272
|
+
['usage', 'cache_write_input_tokens'],
|
|
273
|
+
['token_usage', 'cache_creation_input_tokens'],
|
|
274
|
+
['tokenUsage', 'cacheCreationInputTokens']
|
|
275
|
+
]) ?? sumFiniteNumbers(usage, [
|
|
276
|
+
['cache_creation', 'ephemeral_5m_input_tokens'],
|
|
277
|
+
['cache_creation', 'ephemeral_1h_input_tokens'],
|
|
278
|
+
['cacheCreation', 'ephemeral5mInputTokens'],
|
|
279
|
+
['cacheCreation', 'ephemeral1hInputTokens'],
|
|
280
|
+
['usage', 'cache_creation', 'ephemeral_5m_input_tokens'],
|
|
281
|
+
['usage', 'cache_creation', 'ephemeral_1h_input_tokens']
|
|
282
|
+
]);
|
|
283
|
+
const hasAnthropicSplitCacheInput = explicitInputTokens != null
|
|
284
|
+
&& (cacheReadInputTokens != null || cacheWriteInputTokens != null)
|
|
285
|
+
&& promptCacheHitTokens == null;
|
|
286
|
+
const cacheMissInputTokens = explicitCacheMissInputTokens ?? (
|
|
287
|
+
hasAnthropicSplitCacheInput
|
|
288
|
+
? Number(explicitInputTokens || 0) + Number(cacheWriteInputTokens || 0)
|
|
289
|
+
: null
|
|
290
|
+
);
|
|
291
|
+
const inputTokens = explicitInputTokens != null
|
|
292
|
+
? Number(explicitInputTokens || 0)
|
|
293
|
+
+ (hasAnthropicSplitCacheInput ? Number(cacheReadInputTokens || 0) + Number(cacheWriteInputTokens || 0) : 0)
|
|
294
|
+
: (
|
|
295
|
+
promptCacheHitTokens != null || promptCacheMissTokens != null
|
|
296
|
+
? Number(promptCacheHitTokens || 0) + Number(promptCacheMissTokens || 0)
|
|
297
|
+
: null
|
|
298
|
+
);
|
|
299
|
+
const reasoningOutputTokens = firstFiniteNumber(usage, [
|
|
300
|
+
['completion_tokens_details', 'reasoning_tokens'],
|
|
301
|
+
['output_tokens_details', 'reasoning_tokens'],
|
|
302
|
+
['completionTokensDetails', 'reasoningTokens'],
|
|
303
|
+
['outputTokensDetails', 'reasoningTokens'],
|
|
304
|
+
['reasoning_tokens'],
|
|
305
|
+
['reasoningTokens'],
|
|
306
|
+
['thoughts_token_count'],
|
|
307
|
+
['thoughtsTokenCount'],
|
|
308
|
+
['usage', 'completion_tokens_details', 'reasoning_tokens'],
|
|
309
|
+
['usage_metadata', 'thoughts_token_count'],
|
|
310
|
+
['usageMetadata', 'thoughtsTokenCount']
|
|
311
|
+
]);
|
|
312
|
+
const totalTokens = explicitTotal ?? (
|
|
313
|
+
inputTokens != null || outputTokens != null
|
|
314
|
+
? Number(inputTokens || 0) + Number(outputTokens || 0)
|
|
315
|
+
: null
|
|
316
|
+
);
|
|
317
|
+
if (
|
|
318
|
+
inputTokens == null &&
|
|
319
|
+
outputTokens == null &&
|
|
320
|
+
totalTokens == null &&
|
|
321
|
+
cachedInputTokens == null &&
|
|
322
|
+
cacheWriteInputTokens == null
|
|
323
|
+
) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
inputTokens: Math.round(inputTokens || 0),
|
|
328
|
+
outputTokens: Math.round(outputTokens || 0),
|
|
329
|
+
totalTokens: Math.round(totalTokens || 0),
|
|
330
|
+
cachedInputTokens: Math.round(cachedInputTokens || 0),
|
|
331
|
+
cacheMissInputTokens: Math.round(cacheMissInputTokens || 0),
|
|
332
|
+
cacheWriteInputTokens: Math.round(cacheWriteInputTokens || 0),
|
|
333
|
+
reasoningOutputTokens: Math.round(reasoningOutputTokens || 0),
|
|
334
|
+
requests: 1,
|
|
335
|
+
raw: collectRawUsage(usage)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function cloneModelUsage(usage) {
|
|
340
|
+
if (!usage || typeof usage !== 'object') return null;
|
|
341
|
+
return {
|
|
342
|
+
inputTokens: Math.max(0, Math.round(Number(usage.inputTokens || 0))),
|
|
343
|
+
outputTokens: Math.max(0, Math.round(Number(usage.outputTokens || 0))),
|
|
344
|
+
totalTokens: Math.max(0, Math.round(Number(usage.totalTokens || 0))),
|
|
345
|
+
cachedInputTokens: Math.max(0, Math.round(Number(usage.cachedInputTokens || 0))),
|
|
346
|
+
cacheMissInputTokens: Math.max(0, Math.round(Number(usage.cacheMissInputTokens || 0))),
|
|
347
|
+
cacheWriteInputTokens: Math.max(0, Math.round(Number(usage.cacheWriteInputTokens || 0))),
|
|
348
|
+
reasoningOutputTokens: Math.max(0, Math.round(Number(usage.reasoningOutputTokens || 0))),
|
|
349
|
+
requests: Math.max(0, Math.round(Number(usage.requests || 0))),
|
|
350
|
+
raw: Array.isArray(usage.raw) ? usage.raw.map((item) => ({ ...item })) : []
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function mergeModelUsage(left, right) {
|
|
355
|
+
const a = cloneModelUsage(left);
|
|
356
|
+
const b = cloneModelUsage(right);
|
|
357
|
+
if (!a) return b;
|
|
358
|
+
if (!b) return a;
|
|
359
|
+
return {
|
|
360
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
361
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
362
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
363
|
+
cachedInputTokens: a.cachedInputTokens + b.cachedInputTokens,
|
|
364
|
+
cacheMissInputTokens: a.cacheMissInputTokens + b.cacheMissInputTokens,
|
|
365
|
+
cacheWriteInputTokens: a.cacheWriteInputTokens + b.cacheWriteInputTokens,
|
|
366
|
+
reasoningOutputTokens: a.reasoningOutputTokens + b.reasoningOutputTokens,
|
|
367
|
+
requests: a.requests + b.requests,
|
|
368
|
+
raw: [...a.raw, ...b.raw]
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function prioritizeByPreferredOrder(items, preferredOrder) {
|
|
363
373
|
const source = Array.isArray(items) ? items : [];
|
|
364
374
|
const priorities = new Map((Array.isArray(preferredOrder) ? preferredOrder : []).map((value, index) => [value, index]));
|
|
365
375
|
return [...source].sort((left, right) => {
|
|
@@ -370,22 +380,22 @@ function prioritizeByPreferredOrder(items, preferredOrder) {
|
|
|
370
380
|
});
|
|
371
381
|
}
|
|
372
382
|
|
|
373
|
-
function normalizeUiLocale(value) {
|
|
374
|
-
return String(value || '').toLowerCase().startsWith('en') ? 'en' : 'zh';
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function formatLocalDateTimeSlug(date = new Date()) {
|
|
378
|
-
const value = date instanceof Date ? date : new Date(date);
|
|
379
|
-
const year = value.getFullYear();
|
|
380
|
-
const month = String(value.getMonth() + 1).padStart(2, '0');
|
|
381
|
-
const day = String(value.getDate()).padStart(2, '0');
|
|
382
|
-
const hour = String(value.getHours()).padStart(2, '0');
|
|
383
|
-
const minute = String(value.getMinutes()).padStart(2, '0');
|
|
384
|
-
const second = String(value.getSeconds()).padStart(2, '0');
|
|
385
|
-
return `${year}-${month}-${day}-${hour}-${minute}-${second}`;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function getCompletionCopy(language = 'zh') {
|
|
383
|
+
function normalizeUiLocale(value) {
|
|
384
|
+
return String(value || '').toLowerCase().startsWith('en') ? 'en' : 'zh';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function formatLocalDateTimeSlug(date = new Date()) {
|
|
388
|
+
const value = date instanceof Date ? date : new Date(date);
|
|
389
|
+
const year = value.getFullYear();
|
|
390
|
+
const month = String(value.getMonth() + 1).padStart(2, '0');
|
|
391
|
+
const day = String(value.getDate()).padStart(2, '0');
|
|
392
|
+
const hour = String(value.getHours()).padStart(2, '0');
|
|
393
|
+
const minute = String(value.getMinutes()).padStart(2, '0');
|
|
394
|
+
const second = String(value.getSeconds()).padStart(2, '0');
|
|
395
|
+
return `${year}-${month}-${day}-${hour}-${minute}-${second}`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function getCompletionCopy(language = 'zh') {
|
|
389
399
|
const lang = normalizeUiLocale(language);
|
|
390
400
|
return {
|
|
391
401
|
zh: {
|
|
@@ -428,7 +438,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
428
438
|
'sdk.provider': '可选:openai-compatible | anthropic',
|
|
429
439
|
'ui.language': '可选:zh | en',
|
|
430
440
|
'ui.reply_language': '可选:zh | en',
|
|
431
|
-
'execution.mode': '可选:
|
|
441
|
+
'execution.mode': '可选:normal | plan',
|
|
442
|
+
'execution.approval_mode': '可选:review | auto | full_access',
|
|
432
443
|
'shell.default': '常用:bash | powershell',
|
|
433
444
|
'policy.safe_mode': '可选:true | false',
|
|
434
445
|
'policy.allowed_paths': 'JSON 数组,例如 ["D:\\\\shared"]',
|
|
@@ -460,7 +471,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
460
471
|
commands: '列出 slash/自定义命令',
|
|
461
472
|
status: '查看运行状态(mode/model/session)',
|
|
462
473
|
model: '查看或切换模型',
|
|
463
|
-
mode: '
|
|
474
|
+
mode: '设置工作模式:normal|plan',
|
|
475
|
+
approval: '设置审阅权限:review|auto|full_access',
|
|
464
476
|
compact: '压缩消息上下文',
|
|
465
477
|
checkpoint: '创建/查看/加载检查点',
|
|
466
478
|
spec: '在 .codemini/specs 中创建 spec',
|
|
@@ -483,7 +495,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
483
495
|
generic: {
|
|
484
496
|
configCommand: '配置命令',
|
|
485
497
|
historyCommand: '历史会话命令',
|
|
486
|
-
modeCommand: '
|
|
498
|
+
modeCommand: '切换工作模式',
|
|
499
|
+
approvalCommand: '切换审阅权限',
|
|
487
500
|
checkpointCommand: '检查点命令',
|
|
488
501
|
specCommand: '创建 spec 文件',
|
|
489
502
|
planCommand: '规划命令',
|
|
@@ -541,7 +554,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
541
554
|
'sdk.provider': 'options: openai-compatible | anthropic',
|
|
542
555
|
'ui.language': 'options: zh | en',
|
|
543
556
|
'ui.reply_language': 'options: zh | en',
|
|
544
|
-
'execution.mode': 'options:
|
|
557
|
+
'execution.mode': 'options: normal | plan',
|
|
558
|
+
'execution.approval_mode': 'options: review | auto | full_access',
|
|
545
559
|
'shell.default': 'common: bash | powershell',
|
|
546
560
|
'policy.safe_mode': 'options: true | false',
|
|
547
561
|
'policy.allowed_paths': 'JSON array, for example ["D:\\\\shared"]',
|
|
@@ -573,7 +587,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
573
587
|
commands: 'list slash/custom commands',
|
|
574
588
|
status: 'show runtime status (mode/model/session)',
|
|
575
589
|
model: 'show or switch model',
|
|
576
|
-
mode: 'set
|
|
590
|
+
mode: 'set work mode: normal|plan',
|
|
591
|
+
approval: 'set approval mode: review|auto|full_access',
|
|
577
592
|
compact: 'compress message context',
|
|
578
593
|
checkpoint: 'create/list/load conversation checkpoints',
|
|
579
594
|
spec: 'create a spec markdown file in .codemini/specs',
|
|
@@ -596,7 +611,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
596
611
|
generic: {
|
|
597
612
|
configCommand: 'config command',
|
|
598
613
|
historyCommand: 'history command',
|
|
599
|
-
modeCommand: 'switch
|
|
614
|
+
modeCommand: 'switch work mode',
|
|
615
|
+
approvalCommand: 'switch approval mode',
|
|
600
616
|
checkpointCommand: 'checkpoint command',
|
|
601
617
|
specCommand: 'create a spec file',
|
|
602
618
|
planCommand: 'planning command',
|
|
@@ -2577,7 +2593,7 @@ function summarizePromptBudgetAudit(audit) {
|
|
|
2577
2593
|
return `prompt budget: ${totalTokens}/${maxContextTokens} est tokens (${pct}%)${components ? `; ${components}` : ''}`;
|
|
2578
2594
|
}
|
|
2579
2595
|
|
|
2580
|
-
function buildRuntimeStateSnapshot({ currentSession, config, model, executionMode, extraSession }) {
|
|
2596
|
+
function buildRuntimeStateSnapshot({ currentSession, config, model, executionMode, extraSession }) {
|
|
2581
2597
|
const activeParentMessages = Array.isArray(currentSession?.compact?.view) && currentSession.compact.view.length > 0
|
|
2582
2598
|
? currentSession.compact.view
|
|
2583
2599
|
: currentSession?.messages || [];
|
|
@@ -2588,11 +2604,12 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
2588
2604
|
const contextUsagePct = maxContextTokens > 0 ? Math.min(100, Math.max(0, (currentContextTokens / maxContextTokens) * 100)) : 0;
|
|
2589
2605
|
const planState = currentSession?.planState;
|
|
2590
2606
|
const snapshot = {
|
|
2591
|
-
sessionId: currentSession?.id || '',
|
|
2592
|
-
sessionTitle: currentSession?.title || '',
|
|
2593
|
-
messageCount: Array.isArray(currentSession?.messages) ? currentSession.messages.length : 0,
|
|
2594
|
-
mode: executionMode || config.execution?.mode || 'normal',
|
|
2595
|
-
|
|
2607
|
+
sessionId: currentSession?.id || '',
|
|
2608
|
+
sessionTitle: currentSession?.title || '',
|
|
2609
|
+
messageCount: Array.isArray(currentSession?.messages) ? currentSession.messages.length : 0,
|
|
2610
|
+
mode: executionMode || config.execution?.mode || 'normal',
|
|
2611
|
+
approvalMode: config.execution?.approval_mode || 'review',
|
|
2612
|
+
sdkProvider: config.sdk?.provider || 'openai-compatible',
|
|
2596
2613
|
agentRole: 'general',
|
|
2597
2614
|
model: model || config.model?.name || '',
|
|
2598
2615
|
mainModel: config.model?.name || '',
|
|
@@ -2939,10 +2956,13 @@ async function askModel({
|
|
|
2939
2956
|
signal,
|
|
2940
2957
|
allowedTools,
|
|
2941
2958
|
maxSteps: maxStepsOverride,
|
|
2942
|
-
skipAnalysisNudge = false,
|
|
2943
|
-
compactedForModel: compactedInput = null,
|
|
2944
|
-
onCompactedUpdate = null
|
|
2945
|
-
|
|
2959
|
+
skipAnalysisNudge = false,
|
|
2960
|
+
compactedForModel: compactedInput = null,
|
|
2961
|
+
onCompactedUpdate = null,
|
|
2962
|
+
changeTracker = null,
|
|
2963
|
+
backupManager = null,
|
|
2964
|
+
projectIsGit = Boolean(config?.runtime?.project_is_git)
|
|
2965
|
+
}) {
|
|
2946
2966
|
let compacted = compactedInput;
|
|
2947
2967
|
const modelInputText = typeof modelText === 'string' && modelText ? modelText : text;
|
|
2948
2968
|
const maxContextTokens = effectiveMaxContextTokens(config);
|
|
@@ -3104,11 +3124,12 @@ async function askModel({
|
|
|
3104
3124
|
scheduleSessionSave();
|
|
3105
3125
|
},
|
|
3106
3126
|
getPlanState: () => normalizePlanState(session.planState),
|
|
3107
|
-
onPlanStateUpdate: (planState) => {
|
|
3108
|
-
session.planState = normalizePlanState(planState);
|
|
3109
|
-
scheduleSessionSave();
|
|
3110
|
-
}
|
|
3111
|
-
|
|
3127
|
+
onPlanStateUpdate: (planState) => {
|
|
3128
|
+
session.planState = normalizePlanState(planState);
|
|
3129
|
+
scheduleSessionSave();
|
|
3130
|
+
},
|
|
3131
|
+
backupManager
|
|
3132
|
+
});
|
|
3112
3133
|
|
|
3113
3134
|
const filteredDefinitions = Array.isArray(allowedTools)
|
|
3114
3135
|
? definitions.filter((t) => allowedTools.includes(t.function?.name || t.name))
|
|
@@ -3147,38 +3168,45 @@ async function askModel({
|
|
|
3147
3168
|
|
|
3148
3169
|
let activeAssistantIndex = -1;
|
|
3149
3170
|
const pendingToolMeta = new Map();
|
|
3150
|
-
const normalizeFileChange = (change) => {
|
|
3151
|
-
if (!change || typeof change !== 'object') return null;
|
|
3152
|
-
const path = String(change.path || '').trim();
|
|
3153
|
-
if (!path) return null;
|
|
3154
|
-
const action = String(change.action || '').trim();
|
|
3155
|
-
return {
|
|
3156
|
-
path,
|
|
3157
|
-
action: action === 'create' || action === 'delete' ? action : 'edit',
|
|
3171
|
+
const normalizeFileChange = (change) => {
|
|
3172
|
+
if (!change || typeof change !== 'object') return null;
|
|
3173
|
+
const path = String(change.path || '').trim();
|
|
3174
|
+
if (!path) return null;
|
|
3175
|
+
const action = String(change.action || '').trim();
|
|
3176
|
+
return {
|
|
3177
|
+
path,
|
|
3178
|
+
action: action === 'create' || action === 'delete' ? action : 'edit',
|
|
3158
3179
|
linesAdded: Number(change.linesAdded || 0),
|
|
3159
3180
|
linesRemoved: Number(change.linesRemoved || 0),
|
|
3160
3181
|
changedLine: Number(change.changedLine || 0),
|
|
3161
|
-
diffPreview: String(change.diffPreview || '')
|
|
3182
|
+
diffPreview: String(change.diffPreview || ''),
|
|
3183
|
+
changeSetId: String(change.changeSetId || ''),
|
|
3184
|
+
patchRef: String(change.patchRef || '')
|
|
3162
3185
|
};
|
|
3163
3186
|
};
|
|
3164
|
-
const fileChangeFingerprint = (change) => JSON.stringify({
|
|
3165
|
-
path: change.path,
|
|
3166
|
-
action: change.action,
|
|
3167
|
-
linesAdded: Number(change.linesAdded || 0),
|
|
3168
|
-
linesRemoved: Number(change.linesRemoved || 0),
|
|
3169
|
-
changedLine: Number(change.changedLine || 0),
|
|
3170
|
-
diffPreview: String(change.diffPreview || '')
|
|
3187
|
+
const fileChangeFingerprint = (change) => JSON.stringify({
|
|
3188
|
+
path: change.path,
|
|
3189
|
+
action: change.action,
|
|
3190
|
+
linesAdded: Number(change.linesAdded || 0),
|
|
3191
|
+
linesRemoved: Number(change.linesRemoved || 0),
|
|
3192
|
+
changedLine: Number(change.changedLine || 0),
|
|
3193
|
+
diffPreview: String(change.diffPreview || ''),
|
|
3194
|
+
changeSetId: String(change.changeSetId || ''),
|
|
3195
|
+
patchRef: String(change.patchRef || '')
|
|
3171
3196
|
});
|
|
3172
|
-
const
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3197
|
+
const normalizeFileChanges = (changes) => (Array.isArray(changes) ? changes : [changes])
|
|
3198
|
+
.map(normalizeFileChange)
|
|
3199
|
+
.filter(Boolean);
|
|
3200
|
+
const appendUniqueFileChange = (message, fileChange) => {
|
|
3201
|
+
const existing = Array.isArray(message.file_changes) ? message.file_changes : [];
|
|
3202
|
+
const nextKey = fileChangeFingerprint(fileChange);
|
|
3203
|
+
if (existing.some((change) => fileChangeFingerprint(normalizeFileChange(change) || {}) === nextKey)) {
|
|
3204
|
+
message.file_changes = existing;
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
message.file_changes = [...existing, fileChange];
|
|
3208
|
+
};
|
|
3209
|
+
const attachToolMetaToSessionCall = (toolId, meta = {}) => {
|
|
3182
3210
|
if (!toolId) return false;
|
|
3183
3211
|
for (let i = session.messages.length - 1; i >= 0; i -= 1) {
|
|
3184
3212
|
const msg = session.messages[i];
|
|
@@ -3188,11 +3216,14 @@ async function askModel({
|
|
|
3188
3216
|
if (Number.isFinite(Number(meta.durationMs))) call.durationMs = Number(meta.durationMs);
|
|
3189
3217
|
if (typeof meta.summary === 'string' && meta.summary.trim()) call.summary = meta.summary.trim();
|
|
3190
3218
|
if (typeof meta.status === 'string' && meta.status.trim()) call.status = meta.status.trim();
|
|
3219
|
+
if (meta.resultMeta && typeof meta.resultMeta === 'object') call.resultMeta = meta.resultMeta;
|
|
3191
3220
|
const fileChange = normalizeFileChange(meta.fileChange);
|
|
3192
3221
|
if (fileChange) {
|
|
3193
3222
|
call.fileChange = fileChange;
|
|
3194
|
-
appendUniqueFileChange(msg, fileChange);
|
|
3195
3223
|
}
|
|
3224
|
+
const fileChanges = normalizeFileChanges(meta.fileChanges && meta.fileChanges.length ? meta.fileChanges : fileChange);
|
|
3225
|
+
if (fileChanges.length) call.fileChanges = fileChanges;
|
|
3226
|
+
for (const change of fileChanges) appendUniqueFileChange(msg, change);
|
|
3196
3227
|
msg.at = new Date().toISOString();
|
|
3197
3228
|
return true;
|
|
3198
3229
|
}
|
|
@@ -3204,64 +3235,64 @@ async function askModel({
|
|
|
3204
3235
|
session.messages.push(stampedMessage('assistant', ''));
|
|
3205
3236
|
activeAssistantIndex = session.messages.length - 1;
|
|
3206
3237
|
if (persistSession) scheduleSessionSave();
|
|
3207
|
-
} else if (event?.type === 'assistant:delta') {
|
|
3208
|
-
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3209
|
-
const current = session.messages[activeAssistantIndex];
|
|
3210
|
-
const now = new Date();
|
|
3211
|
-
if (current.reasoning_started_at && !current.reasoning_ended_at) {
|
|
3212
|
-
current.reasoning_ended_at = now.toISOString();
|
|
3213
|
-
current.reasoning_duration_ms = Math.max(
|
|
3214
|
-
Number(current.reasoning_duration_ms || 0),
|
|
3215
|
-
Date.parse(current.reasoning_ended_at) - Date.parse(current.reasoning_started_at)
|
|
3216
|
-
);
|
|
3217
|
-
}
|
|
3218
|
-
current.content = `${current.content || ''}${event.text || ''}`;
|
|
3219
|
-
current.at = now.toISOString();
|
|
3220
|
-
if (persistSession) scheduleSessionSave();
|
|
3221
|
-
}
|
|
3222
|
-
} else if (event?.type === 'assistant:reasoning_delta') {
|
|
3223
|
-
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3224
|
-
const current = session.messages[activeAssistantIndex];
|
|
3225
|
-
const now = new Date();
|
|
3226
|
-
if (!current.reasoning_started_at) current.reasoning_started_at = now.toISOString();
|
|
3227
|
-
current.reasoning_content = `${current.reasoning_content || ''}${event.text || ''}`;
|
|
3228
|
-
current.reasoning_duration_ms = Math.max(
|
|
3229
|
-
0,
|
|
3230
|
-
now.getTime() - Date.parse(current.reasoning_started_at)
|
|
3231
|
-
);
|
|
3232
|
-
current.at = now.toISOString();
|
|
3233
|
-
if (persistSession) scheduleSessionSave();
|
|
3234
|
-
}
|
|
3235
|
-
} else if (event?.type === 'assistant:response') {
|
|
3236
|
-
const eventUsage = normalizeModelUsage(event.usage || event.assistantMessage?.usage);
|
|
3237
|
-
if (eventUsage) event.usage = eventUsage;
|
|
3238
|
-
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3239
|
-
const current = session.messages[activeAssistantIndex];
|
|
3240
|
-
const now = new Date();
|
|
3241
|
-
current.content = event.assistantMessage?.content ?? event.text ?? current.content;
|
|
3242
|
-
if (typeof event.assistantMessage?.reasoning_content === 'string' && event.assistantMessage.reasoning_content) {
|
|
3243
|
-
current.reasoning_content = event.assistantMessage.reasoning_content;
|
|
3244
|
-
}
|
|
3238
|
+
} else if (event?.type === 'assistant:delta') {
|
|
3239
|
+
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3240
|
+
const current = session.messages[activeAssistantIndex];
|
|
3241
|
+
const now = new Date();
|
|
3242
|
+
if (current.reasoning_started_at && !current.reasoning_ended_at) {
|
|
3243
|
+
current.reasoning_ended_at = now.toISOString();
|
|
3244
|
+
current.reasoning_duration_ms = Math.max(
|
|
3245
|
+
Number(current.reasoning_duration_ms || 0),
|
|
3246
|
+
Date.parse(current.reasoning_ended_at) - Date.parse(current.reasoning_started_at)
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3249
|
+
current.content = `${current.content || ''}${event.text || ''}`;
|
|
3250
|
+
current.at = now.toISOString();
|
|
3251
|
+
if (persistSession) scheduleSessionSave();
|
|
3252
|
+
}
|
|
3253
|
+
} else if (event?.type === 'assistant:reasoning_delta') {
|
|
3254
|
+
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3255
|
+
const current = session.messages[activeAssistantIndex];
|
|
3256
|
+
const now = new Date();
|
|
3257
|
+
if (!current.reasoning_started_at) current.reasoning_started_at = now.toISOString();
|
|
3258
|
+
current.reasoning_content = `${current.reasoning_content || ''}${event.text || ''}`;
|
|
3259
|
+
current.reasoning_duration_ms = Math.max(
|
|
3260
|
+
0,
|
|
3261
|
+
now.getTime() - Date.parse(current.reasoning_started_at)
|
|
3262
|
+
);
|
|
3263
|
+
current.at = now.toISOString();
|
|
3264
|
+
if (persistSession) scheduleSessionSave();
|
|
3265
|
+
}
|
|
3266
|
+
} else if (event?.type === 'assistant:response') {
|
|
3267
|
+
const eventUsage = normalizeModelUsage(event.usage || event.assistantMessage?.usage);
|
|
3268
|
+
if (eventUsage) event.usage = eventUsage;
|
|
3269
|
+
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3270
|
+
const current = session.messages[activeAssistantIndex];
|
|
3271
|
+
const now = new Date();
|
|
3272
|
+
current.content = event.assistantMessage?.content ?? event.text ?? current.content;
|
|
3273
|
+
if (typeof event.assistantMessage?.reasoning_content === 'string' && event.assistantMessage.reasoning_content) {
|
|
3274
|
+
current.reasoning_content = event.assistantMessage.reasoning_content;
|
|
3275
|
+
}
|
|
3245
3276
|
if (Array.isArray(event.assistantMessage?.reasoning_details) && event.assistantMessage.reasoning_details.length > 0) {
|
|
3246
3277
|
current.reasoning_details = event.assistantMessage.reasoning_details;
|
|
3247
3278
|
}
|
|
3248
|
-
if (Array.isArray(event.assistantMessage?.tool_calls) && event.assistantMessage.tool_calls.length > 0) {
|
|
3249
|
-
current.tool_calls = event.assistantMessage.tool_calls;
|
|
3250
|
-
}
|
|
3251
|
-
if (eventUsage) {
|
|
3252
|
-
current.usage = mergeModelUsage(current.usage, eventUsage);
|
|
3253
|
-
}
|
|
3254
|
-
if ((current.reasoning_content || current.reasoning_details) && current.reasoning_started_at) {
|
|
3255
|
-
current.reasoning_ended_at = current.reasoning_ended_at || now.toISOString();
|
|
3256
|
-
current.reasoning_duration_ms = Math.max(
|
|
3257
|
-
Number(current.reasoning_duration_ms || 0),
|
|
3258
|
-
Date.parse(current.reasoning_ended_at) - Date.parse(current.reasoning_started_at)
|
|
3259
|
-
);
|
|
3260
|
-
}
|
|
3261
|
-
current.at = now.toISOString();
|
|
3262
|
-
if (persistSession) scheduleSessionSave();
|
|
3263
|
-
} else {
|
|
3264
|
-
const assistantMessage = event.assistantMessage && typeof event.assistantMessage === 'object'
|
|
3279
|
+
if (Array.isArray(event.assistantMessage?.tool_calls) && event.assistantMessage.tool_calls.length > 0) {
|
|
3280
|
+
current.tool_calls = event.assistantMessage.tool_calls;
|
|
3281
|
+
}
|
|
3282
|
+
if (eventUsage) {
|
|
3283
|
+
current.usage = mergeModelUsage(current.usage, eventUsage);
|
|
3284
|
+
}
|
|
3285
|
+
if ((current.reasoning_content || current.reasoning_details) && current.reasoning_started_at) {
|
|
3286
|
+
current.reasoning_ended_at = current.reasoning_ended_at || now.toISOString();
|
|
3287
|
+
current.reasoning_duration_ms = Math.max(
|
|
3288
|
+
Number(current.reasoning_duration_ms || 0),
|
|
3289
|
+
Date.parse(current.reasoning_ended_at) - Date.parse(current.reasoning_started_at)
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
current.at = now.toISOString();
|
|
3293
|
+
if (persistSession) scheduleSessionSave();
|
|
3294
|
+
} else {
|
|
3295
|
+
const assistantMessage = event.assistantMessage && typeof event.assistantMessage === 'object'
|
|
3265
3296
|
? event.assistantMessage
|
|
3266
3297
|
: { content: event.text || '' };
|
|
3267
3298
|
session.messages.push(stampedMessage('assistant', assistantMessage.content || event.text || '', {
|
|
@@ -3271,38 +3302,40 @@ async function askModel({
|
|
|
3271
3302
|
...(Array.isArray(assistantMessage.reasoning_details) && assistantMessage.reasoning_details.length > 0
|
|
3272
3303
|
? { reasoning_details: assistantMessage.reasoning_details }
|
|
3273
3304
|
: {}),
|
|
3274
|
-
...(Array.isArray(assistantMessage.tool_calls) && assistantMessage.tool_calls.length > 0
|
|
3275
|
-
? { tool_calls: assistantMessage.tool_calls }
|
|
3276
|
-
: {}),
|
|
3277
|
-
...(eventUsage ? { usage: eventUsage } : {})
|
|
3278
|
-
}));
|
|
3279
|
-
if (persistSession) scheduleSessionSave();
|
|
3280
|
-
}
|
|
3305
|
+
...(Array.isArray(assistantMessage.tool_calls) && assistantMessage.tool_calls.length > 0
|
|
3306
|
+
? { tool_calls: assistantMessage.tool_calls }
|
|
3307
|
+
: {}),
|
|
3308
|
+
...(eventUsage ? { usage: eventUsage } : {})
|
|
3309
|
+
}));
|
|
3310
|
+
if (persistSession) scheduleSessionSave();
|
|
3311
|
+
}
|
|
3281
3312
|
activeAssistantIndex = -1;
|
|
3282
|
-
} else if (event?.type === 'tool:start') {
|
|
3283
|
-
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3284
|
-
const current = session.messages[activeAssistantIndex];
|
|
3285
|
-
const now = new Date();
|
|
3286
|
-
if (current.reasoning_started_at && !current.reasoning_ended_at) {
|
|
3287
|
-
current.reasoning_ended_at = now.toISOString();
|
|
3288
|
-
current.reasoning_duration_ms = Math.max(
|
|
3289
|
-
Number(current.reasoning_duration_ms || 0),
|
|
3290
|
-
Date.parse(current.reasoning_ended_at) - Date.parse(current.reasoning_started_at)
|
|
3291
|
-
);
|
|
3292
|
-
current.at = now.toISOString();
|
|
3293
|
-
if (persistSession) scheduleSessionSave();
|
|
3294
|
-
}
|
|
3295
|
-
}
|
|
3296
|
-
} else if (event?.type === 'tool:end' || event?.type === 'tool:error' || event?.type === 'tool:blocked') {
|
|
3313
|
+
} else if (event?.type === 'tool:start') {
|
|
3314
|
+
if (activeAssistantIndex >= 0 && session.messages[activeAssistantIndex]) {
|
|
3315
|
+
const current = session.messages[activeAssistantIndex];
|
|
3316
|
+
const now = new Date();
|
|
3317
|
+
if (current.reasoning_started_at && !current.reasoning_ended_at) {
|
|
3318
|
+
current.reasoning_ended_at = now.toISOString();
|
|
3319
|
+
current.reasoning_duration_ms = Math.max(
|
|
3320
|
+
Number(current.reasoning_duration_ms || 0),
|
|
3321
|
+
Date.parse(current.reasoning_ended_at) - Date.parse(current.reasoning_started_at)
|
|
3322
|
+
);
|
|
3323
|
+
current.at = now.toISOString();
|
|
3324
|
+
if (persistSession) scheduleSessionSave();
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
} else if (event?.type === 'tool:end' || event?.type === 'tool:error' || event?.type === 'tool:blocked') {
|
|
3297
3328
|
const toolId = String(event.id || '');
|
|
3298
3329
|
if (toolId) {
|
|
3299
3330
|
const meta = {
|
|
3300
3331
|
durationMs: Number.isFinite(Number(event.durationMs)) ? Number(event.durationMs) : undefined,
|
|
3301
3332
|
summary: typeof event.summary === 'string' ? event.summary : '',
|
|
3333
|
+
resultMeta: event.resultMeta && typeof event.resultMeta === 'object' ? event.resultMeta : null,
|
|
3302
3334
|
fileChange: normalizeFileChange(event.fileChange),
|
|
3335
|
+
fileChanges: normalizeFileChanges(event.fileChanges),
|
|
3303
3336
|
status:
|
|
3304
|
-
event.type === 'tool:error'
|
|
3305
|
-
? 'error'
|
|
3337
|
+
event.type === 'tool:error'
|
|
3338
|
+
? 'error'
|
|
3306
3339
|
: event.type === 'tool:blocked'
|
|
3307
3340
|
? 'blocked'
|
|
3308
3341
|
: 'done'
|
|
@@ -3319,9 +3352,11 @@ async function askModel({
|
|
|
3319
3352
|
...(Number.isFinite(Number(meta.durationMs)) ? { tool_duration_ms: Number(meta.durationMs) } : {}),
|
|
3320
3353
|
...(meta.summary ? { tool_summary: meta.summary } : {}),
|
|
3321
3354
|
...(meta.status ? { tool_status: meta.status } : {}),
|
|
3322
|
-
...(meta.
|
|
3355
|
+
...(meta.resultMeta ? { tool_result_meta: meta.resultMeta } : {}),
|
|
3356
|
+
...(meta.fileChange ? { tool_file_change: meta.fileChange } : {}),
|
|
3357
|
+
...(Array.isArray(meta.fileChanges) && meta.fileChanges.length ? { tool_file_changes: meta.fileChanges } : {})
|
|
3323
3358
|
})
|
|
3324
|
-
);
|
|
3359
|
+
);
|
|
3325
3360
|
pendingToolMeta.delete(toolId);
|
|
3326
3361
|
if (persistSession) scheduleSessionSave();
|
|
3327
3362
|
}
|
|
@@ -3341,17 +3376,25 @@ async function askModel({
|
|
|
3341
3376
|
toolHandlers: filteredHandlers,
|
|
3342
3377
|
initialMessages: toOpenAIMessages(compacted ?? session.messages),
|
|
3343
3378
|
onEvent: wrappedAgentEvent,
|
|
3344
|
-
executionMode: executionMode || config.execution?.mode || 'normal',
|
|
3345
|
-
|
|
3379
|
+
executionMode: executionMode || config.execution?.mode || 'normal',
|
|
3380
|
+
approvalMode: config.execution?.approval_mode || 'review',
|
|
3381
|
+
projectIsGit: Boolean(projectIsGit || changeTracker?.enabled),
|
|
3382
|
+
alwaysAllowTools:
|
|
3346
3383
|
alwaysAllowTools || config.execution?.always_allow_tools || ['run', 'read', 'write'],
|
|
3347
3384
|
toolResultMaxChars: config.context?.tool_result_max_chars || 12000,
|
|
3348
3385
|
toolFormatters: formatters,
|
|
3349
3386
|
deferredDefinitions: filteredDeferred,
|
|
3350
3387
|
requestToolApproval,
|
|
3351
|
-
signal,
|
|
3352
|
-
skipAnalysisNudge,
|
|
3353
|
-
config,
|
|
3354
|
-
|
|
3388
|
+
signal,
|
|
3389
|
+
skipAnalysisNudge,
|
|
3390
|
+
config,
|
|
3391
|
+
changeTracker: changeTracker?.enabled
|
|
3392
|
+
? {
|
|
3393
|
+
begin: (meta) => beginGitOplogCapture(changeTracker, meta),
|
|
3394
|
+
capture: (scope, meta) => captureGitOplogChanges(changeTracker, scope, meta)
|
|
3395
|
+
}
|
|
3396
|
+
: null,
|
|
3397
|
+
requestCompletion: async ({ messages, tools, model: selectedModel }) => {
|
|
3355
3398
|
let started = false;
|
|
3356
3399
|
const startAssistantStream = () => {
|
|
3357
3400
|
if (!started) {
|
|
@@ -3370,15 +3413,15 @@ async function askModel({
|
|
|
3370
3413
|
timeoutMs: config.gateway.timeout_ms || 1800000,
|
|
3371
3414
|
maxRetries: config.gateway.max_retries ?? 2,
|
|
3372
3415
|
signal,
|
|
3373
|
-
onTextDelta: (delta) => {
|
|
3374
|
-
startAssistantStream();
|
|
3375
|
-
wrappedAgentEvent({ type: 'assistant:delta', text: delta });
|
|
3376
|
-
},
|
|
3377
|
-
onReasoningDelta: (delta) => {
|
|
3378
|
-
startAssistantStream();
|
|
3379
|
-
wrappedAgentEvent({ type: 'assistant:reasoning_delta', text: delta });
|
|
3380
|
-
},
|
|
3381
|
-
onToolCallDelta: (toolCall) => {
|
|
3416
|
+
onTextDelta: (delta) => {
|
|
3417
|
+
startAssistantStream();
|
|
3418
|
+
wrappedAgentEvent({ type: 'assistant:delta', text: delta });
|
|
3419
|
+
},
|
|
3420
|
+
onReasoningDelta: (delta) => {
|
|
3421
|
+
startAssistantStream();
|
|
3422
|
+
wrappedAgentEvent({ type: 'assistant:reasoning_delta', text: delta });
|
|
3423
|
+
},
|
|
3424
|
+
onToolCallDelta: (toolCall) => {
|
|
3382
3425
|
startAssistantStream();
|
|
3383
3426
|
wrappedAgentEvent({ type: 'assistant:tool_call_delta', toolCall });
|
|
3384
3427
|
}
|
|
@@ -3505,7 +3548,7 @@ async function runSubAgentTask({
|
|
|
3505
3548
|
}
|
|
3506
3549
|
if (
|
|
3507
3550
|
role !== 'summarizer' &&
|
|
3508
|
-
['assistant:start', 'assistant:delta', 'assistant:reasoning_delta', 'assistant:response', 'assistant:tool_call_delta'].includes(String(evt?.type || ''))
|
|
3551
|
+
['assistant:start', 'assistant:delta', 'assistant:reasoning_delta', 'assistant:response', 'assistant:tool_call_delta'].includes(String(evt?.type || ''))
|
|
3509
3552
|
) {
|
|
3510
3553
|
return;
|
|
3511
3554
|
}
|
|
@@ -3528,7 +3571,7 @@ async function runSubAgentTask({
|
|
|
3528
3571
|
systemPrompt: subSystemPrompt,
|
|
3529
3572
|
onAgentEvent: wrappedOnAgentEvent,
|
|
3530
3573
|
persistSession: false,
|
|
3531
|
-
executionMode: '
|
|
3574
|
+
executionMode: 'normal',
|
|
3532
3575
|
allowedTools: roleAllowedTools,
|
|
3533
3576
|
skipAnalysisNudge: true,
|
|
3534
3577
|
signal
|
|
@@ -3545,18 +3588,18 @@ async function runSubAgentTask({
|
|
|
3545
3588
|
};
|
|
3546
3589
|
}
|
|
3547
3590
|
|
|
3548
|
-
function buildPlanStepTranscript({ stepRecord, stepIndex, totalSteps, messages }) {
|
|
3549
|
-
const toolCardsById = new Map();
|
|
3550
|
-
const toolCards = [];
|
|
3551
|
-
const source = Array.isArray(messages) ? messages : [];
|
|
3552
|
-
let usage = null;
|
|
3553
|
-
|
|
3554
|
-
for (const msg of source) {
|
|
3555
|
-
if (msg?.role === 'assistant' && msg.usage) {
|
|
3556
|
-
usage = mergeModelUsage(usage, msg.usage);
|
|
3557
|
-
}
|
|
3558
|
-
if (msg?.role === 'assistant' && Array.isArray(msg.tool_calls)) {
|
|
3559
|
-
for (const tc of msg.tool_calls) {
|
|
3591
|
+
function buildPlanStepTranscript({ stepRecord, stepIndex, totalSteps, messages }) {
|
|
3592
|
+
const toolCardsById = new Map();
|
|
3593
|
+
const toolCards = [];
|
|
3594
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
3595
|
+
let usage = null;
|
|
3596
|
+
|
|
3597
|
+
for (const msg of source) {
|
|
3598
|
+
if (msg?.role === 'assistant' && msg.usage) {
|
|
3599
|
+
usage = mergeModelUsage(usage, msg.usage);
|
|
3600
|
+
}
|
|
3601
|
+
if (msg?.role === 'assistant' && Array.isArray(msg.tool_calls)) {
|
|
3602
|
+
for (const tc of msg.tool_calls) {
|
|
3560
3603
|
const id = String(tc?.id || `tool-${toolCards.length + 1}`);
|
|
3561
3604
|
if (toolCardsById.has(id)) continue;
|
|
3562
3605
|
const card = {
|
|
@@ -3595,12 +3638,12 @@ function buildPlanStepTranscript({ stepRecord, stepIndex, totalSteps, messages }
|
|
|
3595
3638
|
total: totalSteps,
|
|
3596
3639
|
role: stepRecord.role || 'general',
|
|
3597
3640
|
title: stepRecord.title || '',
|
|
3598
|
-
status: stepRecord.failed ? 'failed' : 'done',
|
|
3599
|
-
summary: stepRecord.failed ? stepRecord.failureReason : trimInline(stepRecord.output || '', 160),
|
|
3600
|
-
segments,
|
|
3601
|
-
...(usage ? { usage } : {})
|
|
3602
|
-
};
|
|
3603
|
-
}
|
|
3641
|
+
status: stepRecord.failed ? 'failed' : 'done',
|
|
3642
|
+
summary: stepRecord.failed ? stepRecord.failureReason : trimInline(stepRecord.output || '', 160),
|
|
3643
|
+
segments,
|
|
3644
|
+
...(usage ? { usage } : {})
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3604
3647
|
|
|
3605
3648
|
async function executePlanWithSubAgents({
|
|
3606
3649
|
planState,
|
|
@@ -4013,13 +4056,13 @@ function renderProjectRequirementsSectionContract(ignoredSections = []) {
|
|
|
4013
4056
|
return lines.join('\n');
|
|
4014
4057
|
}
|
|
4015
4058
|
|
|
4016
|
-
function buildProjectRequirementsSteps(renderedSkillPrompt, args = [], config = {}, reportSlug = formatLocalDateTimeSlug()) {
|
|
4017
|
-
const options = parseProjectRequirementsOptions(args);
|
|
4018
|
-
const userArgs = options.raw;
|
|
4019
|
-
const requestedFocus = userArgs ? `User request/focus: ${userArgs}` : 'User request/focus: full workspace requirements report.';
|
|
4020
|
-
const replyLanguageName = getReplyLanguageName(config);
|
|
4021
|
-
const reportPath = `docs/requirements/${reportSlug}-project-requirements.html`;
|
|
4022
|
-
const companionPath = `docs/requirements/${reportSlug}-project-requirements.md`;
|
|
4059
|
+
function buildProjectRequirementsSteps(renderedSkillPrompt, args = [], config = {}, reportSlug = formatLocalDateTimeSlug()) {
|
|
4060
|
+
const options = parseProjectRequirementsOptions(args);
|
|
4061
|
+
const userArgs = options.raw;
|
|
4062
|
+
const requestedFocus = userArgs ? `User request/focus: ${userArgs}` : 'User request/focus: full workspace requirements report.';
|
|
4063
|
+
const replyLanguageName = getReplyLanguageName(config);
|
|
4064
|
+
const reportPath = `docs/requirements/${reportSlug}-project-requirements.html`;
|
|
4065
|
+
const companionPath = `docs/requirements/${reportSlug}-project-requirements.md`;
|
|
4023
4066
|
const reportContract = [
|
|
4024
4067
|
requestedFocus,
|
|
4025
4068
|
`Reply language: write generated report prose, UI labels inserted into the report, review notes, and final user-facing status in ${replyLanguageName} unless the user explicitly requested a different language. Do not translate REQUIREMENTS_* marker names or source code identifiers.`,
|
|
@@ -4341,11 +4384,11 @@ async function runProjectRequirementsPipeline({
|
|
|
4341
4384
|
const options = parseProjectRequirementsOptions(parsedInput.args);
|
|
4342
4385
|
const userFocus = options.raw;
|
|
4343
4386
|
const goal = userFocus ? `project requirements report: ${userFocus}` : 'project requirements report';
|
|
4344
|
-
const reportSlug = formatLocalDateTimeSlug();
|
|
4345
|
-
const reportPath = `docs/requirements/${reportSlug}-project-requirements.html`;
|
|
4346
|
-
const companionPath = `docs/requirements/${reportSlug}-project-requirements.md`;
|
|
4347
|
-
const manifestPath = `docs/requirements/${reportSlug}-project-requirements.manifest.json`;
|
|
4348
|
-
const steps = buildProjectRequirementsSteps(renderedSkillPrompt, parsedInput.args, config, reportSlug);
|
|
4387
|
+
const reportSlug = formatLocalDateTimeSlug();
|
|
4388
|
+
const reportPath = `docs/requirements/${reportSlug}-project-requirements.html`;
|
|
4389
|
+
const companionPath = `docs/requirements/${reportSlug}-project-requirements.md`;
|
|
4390
|
+
const manifestPath = `docs/requirements/${reportSlug}-project-requirements.manifest.json`;
|
|
4391
|
+
const steps = buildProjectRequirementsSteps(renderedSkillPrompt, parsedInput.args, config, reportSlug);
|
|
4349
4392
|
const planFile = await writeMarkdownInProjectDir(
|
|
4350
4393
|
'plans',
|
|
4351
4394
|
'project-requirements-pipeline',
|
|
@@ -4655,10 +4698,10 @@ export async function createChatRuntime({
|
|
|
4655
4698
|
currentSession.model = model;
|
|
4656
4699
|
}
|
|
4657
4700
|
const baseSystemPrompt = systemPrompt;
|
|
4658
|
-
let executionMode = config.execution?.mode || 'normal';
|
|
4659
|
-
if (hasPendingPlanApproval(currentSession)) {
|
|
4660
|
-
executionMode = 'plan';
|
|
4661
|
-
}
|
|
4701
|
+
let executionMode = config.execution?.mode || 'normal';
|
|
4702
|
+
if (hasPendingPlanApproval(currentSession)) {
|
|
4703
|
+
executionMode = 'plan';
|
|
4704
|
+
}
|
|
4662
4705
|
let compactState = null;
|
|
4663
4706
|
const normalizeCompactThreshold = (value, fallback = 60) => {
|
|
4664
4707
|
const num = Number(value);
|
|
@@ -4670,10 +4713,10 @@ export async function createChatRuntime({
|
|
|
4670
4713
|
compactState.threshold = normalizeCompactThreshold(config.context?.preflight_trigger_pct, 60);
|
|
4671
4714
|
};
|
|
4672
4715
|
const syncRuntimeFromConfig = async ({ model: nextModel } = {}) => {
|
|
4673
|
-
const configuredMode = String(config.execution?.mode || 'normal');
|
|
4674
|
-
executionMode = hasPendingPlanApproval(currentSession)
|
|
4675
|
-
? 'plan'
|
|
4676
|
-
: (['normal', '
|
|
4716
|
+
const configuredMode = String(config.execution?.mode || 'normal');
|
|
4717
|
+
executionMode = hasPendingPlanApproval(currentSession)
|
|
4718
|
+
? 'plan'
|
|
4719
|
+
: (['normal', 'plan'].includes(configuredMode) ? configuredMode : 'normal');
|
|
4677
4720
|
syncCompactStateFromConfig();
|
|
4678
4721
|
|
|
4679
4722
|
const resolvedModel = String(nextModel || '').trim();
|
|
@@ -4686,15 +4729,29 @@ export async function createChatRuntime({
|
|
|
4686
4729
|
}
|
|
4687
4730
|
};
|
|
4688
4731
|
const commands = await loadCommandsAndSkills();
|
|
4689
|
-
const reloadCommandsAndSkills = async () => {
|
|
4690
|
-
const next = await loadCommandsAndSkills();
|
|
4691
|
-
commands.clear();
|
|
4692
|
-
for (const [name, command] of next.entries()) {
|
|
4693
|
-
commands.set(name, command);
|
|
4694
|
-
}
|
|
4695
|
-
};
|
|
4696
|
-
|
|
4697
|
-
|
|
4732
|
+
const reloadCommandsAndSkills = async () => {
|
|
4733
|
+
const next = await loadCommandsAndSkills();
|
|
4734
|
+
commands.clear();
|
|
4735
|
+
for (const [name, command] of next.entries()) {
|
|
4736
|
+
commands.set(name, command);
|
|
4737
|
+
}
|
|
4738
|
+
};
|
|
4739
|
+
let changeTracker = await createGitOplogChangeTracker({
|
|
4740
|
+
workspaceRoot: process.cwd(),
|
|
4741
|
+
sessionId: currentSession.id
|
|
4742
|
+
});
|
|
4743
|
+
let backupManager = changeTracker?.enabled
|
|
4744
|
+
? null
|
|
4745
|
+
: await createNonGitBackupManager({
|
|
4746
|
+
workspaceRoot: process.cwd(),
|
|
4747
|
+
sessionId: currentSession.id
|
|
4748
|
+
}).catch(() => null);
|
|
4749
|
+
config.runtime = {
|
|
4750
|
+
...(config.runtime || {}),
|
|
4751
|
+
project_is_git: Boolean(changeTracker?.enabled)
|
|
4752
|
+
};
|
|
4753
|
+
|
|
4754
|
+
// Set up tool result store under session directory
|
|
4698
4755
|
const sessionResultsDir = path.join(getSessionsDir(), String(currentSession.id));
|
|
4699
4756
|
setResultDir(sessionResultsDir);
|
|
4700
4757
|
compactState = {
|
|
@@ -4762,7 +4819,8 @@ export async function createChatRuntime({
|
|
|
4762
4819
|
'model.fast_name',
|
|
4763
4820
|
'ui.language',
|
|
4764
4821
|
'ui.reply_language',
|
|
4765
|
-
'execution.mode',
|
|
4822
|
+
'execution.mode',
|
|
4823
|
+
'execution.approval_mode',
|
|
4766
4824
|
'shell.default',
|
|
4767
4825
|
'sdk.provider',
|
|
4768
4826
|
'gateway.timeout_ms',
|
|
@@ -4798,9 +4856,10 @@ export async function createChatRuntime({
|
|
|
4798
4856
|
'/memory',
|
|
4799
4857
|
'/capture',
|
|
4800
4858
|
'/inbox',
|
|
4801
|
-
'/dream',
|
|
4802
|
-
'/mode',
|
|
4803
|
-
'/
|
|
4859
|
+
'/dream',
|
|
4860
|
+
'/mode',
|
|
4861
|
+
'/approval',
|
|
4862
|
+
'/plan',
|
|
4804
4863
|
'/history',
|
|
4805
4864
|
'/checkpoint',
|
|
4806
4865
|
'/agents',
|
|
@@ -4819,7 +4878,8 @@ export async function createChatRuntime({
|
|
|
4819
4878
|
{ name: 'commands', description: completionCopy.commands.commands },
|
|
4820
4879
|
{ name: 'status', description: completionCopy.commands.status },
|
|
4821
4880
|
{ name: 'model', description: completionCopy.commands.model },
|
|
4822
|
-
{ name: 'mode', description: completionCopy.commands.mode },
|
|
4881
|
+
{ name: 'mode', description: completionCopy.commands.mode },
|
|
4882
|
+
{ name: 'approval', description: completionCopy.commands.approval },
|
|
4823
4883
|
{ name: 'compact', description: completionCopy.commands.compact },
|
|
4824
4884
|
{ name: 'checkpoint', description: completionCopy.commands.checkpoint },
|
|
4825
4885
|
{ name: 'spec', description: completionCopy.commands.spec },
|
|
@@ -4869,7 +4929,8 @@ export async function createChatRuntime({
|
|
|
4869
4929
|
|
|
4870
4930
|
const historyTemplates = ['/history list', '/history current', '/history resume <session_id>'];
|
|
4871
4931
|
const memoryTemplates = ['/memory list <scope>', '/memory search <scope> <query>', '/memory forget <scope> <id>'];
|
|
4872
|
-
const modeTemplates = ['/mode normal', '/mode
|
|
4932
|
+
const modeTemplates = ['/mode normal', '/mode plan'];
|
|
4933
|
+
const approvalTemplates = ['/approval review', '/approval auto', '/approval full_access'];
|
|
4873
4934
|
const modelTemplates = ['/model current', '/model main', '/model fast', '/model set <name>'];
|
|
4874
4935
|
const checkpointTemplates = [
|
|
4875
4936
|
'/checkpoint create <name>',
|
|
@@ -4888,7 +4949,8 @@ export async function createChatRuntime({
|
|
|
4888
4949
|
...configTemplates,
|
|
4889
4950
|
...memoryTemplates,
|
|
4890
4951
|
...historyTemplates,
|
|
4891
|
-
...modeTemplates,
|
|
4952
|
+
...modeTemplates,
|
|
4953
|
+
...approvalTemplates,
|
|
4892
4954
|
...modelTemplates,
|
|
4893
4955
|
...checkpointTemplates,
|
|
4894
4956
|
...specTemplates,
|
|
@@ -4936,7 +4998,8 @@ export async function createChatRuntime({
|
|
|
4936
4998
|
'config',
|
|
4937
4999
|
'memory',
|
|
4938
5000
|
'compact',
|
|
4939
|
-
'mode',
|
|
5001
|
+
'mode',
|
|
5002
|
+
'approval',
|
|
4940
5003
|
'model',
|
|
4941
5004
|
'checkpoint',
|
|
4942
5005
|
'plan',
|
|
@@ -4956,7 +5019,8 @@ export async function createChatRuntime({
|
|
|
4956
5019
|
}
|
|
4957
5020
|
for (const template of memoryTemplates) registerSuggestion(template, completionCopy.generic.memoryCommand);
|
|
4958
5021
|
for (const template of historyTemplates) registerSuggestion(template, completionCopy.generic.historyCommand);
|
|
4959
|
-
for (const template of modeTemplates) registerSuggestion(template, completionCopy.generic.modeCommand);
|
|
5022
|
+
for (const template of modeTemplates) registerSuggestion(template, completionCopy.generic.modeCommand);
|
|
5023
|
+
for (const template of approvalTemplates) registerSuggestion(template, completionCopy.generic.approvalCommand);
|
|
4960
5024
|
for (const template of modelTemplates) registerSuggestion(template, completionCopy.generic.modelCommand || completionCopy.commands.model);
|
|
4961
5025
|
for (const template of checkpointTemplates) registerSuggestion(template, completionCopy.generic.checkpointCommand);
|
|
4962
5026
|
for (const template of specTemplates) registerSuggestion(template, completionCopy.generic.specCommand);
|
|
@@ -5061,15 +5125,24 @@ export async function createChatRuntime({
|
|
|
5061
5125
|
}
|
|
5062
5126
|
return materializeSuggestions(modelTemplates);
|
|
5063
5127
|
}
|
|
5064
|
-
if (commandPart === 'mode') {
|
|
5065
|
-
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
5066
|
-
const sub = tokens[1] || '';
|
|
5067
|
-
return ['normal', '
|
|
5068
|
-
.filter((m) => m.startsWith(sub))
|
|
5069
|
-
.map((m) => registerSuggestion(`/mode ${m}`, completionCopy.generic.modeCommand));
|
|
5070
|
-
}
|
|
5071
|
-
return materializeSuggestions(modeTemplates);
|
|
5072
|
-
}
|
|
5128
|
+
if (commandPart === 'mode') {
|
|
5129
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
5130
|
+
const sub = tokens[1] || '';
|
|
5131
|
+
return ['normal', 'plan']
|
|
5132
|
+
.filter((m) => m.startsWith(sub))
|
|
5133
|
+
.map((m) => registerSuggestion(`/mode ${m}`, completionCopy.generic.modeCommand));
|
|
5134
|
+
}
|
|
5135
|
+
return materializeSuggestions(modeTemplates);
|
|
5136
|
+
}
|
|
5137
|
+
if (commandPart === 'approval') {
|
|
5138
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
5139
|
+
const sub = tokens[1] || '';
|
|
5140
|
+
return ['review', 'auto', 'full_access']
|
|
5141
|
+
.filter((m) => m.startsWith(sub))
|
|
5142
|
+
.map((m) => registerSuggestion(`/approval ${m}`, completionCopy.generic.approvalCommand));
|
|
5143
|
+
}
|
|
5144
|
+
return materializeSuggestions(approvalTemplates);
|
|
5145
|
+
}
|
|
5073
5146
|
if (commandPart === 'checkpoint') {
|
|
5074
5147
|
if (tokens.length <= 2 && !hasTrailingSpace) {
|
|
5075
5148
|
const sub = tokens[1] || '';
|
|
@@ -5384,10 +5457,10 @@ export async function createChatRuntime({
|
|
|
5384
5457
|
// 每次提交创建新的 AbortController,替代旧的
|
|
5385
5458
|
activeAbortController = new AbortController();
|
|
5386
5459
|
const { signal } = activeAbortController;
|
|
5387
|
-
const activeReplySystemPrompt = await buildActiveSystemPrompt();
|
|
5388
|
-
const parsedInput = parseInput(line);
|
|
5389
|
-
const readOnlyCodeWiki = options?.readOnlyCodeWiki === true;
|
|
5390
|
-
const codeWikiGenerate = options?.codeWikiGenerate === true;
|
|
5460
|
+
const activeReplySystemPrompt = await buildActiveSystemPrompt();
|
|
5461
|
+
const parsedInput = parseInput(line);
|
|
5462
|
+
const readOnlyCodeWiki = options?.readOnlyCodeWiki === true;
|
|
5463
|
+
const codeWikiGenerate = options?.codeWikiGenerate === true;
|
|
5391
5464
|
const maybeAutoDreamFromRuntime = async () => {
|
|
5392
5465
|
const threshold = Number(config?.memory?.auto_dream_threshold ?? 10);
|
|
5393
5466
|
if (!(threshold > 0)) return null;
|
|
@@ -5421,7 +5494,7 @@ export async function createChatRuntime({
|
|
|
5421
5494
|
}
|
|
5422
5495
|
};
|
|
5423
5496
|
try {
|
|
5424
|
-
if (!readOnlyCodeWiki && !codeWikiGenerate && shouldPersistInputHistory(parsedInput)) {
|
|
5497
|
+
if (!readOnlyCodeWiki && !codeWikiGenerate && shouldPersistInputHistory(parsedInput)) {
|
|
5425
5498
|
await appendInputHistory(line);
|
|
5426
5499
|
}
|
|
5427
5500
|
} catch {
|
|
@@ -5449,7 +5522,7 @@ export async function createChatRuntime({
|
|
|
5449
5522
|
systemPrompt: readOnlySystemPrompt,
|
|
5450
5523
|
onAgentEvent,
|
|
5451
5524
|
requestToolApproval: activeRequestToolApproval,
|
|
5452
|
-
executionMode: '
|
|
5525
|
+
executionMode: 'normal',
|
|
5453
5526
|
alwaysAllowTools: CODEWIKI_READ_ONLY_TOOLS,
|
|
5454
5527
|
allowedTools: CODEWIKI_READ_ONLY_TOOLS,
|
|
5455
5528
|
persistSession: false,
|
|
@@ -5488,13 +5561,13 @@ export async function createChatRuntime({
|
|
|
5488
5561
|
text: 'Commands: /help /exit /new /stop /commands /status /model /mode /compact /checkpoint /spec /plan /yes /no /edit /reject /agents /config /memory /capture /inbox /dream /reflect /history /debug /retry /<custom> !<shell>'
|
|
5489
5562
|
};
|
|
5490
5563
|
}
|
|
5491
|
-
if (parsedInput.command === 'status') {
|
|
5492
|
-
const todoCount = countActiveTodos(currentSession.todos);
|
|
5493
|
-
return {
|
|
5494
|
-
type: 'system',
|
|
5495
|
-
text: `mode=${executionMode} | role=general | model=${model || config.model.name} | max_ctx=${effectiveMaxContextTokens(config)} | session=${currentSession.id} | todos=${todoCount}`
|
|
5496
|
-
};
|
|
5497
|
-
}
|
|
5564
|
+
if (parsedInput.command === 'status') {
|
|
5565
|
+
const todoCount = countActiveTodos(currentSession.todos);
|
|
5566
|
+
return {
|
|
5567
|
+
type: 'system',
|
|
5568
|
+
text: `mode=${executionMode} | approval=${config.execution?.approval_mode || 'review'} | role=general | model=${model || config.model.name} | max_ctx=${effectiveMaxContextTokens(config)} | session=${currentSession.id} | todos=${todoCount}`
|
|
5569
|
+
};
|
|
5570
|
+
}
|
|
5498
5571
|
if (parsedInput.command === 'model') {
|
|
5499
5572
|
const sub = String(parsedInput.args[0] || 'current').trim().toLowerCase();
|
|
5500
5573
|
const mainModel = resolveDefaultModel(config);
|
|
@@ -5520,21 +5593,36 @@ export async function createChatRuntime({
|
|
|
5520
5593
|
await saveSession(currentSession);
|
|
5521
5594
|
return { type: 'system', text: `Model switched to: ${model}` };
|
|
5522
5595
|
}
|
|
5523
|
-
if (parsedInput.command === 'mode') {
|
|
5524
|
-
const next = (parsedInput.args[0] || '').trim().toLowerCase();
|
|
5525
|
-
if (!next) {
|
|
5526
|
-
return { type: 'system', text: `Current mode: ${executionMode} (available: normal|
|
|
5527
|
-
}
|
|
5528
|
-
if (!['normal', '
|
|
5529
|
-
return { type: 'system', text: 'Usage: /mode <normal|
|
|
5530
|
-
}
|
|
5531
|
-
executionMode = next;
|
|
5532
|
-
await setConfigValue('execution.mode', next);
|
|
5533
|
-
config = await loadConfig();
|
|
5534
|
-
const text = `
|
|
5535
|
-
await persistLocalExchange(line, text);
|
|
5536
|
-
return { type: 'system', text };
|
|
5537
|
-
}
|
|
5596
|
+
if (parsedInput.command === 'mode') {
|
|
5597
|
+
const next = (parsedInput.args[0] || '').trim().toLowerCase();
|
|
5598
|
+
if (!next) {
|
|
5599
|
+
return { type: 'system', text: `Current work mode: ${executionMode} (available: normal|plan)` };
|
|
5600
|
+
}
|
|
5601
|
+
if (!['normal', 'plan'].includes(next)) {
|
|
5602
|
+
return { type: 'system', text: 'Usage: /mode <normal|plan>' };
|
|
5603
|
+
}
|
|
5604
|
+
executionMode = next;
|
|
5605
|
+
await setConfigValue('execution.mode', next);
|
|
5606
|
+
config = await loadConfig();
|
|
5607
|
+
const text = `Work mode set to: ${next}`;
|
|
5608
|
+
await persistLocalExchange(line, text);
|
|
5609
|
+
return { type: 'system', text };
|
|
5610
|
+
}
|
|
5611
|
+
if (parsedInput.command === 'approval') {
|
|
5612
|
+
const raw = (parsedInput.args[0] || '').trim().toLowerCase().replace(/-/g, '_');
|
|
5613
|
+
const next = raw === 'full' ? 'full_access' : raw;
|
|
5614
|
+
if (!next) {
|
|
5615
|
+
return { type: 'system', text: `Current approval mode: ${config.execution?.approval_mode || 'review'} (available: review|auto|full_access)` };
|
|
5616
|
+
}
|
|
5617
|
+
if (!['review', 'auto', 'full_access'].includes(next)) {
|
|
5618
|
+
return { type: 'system', text: 'Usage: /approval <review|auto|full_access>' };
|
|
5619
|
+
}
|
|
5620
|
+
await setConfigValue('execution.approval_mode', next);
|
|
5621
|
+
config = await loadConfig();
|
|
5622
|
+
const text = `Approval mode set to: ${next}`;
|
|
5623
|
+
await persistLocalExchange(line, text);
|
|
5624
|
+
return { type: 'system', text };
|
|
5625
|
+
}
|
|
5538
5626
|
if (parsedInput.command === 'yes') {
|
|
5539
5627
|
if (hasPendingReflectSkill(currentSession)) {
|
|
5540
5628
|
const state = { ...currentSession.planState };
|
|
@@ -5551,7 +5639,7 @@ export async function createChatRuntime({
|
|
|
5551
5639
|
workspaceRoot: process.cwd()
|
|
5552
5640
|
});
|
|
5553
5641
|
currentSession.planState = null;
|
|
5554
|
-
executionMode = '
|
|
5642
|
+
executionMode = 'normal';
|
|
5555
5643
|
if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
|
|
5556
5644
|
await reloadCommandsAndSkills();
|
|
5557
5645
|
const text = `Reflect skill written and loaded: /${written.draft.name}\nPath: ${written.filePath}`;
|
|
@@ -5577,7 +5665,7 @@ export async function createChatRuntime({
|
|
|
5577
5665
|
currentSession.planState = null;
|
|
5578
5666
|
if (onAgentEvent) onAgentEvent({ type: 'plan:approval_cleared' });
|
|
5579
5667
|
await removePlanFileIfPresent(planState);
|
|
5580
|
-
executionMode = '
|
|
5668
|
+
executionMode = 'normal';
|
|
5581
5669
|
await persistAssistantExchange(line, result.sessionText || result.text || '', {
|
|
5582
5670
|
includeUser: false,
|
|
5583
5671
|
extra: Array.isArray(result.transcript) ? { plan_transcript: result.transcript } : {}
|
|
@@ -5652,7 +5740,7 @@ export async function createChatRuntime({
|
|
|
5652
5740
|
if (parsedInput.command === 'no') {
|
|
5653
5741
|
if (hasPendingReflectSkill(currentSession)) {
|
|
5654
5742
|
currentSession.planState = null;
|
|
5655
|
-
executionMode = '
|
|
5743
|
+
executionMode = 'normal';
|
|
5656
5744
|
if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
|
|
5657
5745
|
const text = 'Reflect skill draft discarded.';
|
|
5658
5746
|
await persistLocalExchange(line, text, { includeUser: false });
|
|
@@ -5661,7 +5749,7 @@ export async function createChatRuntime({
|
|
|
5661
5749
|
if (hasPendingPlanApproval(currentSession)) {
|
|
5662
5750
|
currentSession.planState = null;
|
|
5663
5751
|
if (onAgentEvent) onAgentEvent({ type: 'plan:approval_cleared' });
|
|
5664
|
-
executionMode = '
|
|
5752
|
+
executionMode = 'normal';
|
|
5665
5753
|
const text = 'Pending plan rejected and cleared.';
|
|
5666
5754
|
await persistLocalExchange(line, text, { includeUser: false });
|
|
5667
5755
|
return { type: 'system', text };
|
|
@@ -5676,7 +5764,7 @@ export async function createChatRuntime({
|
|
|
5676
5764
|
currentSession.planState = null;
|
|
5677
5765
|
if (onAgentEvent) onAgentEvent({ type: 'plan:approval_cleared' });
|
|
5678
5766
|
await removePlanFileIfPresent(planState);
|
|
5679
|
-
executionMode = '
|
|
5767
|
+
executionMode = 'normal';
|
|
5680
5768
|
const text = 'Pending plan rejected and cleared.';
|
|
5681
5769
|
await persistLocalExchange(line, text);
|
|
5682
5770
|
return { type: 'system', text };
|
|
@@ -5826,7 +5914,7 @@ export async function createChatRuntime({
|
|
|
5826
5914
|
currentSession.planState = null;
|
|
5827
5915
|
if (onAgentEvent) onAgentEvent({ type: 'plan:approval_cleared' });
|
|
5828
5916
|
await removePlanFileIfPresent(planState);
|
|
5829
|
-
executionMode = '
|
|
5917
|
+
executionMode = 'normal';
|
|
5830
5918
|
await persistAssistantExchange(line, result.sessionText || result.text || '', {
|
|
5831
5919
|
includeUser: false,
|
|
5832
5920
|
extra: Array.isArray(result.transcript) ? { plan_transcript: result.transcript } : {}
|
|
@@ -6273,14 +6361,14 @@ export async function createChatRuntime({
|
|
|
6273
6361
|
return { type: 'system', text: rows.join('\n') };
|
|
6274
6362
|
}
|
|
6275
6363
|
|
|
6276
|
-
let custom = commands.get(parsedInput.command);
|
|
6277
|
-
if (!custom) {
|
|
6278
|
-
await reloadCommandsAndSkills();
|
|
6279
|
-
custom = commands.get(parsedInput.command);
|
|
6280
|
-
if (!custom) {
|
|
6281
|
-
return { type: 'system', text: `Unknown slash command: /${parsedInput.command}` };
|
|
6282
|
-
}
|
|
6283
|
-
}
|
|
6364
|
+
let custom = commands.get(parsedInput.command);
|
|
6365
|
+
if (!custom) {
|
|
6366
|
+
await reloadCommandsAndSkills();
|
|
6367
|
+
custom = commands.get(parsedInput.command);
|
|
6368
|
+
if (!custom) {
|
|
6369
|
+
return { type: 'system', text: `Unknown slash command: /${parsedInput.command}` };
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6284
6372
|
if (custom.metadata.type === 'skill' && !isSkillEnabled(config, custom.name, custom)) {
|
|
6285
6373
|
return { type: 'system', text: `Skill is disabled: ${custom.name}` };
|
|
6286
6374
|
}
|
|
@@ -6373,7 +6461,7 @@ export async function createChatRuntime({
|
|
|
6373
6461
|
activeSubSession = null;
|
|
6374
6462
|
currentSession.planState = null;
|
|
6375
6463
|
if (onAgentEvent) onAgentEvent({ type: 'plan:approval_cleared' });
|
|
6376
|
-
executionMode = '
|
|
6464
|
+
executionMode = 'normal';
|
|
6377
6465
|
await persistAssistantExchange(line, result.sessionText || result.text || '', {
|
|
6378
6466
|
includeUser: false,
|
|
6379
6467
|
extra: Array.isArray(result.transcript) ? { plan_transcript: result.transcript } : {}
|
|
@@ -6388,7 +6476,7 @@ export async function createChatRuntime({
|
|
|
6388
6476
|
if (isRejectPlanText(parsedInput.text)) {
|
|
6389
6477
|
currentSession.planState = null;
|
|
6390
6478
|
if (onAgentEvent) onAgentEvent({ type: 'plan:approval_cleared' });
|
|
6391
|
-
executionMode = '
|
|
6479
|
+
executionMode = 'normal';
|
|
6392
6480
|
const text = 'Pending plan rejected and cleared.';
|
|
6393
6481
|
await persistLocalExchange(line, text);
|
|
6394
6482
|
return { type: 'system', text };
|
|
@@ -6582,11 +6670,13 @@ export async function createChatRuntime({
|
|
|
6582
6670
|
systemPrompt: routedSystemPrompt,
|
|
6583
6671
|
onAgentEvent,
|
|
6584
6672
|
requestToolApproval: activeRequestToolApproval,
|
|
6585
|
-
executionMode,
|
|
6586
|
-
signal,
|
|
6587
|
-
compactedForModel,
|
|
6588
|
-
onCompactedUpdate: setCompactedView
|
|
6589
|
-
|
|
6673
|
+
executionMode,
|
|
6674
|
+
signal,
|
|
6675
|
+
compactedForModel,
|
|
6676
|
+
onCompactedUpdate: setCompactedView,
|
|
6677
|
+
changeTracker,
|
|
6678
|
+
backupManager
|
|
6679
|
+
});
|
|
6590
6680
|
await saveDirectMemoryPrompt(expandedText);
|
|
6591
6681
|
await captureUserPromptForDream(expandedText);
|
|
6592
6682
|
return { type: 'assistant', text: result.text, aborted: !!result.aborted };
|
|
@@ -6606,25 +6696,40 @@ export async function createChatRuntime({
|
|
|
6606
6696
|
},
|
|
6607
6697
|
consumeStartupEvents: () => startupEvents.splice(0, startupEvents.length),
|
|
6608
6698
|
getInputHistory: () => loadInputHistory(),
|
|
6609
|
-
getCurrentSessionId: () => currentSession.id,
|
|
6610
|
-
getSessionMessages: () => currentSession.messages || [],
|
|
6611
|
-
getSessionCompact: () => currentSession.compact || null,
|
|
6699
|
+
getCurrentSessionId: () => currentSession.id,
|
|
6700
|
+
getSessionMessages: () => currentSession.messages || [],
|
|
6701
|
+
getSessionCompact: () => currentSession.compact || null,
|
|
6702
|
+
getChangeSets: () => listGitOplogChanges(changeTracker),
|
|
6703
|
+
getChangeSetPatch: (id) => readGitOplogPatch(changeTracker, id),
|
|
6704
|
+
undoChangeSet: (id) => undoGitOplogChange(changeTracker, id),
|
|
6705
|
+
undoChangeSets: (ids) => undoGitOplogChanges(changeTracker, ids),
|
|
6612
6706
|
reloadConfig: async (options = {}) => {
|
|
6613
6707
|
config = await loadConfig();
|
|
6708
|
+
config.runtime = {
|
|
6709
|
+
...(config.runtime || {}),
|
|
6710
|
+
project_is_git: Boolean(changeTracker?.enabled)
|
|
6711
|
+
};
|
|
6614
6712
|
await syncRuntimeFromConfig(options);
|
|
6615
6713
|
return config;
|
|
6714
|
+
},
|
|
6715
|
+
reloadCommandsAndSkills: async () => {
|
|
6716
|
+
await reloadCommandsAndSkills();
|
|
6717
|
+
return true;
|
|
6718
|
+
},
|
|
6719
|
+
setExecutionMode: async (next) => {
|
|
6720
|
+
if (!['normal', 'plan'].includes(next)) return false;
|
|
6721
|
+
executionMode = next;
|
|
6722
|
+
await setConfigValue('execution.mode', next);
|
|
6723
|
+
config = await loadConfig();
|
|
6724
|
+
return true;
|
|
6616
6725
|
},
|
|
6617
|
-
|
|
6618
|
-
|
|
6726
|
+
setApprovalMode: async (next) => {
|
|
6727
|
+
const normalized = String(next || '').toLowerCase().replace(/-/g, '_');
|
|
6728
|
+
if (!['review', 'auto', 'full_access'].includes(normalized)) return false;
|
|
6729
|
+
await setConfigValue('execution.approval_mode', normalized);
|
|
6730
|
+
config = await loadConfig();
|
|
6619
6731
|
return true;
|
|
6620
6732
|
},
|
|
6621
|
-
setExecutionMode: async (next) => {
|
|
6622
|
-
if (!['normal', 'auto', 'plan'].includes(next)) return false;
|
|
6623
|
-
executionMode = next;
|
|
6624
|
-
await setConfigValue('execution.mode', next);
|
|
6625
|
-
config = await loadConfig();
|
|
6626
|
-
return true;
|
|
6627
|
-
},
|
|
6628
6733
|
setRequestToolApproval: (handler) => {
|
|
6629
6734
|
activeRequestToolApproval = typeof handler === 'function' ? handler : null;
|
|
6630
6735
|
return true;
|