dsh-context 0.54.3 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/icon.svg +12 -0
- package/lib/client.js +386 -194
- package/lib/index.d.ts +83 -36
- package/lib/index.js +252 -92
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +9 -4
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { SessionLogOffset, deriveEventMessage, interruptedTurnClosers } from "@deepseek-ai/dsh-session";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import { dirname, join, normalize, relative } from "node:path";
|
|
5
|
+
import { dirname, isAbsolute, join, normalize, relative } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import z$1 from "@deepseek-ai/schemastery";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
@@ -372,21 +372,30 @@ function decodeKindOfBlock(blockType) {
|
|
|
372
372
|
if (blockType === "tool-call") return "toolarg";
|
|
373
373
|
}
|
|
374
374
|
/**
|
|
375
|
-
* Per-kind decode spans inside one embedded
|
|
376
|
-
* [first block-start, endTime]: each `block-start`
|
|
377
|
-
* to the next one, the last one up to `endTime`.
|
|
378
|
-
* timed stream rides the settlement
|
|
379
|
-
* of separate `assistant/chunk`
|
|
380
|
-
*
|
|
381
|
-
* with
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
375
|
+
* Per-kind decode spans AND counted `block-start` markers inside one embedded
|
|
376
|
+
* assistant stream, tiling [first block-start, endTime]: each `block-start`
|
|
377
|
+
* record owns the interval up to the next one, the last one up to `endTime`.
|
|
378
|
+
* This is the V2+ shape, whose timed stream rides the settlement
|
|
379
|
+
* (`assistant/message.data.stream`) instead of separate `assistant/chunk`
|
|
380
|
+
* events. Total over untrusted input — a malformed record is skipped whole (a
|
|
381
|
+
* marker with an unusable time anchors no span and counts nothing), a
|
|
382
|
+
* non-finite boundary yields zero, and a stream with no marker (or not an
|
|
383
|
+
* array) yields all zeros.
|
|
384
|
+
*/
|
|
385
|
+
function decodeTallyOfStream(stream, endTime) {
|
|
386
|
+
const tally = {
|
|
387
|
+
spans: {
|
|
388
|
+
reasoning: 0,
|
|
389
|
+
text: 0,
|
|
390
|
+
toolarg: 0
|
|
391
|
+
},
|
|
392
|
+
blocks: {
|
|
393
|
+
reasoning: 0,
|
|
394
|
+
text: 0,
|
|
395
|
+
toolarg: 0
|
|
396
|
+
}
|
|
388
397
|
};
|
|
389
|
-
if (!Array.isArray(stream) || !Number.isFinite(endTime)) return
|
|
398
|
+
if (!Array.isArray(stream) || !Number.isFinite(endTime)) return tally;
|
|
390
399
|
let kind;
|
|
391
400
|
let since = 0;
|
|
392
401
|
for (const record of stream) {
|
|
@@ -397,12 +406,13 @@ function decodeSpansOfStream(stream, endTime) {
|
|
|
397
406
|
if (chunk.type !== "block-start") continue;
|
|
398
407
|
const time = r.time;
|
|
399
408
|
if (typeof time !== "number" || !Number.isFinite(time)) continue;
|
|
400
|
-
if (kind !== void 0) spans[kind] += Math.max(0, time - since);
|
|
409
|
+
if (kind !== void 0) tally.spans[kind] += Math.max(0, time - since);
|
|
401
410
|
kind = decodeKindOfBlock(chunk.blockType);
|
|
411
|
+
if (kind !== void 0) tally.blocks[kind] += 1;
|
|
402
412
|
since = time;
|
|
403
413
|
}
|
|
404
|
-
if (kind !== void 0) spans[kind] += Math.max(0, endTime - since);
|
|
405
|
-
return
|
|
414
|
+
if (kind !== void 0) tally.spans[kind] += Math.max(0, endTime - since);
|
|
415
|
+
return tally;
|
|
406
416
|
}
|
|
407
417
|
/**
|
|
408
418
|
* The first token's instant inside one PACKED delta run (`text-chunks` /
|
|
@@ -507,6 +517,24 @@ function kindOfCall(tool, args) {
|
|
|
507
517
|
return kindOfTool(tool);
|
|
508
518
|
}
|
|
509
519
|
/**
|
|
520
|
+
* Whether a settled call's arguments can yield an op at all: the file tools
|
|
521
|
+
* plus `str_replace_editor`, whose purpose follows its `command`. The public
|
|
522
|
+
* face of the gate {@link opsOfCall} short-circuits on — callers that decide
|
|
523
|
+
* whether to parse or to KEEP a call's raw arguments ask here instead of
|
|
524
|
+
* restating the tool list.
|
|
525
|
+
*/
|
|
526
|
+
function opBearingTool(tool) {
|
|
527
|
+
return tool === "str_replace_editor" || kindOfTool(tool) !== null;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Whether a call's raw arguments must ride the fold state
|
|
531
|
+
* (TimelineState.callNames): every op-bearing tool, plus `run_code`, whose
|
|
532
|
+
* `description` labels the ops its nested dispatches book at flush time.
|
|
533
|
+
*/
|
|
534
|
+
function rawArgsNeeded(tool) {
|
|
535
|
+
return opBearingTool(tool) || tool === "run_code";
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
510
538
|
* The operation's target path: the path-ish argument of read/write tools;
|
|
511
539
|
* for searches the narrowing `path`, else the pattern itself (a pathless
|
|
512
540
|
* grep/glob's target IS the pattern — the workspace-wide search text).
|
|
@@ -645,7 +673,7 @@ function searchFilesOf(meta) {
|
|
|
645
673
|
* resolve no target) rows nothing.
|
|
646
674
|
*/
|
|
647
675
|
function opsOfCall(input) {
|
|
648
|
-
const args = parseCallArgs(input.argsRaw);
|
|
676
|
+
const args = opBearingTool(input.tool) ? parseCallArgs(input.argsRaw) : null;
|
|
649
677
|
const kind = kindOfCall(input.tool, args);
|
|
650
678
|
if (kind === null) return [];
|
|
651
679
|
const stamp = {
|
|
@@ -724,8 +752,15 @@ function countTurnRuns(requests) {
|
|
|
724
752
|
return runs;
|
|
725
753
|
}
|
|
726
754
|
function trimState(st, bounds) {
|
|
727
|
-
|
|
728
|
-
if (st.
|
|
755
|
+
st.turnRuns = st.turnRuns ?? countTurnRuns(st.requests);
|
|
756
|
+
if (st.turnRuns > bounds.maxKeptTurns) {
|
|
757
|
+
st.requests = trimToLastTurns(st.requests, bounds.maxKeptTurns);
|
|
758
|
+
st.turnRuns = countTurnRuns(st.requests);
|
|
759
|
+
}
|
|
760
|
+
if (st.requests.length > bounds.maxRequestSteps) {
|
|
761
|
+
st.requests = st.requests.slice(-bounds.maxRequestSteps);
|
|
762
|
+
st.turnRuns = countTurnRuns(st.requests);
|
|
763
|
+
}
|
|
729
764
|
if (st.events.length > bounds.maxEvents) st.events = st.events.slice(-bounds.maxEvents);
|
|
730
765
|
if (st.fileOps.length > bounds.maxFileOps) {
|
|
731
766
|
const drop = st.fileOps.length - bounds.maxFileOps;
|
|
@@ -757,6 +792,7 @@ function createTimelineState() {
|
|
|
757
792
|
systemTokens: 0,
|
|
758
793
|
toolsTokens: 0,
|
|
759
794
|
requests: [],
|
|
795
|
+
turnRuns: 0,
|
|
760
796
|
events: [],
|
|
761
797
|
archived: [],
|
|
762
798
|
callNames: {},
|
|
@@ -941,7 +977,7 @@ function applySurface(st, ev, type, data, message) {
|
|
|
941
977
|
for (const k in st.callNames) if (k !== srcId && k !== blockId) kept[k] = st.callNames[k];
|
|
942
978
|
st.callNames = kept;
|
|
943
979
|
}
|
|
944
|
-
if (data?.error) node.err = true;
|
|
980
|
+
if (data?.error || message?.isError === true) node.err = true;
|
|
945
981
|
} else if (source?.kind === "skill-invocation") node.skill = typeof source.name === "string" ? source.name : "?";
|
|
946
982
|
else if (source?.kind === "plugin") {
|
|
947
983
|
if (source.form === "notice" && typeof source.summary === "string") node.text = source.summary;
|
|
@@ -1118,17 +1154,32 @@ function ensureTiming(st) {
|
|
|
1118
1154
|
};
|
|
1119
1155
|
return st.timing;
|
|
1120
1156
|
}
|
|
1157
|
+
/** The TimingTotals fields each decode bucket's span and count land in. */
|
|
1158
|
+
const DECODE_FIELDS = {
|
|
1159
|
+
reasoning: {
|
|
1160
|
+
ms: "reasoningMs",
|
|
1161
|
+
n: "reasoningBlocks"
|
|
1162
|
+
},
|
|
1163
|
+
text: {
|
|
1164
|
+
ms: "textMs",
|
|
1165
|
+
n: "textBlocks"
|
|
1166
|
+
},
|
|
1167
|
+
toolarg: {
|
|
1168
|
+
ms: "toolArgMs",
|
|
1169
|
+
n: "toolArgBlocks"
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1121
1172
|
/**
|
|
1122
|
-
* Fold one block's decode span into the totals' generation split
|
|
1123
|
-
* TimingTotals). A zero span stays ABSENT — the field then
|
|
1124
|
-
* "
|
|
1125
|
-
* every pre-split-shaped state, and the card reads absence
|
|
1173
|
+
* Fold one block's decode span and count into the totals' generation split
|
|
1174
|
+
* (see TimingTotals). A zero span or count stays ABSENT — the field then
|
|
1175
|
+
* carries the "nothing was decoded/counted in this bucket" fact without adding
|
|
1176
|
+
* dead properties to every pre-split-shaped state, and the card reads absence
|
|
1177
|
+
* as 0.
|
|
1126
1178
|
*/
|
|
1127
|
-
function addDecode(timing, kind, ms) {
|
|
1128
|
-
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
else timing.toolArgMs = (timing.toolArgMs ?? 0) + ms;
|
|
1179
|
+
function addDecode(timing, kind, ms, blocks) {
|
|
1180
|
+
const field = DECODE_FIELDS[kind];
|
|
1181
|
+
if (ms > 0) timing[field.ms] = (timing[field.ms] ?? 0) + ms;
|
|
1182
|
+
if (blocks > 0) timing[field.n] = (timing[field.n] ?? 0) + blocks;
|
|
1132
1183
|
}
|
|
1133
1184
|
/**
|
|
1134
1185
|
* Tally one completed tool call into the per-name ranking, bounded to
|
|
@@ -1161,22 +1212,42 @@ function bumpToolTotals(timing, name, ms) {
|
|
|
1161
1212
|
ms: cur.ms + ms
|
|
1162
1213
|
};
|
|
1163
1214
|
}
|
|
1215
|
+
const CLONE_ALL = [
|
|
1216
|
+
"surface",
|
|
1217
|
+
"sums",
|
|
1218
|
+
"requests",
|
|
1219
|
+
"events",
|
|
1220
|
+
"archived",
|
|
1221
|
+
"callNames",
|
|
1222
|
+
"fileOps",
|
|
1223
|
+
"timing"
|
|
1224
|
+
];
|
|
1164
1225
|
function applyTimeline(state, event, bounds) {
|
|
1165
1226
|
let st;
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1227
|
+
/**
|
|
1228
|
+
* The event's private working state: a shallow object clone up front, with
|
|
1229
|
+
* only the named collections cloned into private copies. Everything else
|
|
1230
|
+
* stays shared with the persisted previous state — the apply contract is
|
|
1231
|
+
* "never mutate the previous state in place". Called EXACTLY ONCE per event
|
|
1232
|
+
* (each case names the collections its own path may write into; helpers
|
|
1233
|
+
* never call it), so building afresh per call is safe. The default is the
|
|
1234
|
+
* full clone — a future branch that omits its list degrades to the old
|
|
1235
|
+
* behavior instead of sharing a collection it mutates.
|
|
1236
|
+
*/
|
|
1237
|
+
const ensure = (keys = CLONE_ALL) => {
|
|
1238
|
+
st = { ...state };
|
|
1239
|
+
for (const key of keys) if (key === "surface") st.surface = [...state.surface];
|
|
1240
|
+
else if (key === "sums") st.sums = { ...state.sums };
|
|
1241
|
+
else if (key === "requests") st.requests = [...state.requests];
|
|
1242
|
+
else if (key === "events") st.events = [...state.events];
|
|
1243
|
+
else if (key === "archived") st.archived = [...state.archived];
|
|
1244
|
+
else if (key === "callNames") st.callNames = { ...state.callNames };
|
|
1245
|
+
else if (key === "fileOps") st.fileOps = [...state.fileOps];
|
|
1246
|
+
else if (state.timing !== void 0) st.timing = {
|
|
1177
1247
|
...state.timing,
|
|
1178
1248
|
tools: { ...state.timing.tools }
|
|
1179
|
-
}
|
|
1249
|
+
};
|
|
1250
|
+
return st;
|
|
1180
1251
|
};
|
|
1181
1252
|
const data = event.data;
|
|
1182
1253
|
try {
|
|
@@ -1184,7 +1255,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1184
1255
|
case "request/header": {
|
|
1185
1256
|
const header = data?.header ?? {};
|
|
1186
1257
|
const tools = Array.isArray(header.tools) ? header.tools : [];
|
|
1187
|
-
const s = ensure();
|
|
1258
|
+
const s = ensure(["events"]);
|
|
1188
1259
|
s.toolsTokens = estimateToolsTotal(tools);
|
|
1189
1260
|
const systemText = header.system;
|
|
1190
1261
|
if (typeof systemText === "string" && systemText !== "") {
|
|
@@ -1216,7 +1287,11 @@ function applyTimeline(state, event, bounds) {
|
|
|
1216
1287
|
break;
|
|
1217
1288
|
}
|
|
1218
1289
|
case "system/message": {
|
|
1219
|
-
const s = ensure(
|
|
1290
|
+
const s = ensure([
|
|
1291
|
+
"surface",
|
|
1292
|
+
"sums",
|
|
1293
|
+
"archived"
|
|
1294
|
+
]);
|
|
1220
1295
|
delete s.pendingShadowedSeqs;
|
|
1221
1296
|
delete s.pendingShadowEventSeq;
|
|
1222
1297
|
const op = replaceRangeOf(event.surfaceOp);
|
|
@@ -1235,7 +1310,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1235
1310
|
break;
|
|
1236
1311
|
}
|
|
1237
1312
|
case "request/context": {
|
|
1238
|
-
const s = ensure();
|
|
1313
|
+
const s = ensure([]);
|
|
1239
1314
|
if (data && typeof data.contextWindow === "number") s.contextWindow = data.contextWindow;
|
|
1240
1315
|
if (data && typeof data.model === "string") s.model = data.model;
|
|
1241
1316
|
if (data && typeof data.provider === "string") s.provider = data.provider;
|
|
@@ -1243,8 +1318,8 @@ function applyTimeline(state, event, bounds) {
|
|
|
1243
1318
|
}
|
|
1244
1319
|
case "tool/call":
|
|
1245
1320
|
if (data && typeof data.callId === "string" && typeof data.name === "string") {
|
|
1246
|
-
const s = ensure();
|
|
1247
|
-
const argsRaw = argsRawOf(data.arguments);
|
|
1321
|
+
const s = ensure(["callNames"]);
|
|
1322
|
+
const argsRaw = rawArgsNeeded(data.name) ? argsRawOf(data.arguments) : void 0;
|
|
1248
1323
|
s.callNames[data.callId] = {
|
|
1249
1324
|
name: data.name,
|
|
1250
1325
|
start: event.time,
|
|
@@ -1257,14 +1332,15 @@ function applyTimeline(state, event, bounds) {
|
|
|
1257
1332
|
const rootCallId = data?.rootCallId;
|
|
1258
1333
|
const name = data?.name;
|
|
1259
1334
|
if (typeof rootCallId === "string" && typeof name === "string") {
|
|
1335
|
+
const argsRaw = opBearingTool(name) ? argsRawOf(data?.arguments) : void 0;
|
|
1260
1336
|
const ops = opsOfCall({
|
|
1261
1337
|
seq: event.seq,
|
|
1262
1338
|
time: event.time,
|
|
1263
1339
|
tool: name,
|
|
1264
|
-
argsRaw
|
|
1340
|
+
argsRaw,
|
|
1265
1341
|
err: data?.isError === true
|
|
1266
1342
|
});
|
|
1267
|
-
if (ops.length > 0) bufferCodeOps(ensure(), rootCallId, ops);
|
|
1343
|
+
if (ops.length > 0) bufferCodeOps(ensure([]), rootCallId, ops);
|
|
1268
1344
|
}
|
|
1269
1345
|
break;
|
|
1270
1346
|
}
|
|
@@ -1275,17 +1351,24 @@ function applyTimeline(state, event, bounds) {
|
|
|
1275
1351
|
if (chunk !== null && typeof chunk === "object" && chunk.type === "block-start") {
|
|
1276
1352
|
const kind = decodeKindOfBlock(chunk.blockType);
|
|
1277
1353
|
if (start.block === void 0 && kind === void 0) return state;
|
|
1278
|
-
const s = ensure();
|
|
1354
|
+
const s = ensure([]);
|
|
1279
1355
|
const decode = { ...start.decode ?? {
|
|
1280
1356
|
reasoning: 0,
|
|
1281
1357
|
text: 0,
|
|
1282
1358
|
toolarg: 0
|
|
1283
1359
|
} };
|
|
1284
1360
|
if (start.block !== void 0) decode[start.block.kind] += durOf(start.block.since, event.time);
|
|
1361
|
+
const blocks = { ...start.blocks ?? {
|
|
1362
|
+
reasoning: 0,
|
|
1363
|
+
text: 0,
|
|
1364
|
+
toolarg: 0
|
|
1365
|
+
} };
|
|
1366
|
+
if (kind !== void 0) blocks[kind] += 1;
|
|
1285
1367
|
s.stepStart = {
|
|
1286
1368
|
time: start.time,
|
|
1287
1369
|
...start.firstToken !== void 0 ? { firstToken: start.firstToken } : {},
|
|
1288
1370
|
decode,
|
|
1371
|
+
blocks,
|
|
1289
1372
|
...kind !== void 0 ? { block: {
|
|
1290
1373
|
kind,
|
|
1291
1374
|
since: event.time
|
|
@@ -1295,7 +1378,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1295
1378
|
}
|
|
1296
1379
|
if (start.firstToken !== void 0) return state;
|
|
1297
1380
|
if (!isTokenChunk(data?.chunk)) return state;
|
|
1298
|
-
const s = ensure();
|
|
1381
|
+
const s = ensure([]);
|
|
1299
1382
|
s.stepStart = {
|
|
1300
1383
|
...start,
|
|
1301
1384
|
firstToken: event.time
|
|
@@ -1307,7 +1390,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1307
1390
|
if (start === void 0 || start.firstToken !== void 0) return state;
|
|
1308
1391
|
const first = firstTokenTimeOfStream(data?.stream);
|
|
1309
1392
|
if (first === void 0) return state;
|
|
1310
|
-
const s = ensure();
|
|
1393
|
+
const s = ensure([]);
|
|
1311
1394
|
s.stepStart = {
|
|
1312
1395
|
time: start.time,
|
|
1313
1396
|
firstToken: first
|
|
@@ -1315,21 +1398,26 @@ function applyTimeline(state, event, bounds) {
|
|
|
1315
1398
|
break;
|
|
1316
1399
|
}
|
|
1317
1400
|
case "step/start": {
|
|
1318
|
-
const s = ensure();
|
|
1401
|
+
const s = ensure([]);
|
|
1319
1402
|
s.stepStart = { time: event.time };
|
|
1320
1403
|
break;
|
|
1321
1404
|
}
|
|
1322
1405
|
case "step/end": {
|
|
1323
1406
|
const start = state.stepStart;
|
|
1324
1407
|
if (start === void 0) return state;
|
|
1325
|
-
const s = ensure();
|
|
1408
|
+
const s = ensure(["timing"]);
|
|
1326
1409
|
ensureTiming(s).wallMs += durOf(start.time, event.time);
|
|
1327
1410
|
delete s.stepStart;
|
|
1328
1411
|
break;
|
|
1329
1412
|
}
|
|
1330
1413
|
case "user/message": {
|
|
1331
1414
|
const msg = deriveEventMessage(event);
|
|
1332
|
-
const s = ensure(
|
|
1415
|
+
const s = ensure([
|
|
1416
|
+
"surface",
|
|
1417
|
+
"sums",
|
|
1418
|
+
"archived",
|
|
1419
|
+
"events"
|
|
1420
|
+
]);
|
|
1333
1421
|
bumpDetailRev(s);
|
|
1334
1422
|
const node = applySurface(s, event, event.type, data, msg);
|
|
1335
1423
|
const source = msg?.source;
|
|
@@ -1366,7 +1454,14 @@ function applyTimeline(state, event, bounds) {
|
|
|
1366
1454
|
const blockId = firstBlock?.toolCallId;
|
|
1367
1455
|
const pendingEntry = (typeof srcId === "string" ? state.callNames[srcId] : void 0) ?? (typeof blockId === "string" ? state.callNames[blockId] : void 0);
|
|
1368
1456
|
const buffered = (typeof srcId === "string" ? state.pendingCodeOps?.[srcId] : void 0) ?? (typeof blockId === "string" ? state.pendingCodeOps?.[blockId] : void 0);
|
|
1369
|
-
const s = ensure(
|
|
1457
|
+
const s = ensure([
|
|
1458
|
+
"surface",
|
|
1459
|
+
"sums",
|
|
1460
|
+
"archived",
|
|
1461
|
+
"events",
|
|
1462
|
+
"fileOps",
|
|
1463
|
+
"timing"
|
|
1464
|
+
]);
|
|
1370
1465
|
bumpDetailRev(s);
|
|
1371
1466
|
const node = applySurface(s, event, event.type, data, toolMsg);
|
|
1372
1467
|
if (node.tool === ASK_USER_TOOL) s.humanInputs = (s.humanInputs ?? 0) + 1;
|
|
@@ -1376,7 +1471,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1376
1471
|
tool: pendingEntry.name,
|
|
1377
1472
|
argsRaw: pendingEntry.argsRaw,
|
|
1378
1473
|
meta: data?.meta,
|
|
1379
|
-
err: Boolean(data?.error) || firstBlock?.isError === true
|
|
1474
|
+
err: Boolean(data?.error) || toolMsg?.isError === true || firstBlock?.isError === true
|
|
1380
1475
|
}));
|
|
1381
1476
|
if (buffered !== void 0 && buffered.length > 0) {
|
|
1382
1477
|
const program = parseCallArgs(pendingEntry?.argsRaw)?.description;
|
|
@@ -1413,7 +1508,14 @@ function applyTimeline(state, event, bounds) {
|
|
|
1413
1508
|
}
|
|
1414
1509
|
case "assistant/message": {
|
|
1415
1510
|
const usage = data?.usage;
|
|
1416
|
-
const s = ensure(
|
|
1511
|
+
const s = ensure([
|
|
1512
|
+
"surface",
|
|
1513
|
+
"sums",
|
|
1514
|
+
"archived",
|
|
1515
|
+
"events",
|
|
1516
|
+
"requests",
|
|
1517
|
+
"timing"
|
|
1518
|
+
]);
|
|
1417
1519
|
bumpDetailRev(s);
|
|
1418
1520
|
const total = s.systemTokens + s.toolsTokens + s.sums.user + s.sums.inject + s.sums.skill + s.sums.assistant + s.sums.tool;
|
|
1419
1521
|
const record = {
|
|
@@ -1449,6 +1551,8 @@ function applyTimeline(state, event, bounds) {
|
|
|
1449
1551
|
}
|
|
1450
1552
|
}
|
|
1451
1553
|
s.requests.push(record);
|
|
1554
|
+
const prevLast = state.requests.at(-1);
|
|
1555
|
+
s.turnRuns = (state.turnRuns ?? countTurnRuns(state.requests)) + (prevLast === void 0 ? record.turn !== void 0 ? 1 : 0 : record.turn === prevLast.turn ? 0 : 1);
|
|
1452
1556
|
const timing = ensureTiming(s);
|
|
1453
1557
|
timing.calls += 1;
|
|
1454
1558
|
const stepStart = state.stepStart;
|
|
@@ -1464,10 +1568,10 @@ function applyTimeline(state, event, bounds) {
|
|
|
1464
1568
|
if (stepStart.decode !== void 0) {
|
|
1465
1569
|
const decode = { ...stepStart.decode };
|
|
1466
1570
|
if (stepStart.block !== void 0) decode[stepStart.block.kind] += durOf(stepStart.block.since, event.time);
|
|
1467
|
-
for (const kind of DECODE_KINDS) addDecode(timing, kind, decode[kind]);
|
|
1571
|
+
for (const kind of DECODE_KINDS) addDecode(timing, kind, decode[kind], stepStart.blocks?.[kind] ?? 0);
|
|
1468
1572
|
} else {
|
|
1469
|
-
const
|
|
1470
|
-
for (const kind of DECODE_KINDS) addDecode(timing, kind, spans[kind]);
|
|
1573
|
+
const tally = decodeTallyOfStream(data?.stream, event.time);
|
|
1574
|
+
for (const kind of DECODE_KINDS) addDecode(timing, kind, tally.spans[kind], tally.blocks[kind]);
|
|
1471
1575
|
}
|
|
1472
1576
|
}
|
|
1473
1577
|
}
|
|
@@ -1477,7 +1581,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1477
1581
|
}
|
|
1478
1582
|
case "plan/mode":
|
|
1479
1583
|
if (data && typeof data.active === "boolean") {
|
|
1480
|
-
const s = ensure();
|
|
1584
|
+
const s = ensure(["events"]);
|
|
1481
1585
|
s.events.push({
|
|
1482
1586
|
seq: event.seq,
|
|
1483
1587
|
time: event.time,
|
|
@@ -1489,7 +1593,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1489
1593
|
break;
|
|
1490
1594
|
case "compaction/summary":
|
|
1491
1595
|
case "compaction/prune": {
|
|
1492
|
-
const s = ensure();
|
|
1596
|
+
const s = ensure(["events"]);
|
|
1493
1597
|
bumpDetailRev(s);
|
|
1494
1598
|
if (data && Array.isArray(data.shadowedSeqs)) {
|
|
1495
1599
|
s.pendingShadowedSeqs = data.shadowedSeqs.filter((x) => typeof x === "number");
|
|
@@ -2399,18 +2503,21 @@ function watchActivityBackfill(ctx) {
|
|
|
2399
2503
|
//#endregion
|
|
2400
2504
|
//#region src/host/config.ts
|
|
2401
2505
|
/**
|
|
2402
|
-
* dsh-context
|
|
2403
|
-
* loader row in cordis.yml.
|
|
2404
|
-
*
|
|
2405
|
-
* Cordis validates the entry config against this exported `Config` schema
|
|
2406
|
-
* (any Standard Schema v1 validator — zod is ours) before `apply` runs, fills
|
|
2407
|
-
* per-field defaults, and fails the load loudly on invalid or unknown keys
|
|
2408
|
-
* (`.strict()`). The official plugin-config principle this answers: "anything
|
|
2409
|
-
* that two deployments may want to set differently is a configuration field".
|
|
2506
|
+
* dsh-context entry configuration — the `config:` block of the `dsh-context`
|
|
2507
|
+
* loader row in cordis.yml, plus the per-user display preferences.
|
|
2410
2508
|
*
|
|
2411
|
-
* The
|
|
2412
|
-
*
|
|
2413
|
-
*
|
|
2509
|
+
* The schema is schemastery so both consumers it must serve accept it: cordis
|
|
2510
|
+
* validates the entry config through the schema's Standard Schema face before
|
|
2511
|
+
* `apply` runs (defaults per field, unknown keys merged through), and the
|
|
2512
|
+
* harness's Config-form generation (dsh 0.1.7+) derives the Plugins page's
|
|
2513
|
+
* live form from this same schema — there the namespace is the entry id
|
|
2514
|
+
* (`dsh-context`) and only the `.volatile()` preference fields are served and
|
|
2515
|
+
* editable. Bounds edits remount the entry (the folds read them at apply);
|
|
2516
|
+
* preference edits commit volatile-only and never remount. On the older lines
|
|
2517
|
+
* the preferences are inert here — their surface is the registered settings
|
|
2518
|
+
* namespace (settings.ts). The `Config` type describes the RESOLVED shape
|
|
2519
|
+
* cordis hands to `apply`; the raw patch values are partial and the schema
|
|
2520
|
+
* fills every default.
|
|
2414
2521
|
*/
|
|
2415
2522
|
const DEFAULT_BOUNDS = {
|
|
2416
2523
|
maxRequestSteps: 1500,
|
|
@@ -2421,19 +2528,57 @@ const DEFAULT_BOUNDS = {
|
|
|
2421
2528
|
maxFileOps: 400
|
|
2422
2529
|
};
|
|
2423
2530
|
/**
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2531
|
+
* Mark a field live-editable where the harness's schemastery ships the
|
|
2532
|
+
* `.volatile()` modifier (the Config-form generation reads the mark to serve
|
|
2533
|
+
* the field on the Plugins page); a plain field on the older lines whose
|
|
2534
|
+
* schemastery predates the modifier.
|
|
2535
|
+
*/
|
|
2536
|
+
function volatileField(field) {
|
|
2537
|
+
const volatile = field.volatile;
|
|
2538
|
+
return typeof volatile === "function" ? volatile.call(field) : field;
|
|
2539
|
+
}
|
|
2540
|
+
/** One bounded positive-integer count. */
|
|
2541
|
+
function count(defaultValue) {
|
|
2542
|
+
return z$1.number().min(1).step(1).default(defaultValue);
|
|
2543
|
+
}
|
|
2544
|
+
/** The cordis `Config` validator and the Config-form generation's served schema. */
|
|
2545
|
+
const Config = z$1.object({
|
|
2546
|
+
maxRequestSteps: count(DEFAULT_BOUNDS.maxRequestSteps),
|
|
2547
|
+
maxKeptTurns: count(DEFAULT_BOUNDS.maxKeptTurns),
|
|
2548
|
+
maxEvents: count(DEFAULT_BOUNDS.maxEvents),
|
|
2549
|
+
maxNodes: count(DEFAULT_BOUNDS.maxNodes),
|
|
2550
|
+
maxArchiveNodes: count(DEFAULT_BOUNDS.maxArchiveNodes),
|
|
2551
|
+
maxFileOps: count(DEFAULT_BOUNDS.maxFileOps),
|
|
2552
|
+
defaultPlacement: volatileField(z$1.union([
|
|
2553
|
+
"all",
|
|
2554
|
+
"tab",
|
|
2555
|
+
"sidebar"
|
|
2556
|
+
]).default("all").loose()),
|
|
2557
|
+
defaultGranularity: volatileField(z$1.union(["step", "turn"]).default("step").loose()),
|
|
2558
|
+
defaultTrendMode: volatileField(z$1.union(["total", "delta"]).default("total").loose()),
|
|
2559
|
+
defaultToolSort: volatileField(z$1.union([
|
|
2560
|
+
"size",
|
|
2561
|
+
"count",
|
|
2562
|
+
"name"
|
|
2563
|
+
]).default("count").loose()),
|
|
2564
|
+
defaultFileSort: volatileField(z$1.union([
|
|
2565
|
+
"count",
|
|
2566
|
+
"latest",
|
|
2567
|
+
"path"
|
|
2568
|
+
]).default("count").loose()),
|
|
2569
|
+
insightsEntry: volatileField(z$1.union(["show", "hide"]).default("show").loose())
|
|
2570
|
+
});
|
|
2571
|
+
/** Resolve the fold's retention bounds (the schema fills every default). */
|
|
2435
2572
|
function resolveBounds(config) {
|
|
2436
|
-
|
|
2573
|
+
const resolved = Config(config ?? {});
|
|
2574
|
+
return {
|
|
2575
|
+
maxRequestSteps: resolved.maxRequestSteps,
|
|
2576
|
+
maxKeptTurns: resolved.maxKeptTurns,
|
|
2577
|
+
maxEvents: resolved.maxEvents,
|
|
2578
|
+
maxNodes: resolved.maxNodes,
|
|
2579
|
+
maxArchiveNodes: resolved.maxArchiveNodes,
|
|
2580
|
+
maxFileOps: resolved.maxFileOps
|
|
2581
|
+
};
|
|
2437
2582
|
}
|
|
2438
2583
|
//#endregion
|
|
2439
2584
|
//#region src/host/detail.ts
|
|
@@ -2887,8 +3032,11 @@ const timingTotalsSchema = z.object({
|
|
|
2887
3032
|
ttftMs: z.number().nonnegative(),
|
|
2888
3033
|
genMs: z.number().nonnegative(),
|
|
2889
3034
|
reasoningMs: z.number().nonnegative().optional(),
|
|
3035
|
+
reasoningBlocks: z.number().int().nonnegative().optional(),
|
|
2890
3036
|
textMs: z.number().nonnegative().optional(),
|
|
3037
|
+
textBlocks: z.number().int().nonnegative().optional(),
|
|
2891
3038
|
toolArgMs: z.number().nonnegative().optional(),
|
|
3039
|
+
toolArgBlocks: z.number().int().nonnegative().optional(),
|
|
2892
3040
|
speedTokens: z.number().nonnegative().optional(),
|
|
2893
3041
|
speedMs: z.number().nonnegative().optional(),
|
|
2894
3042
|
calls: z.number().int().nonnegative(),
|
|
@@ -2974,6 +3122,7 @@ const timelineStateSchema = z.object({
|
|
|
2974
3122
|
lastModel: z.string().optional(),
|
|
2975
3123
|
contextWindow: z.number().optional(),
|
|
2976
3124
|
requests: z.array(requestRecordSchema),
|
|
3125
|
+
turnRuns: z.number().int().nonnegative().optional(),
|
|
2977
3126
|
events: z.array(contextEventSchema),
|
|
2978
3127
|
archived: z.array(surfaceNodeSchema),
|
|
2979
3128
|
cost: costUsageSchema.optional(),
|
|
@@ -2989,6 +3138,11 @@ const timelineStateSchema = z.object({
|
|
|
2989
3138
|
text: z.number(),
|
|
2990
3139
|
toolarg: z.number()
|
|
2991
3140
|
}).strict().optional(),
|
|
3141
|
+
blocks: z.object({
|
|
3142
|
+
reasoning: z.number().int(),
|
|
3143
|
+
text: z.number().int(),
|
|
3144
|
+
toolarg: z.number().int()
|
|
3145
|
+
}).strict().optional(),
|
|
2992
3146
|
block: z.object({
|
|
2993
3147
|
kind: z.enum([
|
|
2994
3148
|
"reasoning",
|
|
@@ -3179,10 +3333,12 @@ const SettingsSchema = z$1.object({
|
|
|
3179
3333
|
]).default("count").loose(),
|
|
3180
3334
|
insightsEntry: z$1.union(["show", "hide"]).default("show").loose()
|
|
3181
3335
|
});
|
|
3182
|
-
/** Serve the namespace while a settings provider is composed; inert otherwise. */
|
|
3336
|
+
/** Serve the namespace while a settings provider with the register face is composed; inert otherwise. */
|
|
3183
3337
|
function installSettings(ctx) {
|
|
3184
3338
|
ctx.inject(["settings"], (sctx) => {
|
|
3185
|
-
sctx.settings
|
|
3339
|
+
const service = sctx.settings;
|
|
3340
|
+
if (typeof service.register !== "function") return;
|
|
3341
|
+
service.register(SETTINGS_NAMESPACE, SettingsSchema);
|
|
3186
3342
|
});
|
|
3187
3343
|
}
|
|
3188
3344
|
//#endregion
|
|
@@ -3388,11 +3544,15 @@ function probeAnchor(resolve, packageNames) {
|
|
|
3388
3544
|
/**
|
|
3389
3545
|
* Whether `candidate` lies inside `root`'s tree. A `..`-prefixed relative path
|
|
3390
3546
|
* escapes it; a sibling such as `dsh-context-extra` is reached through `..` too,
|
|
3391
|
-
* so it never counts as a child.
|
|
3392
|
-
*
|
|
3547
|
+
* so it never counts as a child. On Windows, a cross-drive `relative()` returns
|
|
3548
|
+
* the candidate's ABSOLUTE path (no `..` prefix) — a different drive is never
|
|
3549
|
+
* inside this tree, so the absolute answer is rejected the same way. A
|
|
3550
|
+
* pathological child literally named `..foo` would degrade to the home anchor,
|
|
3551
|
+
* which is the safe direction.
|
|
3393
3552
|
*/
|
|
3394
3553
|
function isInside(root, candidate) {
|
|
3395
|
-
|
|
3554
|
+
const rel = relative(root, candidate);
|
|
3555
|
+
return !rel.startsWith("..") && !isAbsolute(rel);
|
|
3396
3556
|
}
|
|
3397
3557
|
/**
|
|
3398
3558
|
* The running module tree's answer, or undefined when it cannot be trusted.
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
|
+
"icon": "icon.svg",
|
|
5
6
|
"author": "bowenliang123",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
@@ -23,9 +24,12 @@
|
|
|
23
24
|
"default": "./lib/index.js"
|
|
24
25
|
},
|
|
25
26
|
"./client": "./lib/client.js",
|
|
26
|
-
"./package.json": "./package.json"
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
"./locale/*.json": "./locale/*.json"
|
|
27
29
|
},
|
|
28
30
|
"files": [
|
|
31
|
+
"icon.svg",
|
|
32
|
+
"locale/*.json",
|
|
29
33
|
"lib/client.js",
|
|
30
34
|
"lib/index.js",
|
|
31
35
|
"lib/index.d.ts",
|
|
@@ -66,7 +70,8 @@
|
|
|
66
70
|
"dshReleases": {
|
|
67
71
|
"0.1.2-rc.1": "compatible",
|
|
68
72
|
"0.1.3-alpha.2": "compatible",
|
|
69
|
-
"0.1.5-rc.1": "compatible"
|
|
73
|
+
"0.1.5-rc.1": "compatible",
|
|
74
|
+
"0.1.7-alpha.2": "compatible"
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
},
|
|
@@ -108,7 +113,7 @@
|
|
|
108
113
|
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
|
109
114
|
"@deepseek-ai/dsh-settings": "0.1.2-rc.1",
|
|
110
115
|
"@deepseek-ai/dsh-util-workspace-path": "0.1.5-rc.1",
|
|
111
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
116
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
112
117
|
"@opencode-ai/models": "^0.0.72",
|
|
113
118
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
114
119
|
"@tailwindcss/node": "4.3.3",
|