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