dsh-context 0.41.2 → 0.42.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 +2 -2
- package/lib/client.js +268 -136
- package/lib/index.d.ts +18 -6
- package/lib/index.js +342 -40
- package/package.json +14 -17
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ Pick **Live (next request)** or any retained step, and browse what that request
|
|
|
91
91
|
|
|
92
92
|

|
|
93
93
|
|
|
94
|
-
- **Image payloads render as cards** — thumbnails with name, dimensions, stored size, and the official DeepSeek image-token estimate (dsh
|
|
94
|
+
- **Image payloads render as cards** — thumbnails with name, dimensions, stored size, and the official DeepSeek image-token estimate (the dsh multimodal pipeline, e.g. `read_image` results and image attachments):
|
|
95
95
|
|
|
96
96
|

|
|
97
97
|
|
|
@@ -140,7 +140,7 @@ In **Settings → Plugins → Plugin configuration**, the **Context** card holds
|
|
|
140
140
|
## Good to know
|
|
141
141
|
|
|
142
142
|
- **Estimates vs actuals** — category figures use dsh's own fixed-density heuristic (the same one as its built-in token meter); the pinned trend details and Token/Timing rings show provider-reported actuals next to them.
|
|
143
|
-
- **Compatibility** — works on `@deepseek-ai/dsh` **0.1.
|
|
143
|
+
- **Compatibility** — works on `@deepseek-ai/dsh` **0.1.2-rc1+**. The per-release matrix and how it is verified: [docs/compatibility.md](docs/compatibility.md).
|
|
144
144
|
- **I18n** — UI in English and 简体中文.
|
|
145
145
|
|
|
146
146
|
## Like it?
|
package/lib/client.js
CHANGED
|
@@ -88,6 +88,7 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
"brief.noInputs": "(无新增)",
|
|
89
89
|
"brief.locate": "在上下文浏览器中查看",
|
|
90
90
|
"jump.title": "在上下文标签页中查看此轮",
|
|
91
|
+
"jump.statsLine": "在上下文标签页中查看上下文构成",
|
|
91
92
|
"brief.turnTip": "这一轮开场时用户发送的消息;回合内的每一步都会回显同一条",
|
|
92
93
|
"brief.inputTip": "上一步回复之后、本次请求发出前新进入上下文的内容——通常是上一步所调工具的结果;每轮的第一步无新增",
|
|
93
94
|
"brief.replyTip": "这一步模型返回的内容(文本回复或工具调用);此步发起的调用,其结果会出现在下一柱的「输入」行",
|
|
@@ -189,6 +190,12 @@ window.__ModuleLoader__.load({
|
|
|
189
190
|
"loading": "正在读取会话日志…",
|
|
190
191
|
"error": "上下文数据读取失败:",
|
|
191
192
|
"error.retry": "重试",
|
|
193
|
+
"gate.title": "请更新 DeepSeek Harness 版本",
|
|
194
|
+
"gate.body": "dsh-context 插件支持 DeepSeek Harness v{minimum} 或更新版本。",
|
|
195
|
+
"gate.current": "当前版本",
|
|
196
|
+
"gate.minimum": "最低要求",
|
|
197
|
+
"gate.orNewer": "及更新的版本",
|
|
198
|
+
"gate.ok": "知道了",
|
|
192
199
|
"footer": "估算口径:与 dsh 内置 tokenMeter 相同的固定密度启发式(约 4 字符 ≈ 1 token);「实际」为供应商上报用量。",
|
|
193
200
|
"tip.step": "第 {t} 轮 · 第{s}步",
|
|
194
201
|
"tip.turn": "第 {t} 轮 · 共 {n} 步",
|
|
@@ -364,6 +371,7 @@ window.__ModuleLoader__.load({
|
|
|
364
371
|
"brief.noInputs": "(nothing new)",
|
|
365
372
|
"brief.locate": "Reveal in Context Browser",
|
|
366
373
|
"jump.title": "View this turn in the Context tab",
|
|
374
|
+
"jump.statsLine": "View the context composition in the Context tab",
|
|
367
375
|
"brief.turnTip": "The user message that opened this turn — every step of the turn recalls it",
|
|
368
376
|
"brief.inputTip": "What entered after the previous reply and before this request went out — usually the previous tool-call results; a turn opener has nothing new",
|
|
369
377
|
"brief.replyTip": "What the model returned on this step (a text reply or tool calls) — results of calls made here appear in the In row of the next bar",
|
|
@@ -465,6 +473,12 @@ window.__ModuleLoader__.load({
|
|
|
465
473
|
"loading": "Reading the session log…",
|
|
466
474
|
"error": "Failed to read context data: ",
|
|
467
475
|
"error.retry": "Retry",
|
|
476
|
+
"gate.title": "Update DeepSeek Harness",
|
|
477
|
+
"gate.body": "dsh-context supports DeepSeek Harness v{minimum} or newer.",
|
|
478
|
+
"gate.current": "Current",
|
|
479
|
+
"gate.minimum": "Minimum required",
|
|
480
|
+
"gate.orNewer": "or newer",
|
|
481
|
+
"gate.ok": "Got it",
|
|
468
482
|
"footer": "Estimate: same fixed-density heuristic as dsh’s built-in tokenMeter (~4 chars ≈ 1 token); “actual” is provider-reported usage.",
|
|
469
483
|
"tip.step": "Turn {t} · Step {s}",
|
|
470
484
|
"tip.turn": "Turn {t} · {n} steps",
|
|
@@ -814,55 +828,36 @@ window.__ModuleLoader__.load({
|
|
|
814
828
|
//#endregion
|
|
815
829
|
//#region src/client/services.ts
|
|
816
830
|
/**
|
|
817
|
-
* A session-authorized durable-image loader over
|
|
818
|
-
* face
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
831
|
+
* A session-authorized durable-image loader over the harness conversation
|
|
832
|
+
* face (`uiConversation.imageUrl`), or undefined when the service is not
|
|
833
|
+
* composed — the caller degrades to metadata-only cards. Hostile snapshots
|
|
834
|
+
* and throwing service reads are caught: this helper can never take a
|
|
835
|
+
* render down.
|
|
822
836
|
*/
|
|
823
837
|
function imageLoaderOf(ctx, sessionId) {
|
|
824
838
|
if (typeof sessionId !== "string" || sessionId === "") return void 0;
|
|
825
839
|
try {
|
|
826
|
-
const
|
|
827
|
-
if (
|
|
828
|
-
const
|
|
829
|
-
return (attachment) => resolveImage(sessionId, attachment);
|
|
830
|
-
}
|
|
831
|
-
const modern = ctx.get("uiConversation");
|
|
832
|
-
if (modern !== void 0 && typeof modern.imageUrl === "function") {
|
|
833
|
-
const imageUrl = modern.imageUrl.bind(modern);
|
|
840
|
+
const conversation = ctx.get("uiConversation");
|
|
841
|
+
if (conversation !== void 0 && typeof conversation.imageUrl === "function") {
|
|
842
|
+
const imageUrl = conversation.imageUrl.bind(conversation);
|
|
834
843
|
return (attachment) => imageUrl(sessionId, attachment);
|
|
835
844
|
}
|
|
836
845
|
} catch {}
|
|
837
846
|
}
|
|
838
847
|
/**
|
|
839
|
-
* The conversation-window nodes this plugin joins on, from
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
* foreign harness, hostile snapshot) — callers render without the join,
|
|
844
|
-
* never an error.
|
|
845
|
-
*
|
|
846
|
-
* Hook-order contract: the seats are real React hooks, so BOTH are invoked
|
|
847
|
-
* on every call whenever present (the chat seat first, the session seat
|
|
848
|
-
* second) and only the RESULT is picked conditionally — a stable call order
|
|
849
|
-
* across renders. Selectors return stable slice references so the
|
|
850
|
-
* framework's snapshot equality can gate re-renders.
|
|
848
|
+
* The conversation-window nodes this plugin joins on, from the `useChat`
|
|
849
|
+
* seat (`ChatSnapshot.legacy.nodes`). Returns undefined when the seat does
|
|
850
|
+
* not deliver a real array (absent seat, foreign harness, hostile snapshot)
|
|
851
|
+
* — callers render without the join, never an error.
|
|
851
852
|
*/
|
|
852
853
|
function conversationNodesOf(props) {
|
|
853
854
|
const useChat = props.useChat;
|
|
854
|
-
|
|
855
|
-
|
|
855
|
+
if (typeof useChat !== "function") return void 0;
|
|
856
|
+
try {
|
|
856
857
|
const slice = useChat((s) => s !== null && typeof s === "object" ? s.legacy : void 0);
|
|
857
|
-
|
|
858
|
+
const nodes = slice !== null && typeof slice === "object" ? slice.nodes : void 0;
|
|
859
|
+
return Array.isArray(nodes) ? nodes : void 0;
|
|
858
860
|
} catch {}
|
|
859
|
-
const useSession = props.useSession;
|
|
860
|
-
let sessionNodes;
|
|
861
|
-
if (typeof useSession === "function") try {
|
|
862
|
-
sessionNodes = useSession((s) => s.nodes);
|
|
863
|
-
} catch {}
|
|
864
|
-
if (Array.isArray(chatNodes)) return chatNodes;
|
|
865
|
-
if (Array.isArray(sessionNodes)) return sessionNodes;
|
|
866
861
|
}
|
|
867
862
|
/**
|
|
868
863
|
* Narrow an unknown projection value to a string-keyed record, or null when
|
|
@@ -886,6 +881,20 @@ window.__ModuleLoader__.load({
|
|
|
886
881
|
return value.filter((v) => v !== null && typeof v === "object");
|
|
887
882
|
}
|
|
888
883
|
/**
|
|
884
|
+
* Narrow a delivered `unsupported` gate record (the host's baseline gate —
|
|
885
|
+
* see host/fallback.ts): both version strings re-proved, anything else
|
|
886
|
+
* degrades to null (no gate shown) instead of rendering garbage.
|
|
887
|
+
*/
|
|
888
|
+
function unsupportedOf(value) {
|
|
889
|
+
const data = asRecord$1(value);
|
|
890
|
+
if (data === null) return null;
|
|
891
|
+
if (typeof data.current !== "string" || typeof data.minimum !== "string") return null;
|
|
892
|
+
return {
|
|
893
|
+
current: data.current,
|
|
894
|
+
minimum: data.minimum
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
889
898
|
* Narrow a delivered projection value to a RENDER-SAFE context timeline —
|
|
890
899
|
* the client's no-white-screen guarantee against backend/parse failures.
|
|
891
900
|
*
|
|
@@ -914,8 +923,10 @@ window.__ModuleLoader__.load({
|
|
|
914
923
|
const safeCurrent = current !== null && typeof current === "object" ? current : {};
|
|
915
924
|
const cost = typeof data.cost === "object" && data.cost !== null && !Array.isArray(data.cost) ? data.cost : void 0;
|
|
916
925
|
const timing = timingOf(data.timing);
|
|
926
|
+
const unsupported = unsupportedOf(data.unsupported);
|
|
917
927
|
return {
|
|
918
928
|
ok: true,
|
|
929
|
+
...unsupported !== null ? { unsupported } : {},
|
|
919
930
|
...typeof data.model === "string" ? { model: data.model } : {},
|
|
920
931
|
...typeof data.provider === "string" ? { provider: data.provider } : {},
|
|
921
932
|
...typeof data.contextWindow === "number" ? { contextWindow: data.contextWindow } : {},
|
|
@@ -1263,109 +1274,74 @@ window.__ModuleLoader__.load({
|
|
|
1263
1274
|
}
|
|
1264
1275
|
return nodes;
|
|
1265
1276
|
}
|
|
1266
|
-
/**
|
|
1267
|
-
*
|
|
1268
|
-
|
|
1277
|
+
/** The `page` verb of a history face, re-proved and bound to its owner.
|
|
1278
|
+
* Hostile objects (any accessor backed by host state can throw) degrade to
|
|
1279
|
+
* undefined instead of escaping the read. */
|
|
1280
|
+
function readPageOf(face) {
|
|
1281
|
+
if (face === null || typeof face !== "object") return void 0;
|
|
1269
1282
|
try {
|
|
1270
|
-
|
|
1283
|
+
const fn = face.page;
|
|
1284
|
+
return typeof fn === "function" ? fn.bind(face) : void 0;
|
|
1271
1285
|
} catch {
|
|
1272
1286
|
return;
|
|
1273
1287
|
}
|
|
1274
1288
|
}
|
|
1275
1289
|
/**
|
|
1276
|
-
*
|
|
1277
|
-
* degrading at the FIRST throw. cordis's undeclared-service proxies are one
|
|
1278
|
-
* hostile object class — any accessor backed by host state can throw too —
|
|
1279
|
-
* and the no-white-screen contract needs the whole chain guarded, not just
|
|
1280
|
-
* the `ctx.get` call.
|
|
1281
|
-
*/
|
|
1282
|
-
function readKeysOf(value, ...keys) {
|
|
1283
|
-
let current = value;
|
|
1284
|
-
for (const key of keys) {
|
|
1285
|
-
if (current === null || typeof current !== "object") return void 0;
|
|
1286
|
-
try {
|
|
1287
|
-
current = current[key];
|
|
1288
|
-
} catch {
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
return current;
|
|
1293
|
-
}
|
|
1294
|
-
/** The `page` verb of a history face, re-proved and bound to its owner. */
|
|
1295
|
-
function readPageOf(face) {
|
|
1296
|
-
const fn = readKeysOf(face, "page");
|
|
1297
|
-
return typeof fn === "function" ? fn.bind(face) : void 0;
|
|
1298
|
-
}
|
|
1299
|
-
/** The `history` verb of the legacy api client face, re-proved and bound. */
|
|
1300
|
-
function readHistoryOf(face) {
|
|
1301
|
-
const sessions = readKeysOf(face, "api", "sessions");
|
|
1302
|
-
const fn = readKeysOf(sessions, "history");
|
|
1303
|
-
return typeof fn === "function" ? fn.bind(sessions) : void 0;
|
|
1304
|
-
}
|
|
1305
|
-
/**
|
|
1306
|
-
* The 0.1.2+ gateway history page verb, resolved through the DECLARED inject
|
|
1290
|
+
* The gateway history page verb, resolved through the DECLARED inject
|
|
1307
1291
|
* (see {@link watchHistoryFaces}) and bound up front: a method extracted
|
|
1308
1292
|
* unbound loses `this`, and the traced `remote` proxy that hands it out
|
|
1309
1293
|
* requires the inject to resolve at all.
|
|
1310
1294
|
*/
|
|
1311
1295
|
let declaredPage;
|
|
1312
1296
|
/**
|
|
1313
|
-
* Register the plugin's
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
* invocation leaves the slot unset — nothing here can throw).
|
|
1297
|
+
* Register the plugin's history face with the harness through the DECLARED
|
|
1298
|
+
* inject — both `remote` AND `remote.session` (the ui-chat idiom) must be in
|
|
1299
|
+
* one fiber's requirement list, because the traced `remote` proxy resolves
|
|
1300
|
+
* `.session` through the context and each name needs the other's
|
|
1301
|
+
* declaration; an undeclared read of the traced proxy throws instead of
|
|
1302
|
+
* resolving. The callback re-runs on every unload/remount, so it owns the
|
|
1303
|
+
* slot's lifetime. The face itself is re-proven: a never-fired invocation
|
|
1304
|
+
* or a hostile property read leaves the slot unset — nothing here can throw.
|
|
1322
1305
|
*/
|
|
1323
1306
|
function watchHistoryFaces(ctx) {
|
|
1324
1307
|
ctx.inject(["remote", "remote.session"], (c) => {
|
|
1325
|
-
|
|
1326
|
-
|
|
1308
|
+
try {
|
|
1309
|
+
const session = c.remote?.session;
|
|
1310
|
+
declaredPage = session !== void 0 ? readPageOf(session) : void 0;
|
|
1311
|
+
} catch {
|
|
1312
|
+
declaredPage = void 0;
|
|
1313
|
+
}
|
|
1327
1314
|
return () => {
|
|
1328
1315
|
declaredPage = void 0;
|
|
1329
1316
|
};
|
|
1330
1317
|
});
|
|
1331
1318
|
}
|
|
1332
|
-
/** The rows array of a history
|
|
1319
|
+
/** The rows array of a successful history page, under the served envelope. */
|
|
1333
1320
|
function rowsOf(response) {
|
|
1334
1321
|
let payload = response;
|
|
1335
|
-
if (payload !== null && typeof payload === "object") {
|
|
1336
|
-
const nested = payload.result;
|
|
1337
|
-
if (nested !== null && typeof nested === "object") payload = nested;
|
|
1338
|
-
}
|
|
1339
1322
|
if (payload !== null && typeof payload === "object" && "ok" in payload) {
|
|
1340
1323
|
const r = payload;
|
|
1341
1324
|
if (r.ok !== true || r.value === null || typeof r.value !== "object") throw new Error("history rpc failed");
|
|
1342
1325
|
payload = r.value;
|
|
1343
1326
|
}
|
|
1344
1327
|
if (payload === null || typeof payload !== "object") throw new Error("history rpc failed");
|
|
1345
|
-
const rows = payload.records
|
|
1328
|
+
const rows = payload.records;
|
|
1346
1329
|
if (!Array.isArray(rows)) throw new Error("history rpc failed");
|
|
1347
1330
|
return rows;
|
|
1348
1331
|
}
|
|
1349
1332
|
/**
|
|
1350
|
-
* The session's history
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
* and because a non-declared read of the traced remote proxy throws
|
|
1357
|
-
* ("cannot get property … without inject"), the page face prefers the
|
|
1358
|
-
* injection-resolved slot and only then tries the reflect read. Every
|
|
1359
|
-
* service property access degrades at its own guard instead of taking the
|
|
1360
|
-
* view down. Undefined when no face exists (older hosts) — callers keep
|
|
1361
|
-
* their static degradation.
|
|
1333
|
+
* The session's history reader over the gateway remotes (`remote.session.page`),
|
|
1334
|
+
* from the declared-inject slot. The page cuts message-aligned pages, so the
|
|
1335
|
+
* returned read covers `seq` whenever the durable log still holds it: the
|
|
1336
|
+
* inclusive cut is pinned to the seq itself, the exclusive bound one past
|
|
1337
|
+
* it. Undefined when the slot holds no face (the inject never fired or
|
|
1338
|
+
* served no usable page verb) — callers keep their static degradation.
|
|
1362
1339
|
*/
|
|
1363
|
-
function
|
|
1364
|
-
const page = declaredPage
|
|
1365
|
-
|
|
1366
|
-
if (page === void 0 && history === void 0) return void 0;
|
|
1340
|
+
function pageReaderOf(sessionId) {
|
|
1341
|
+
const page = declaredPage;
|
|
1342
|
+
if (page === void 0) return void 0;
|
|
1367
1343
|
return (seq) => {
|
|
1368
|
-
|
|
1344
|
+
return page({
|
|
1369
1345
|
address: {
|
|
1370
1346
|
kind: "session",
|
|
1371
1347
|
sessionId
|
|
@@ -1373,24 +1349,18 @@ window.__ModuleLoader__.load({
|
|
|
1373
1349
|
throughSeq: seq,
|
|
1374
1350
|
beforeSeq: seq + 1
|
|
1375
1351
|
}, new AbortController().signal);
|
|
1376
|
-
return history({
|
|
1377
|
-
sessionId,
|
|
1378
|
-
beforeSeq: seq + 1
|
|
1379
|
-
});
|
|
1380
1352
|
};
|
|
1381
1353
|
}
|
|
1382
1354
|
/**
|
|
1383
|
-
* Build the browser's per-session fetcher over
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
* the
|
|
1388
|
-
*
|
|
1389
|
-
* hosts) — the caller keeps its static preview-plus-hint degradation. Found
|
|
1390
|
-
* nodes cache in the closure: one mount re-reading a row never re-fetches.
|
|
1355
|
+
* Build the browser's per-session fetcher over the gateway history face
|
|
1356
|
+
* (`remote.session.page`): message-aligned pages whose inclusive cut pinned
|
|
1357
|
+
* to `seq` (exclusive bound one past it) cover the seq whenever the durable
|
|
1358
|
+
* log still holds it. Undefined when no face was resolved at build time —
|
|
1359
|
+
* the caller keeps its static preview-plus-hint degradation. Found nodes
|
|
1360
|
+
* cache in the closure: one mount re-reading a row never re-fetches.
|
|
1391
1361
|
*/
|
|
1392
|
-
function makeContentFetcher(
|
|
1393
|
-
const read =
|
|
1362
|
+
function makeContentFetcher(sessionId) {
|
|
1363
|
+
const read = pageReaderOf(sessionId);
|
|
1394
1364
|
if (read === void 0) return void 0;
|
|
1395
1365
|
const cache = /* @__PURE__ */ new Map();
|
|
1396
1366
|
return async (seq) => {
|
|
@@ -1434,11 +1404,11 @@ window.__ModuleLoader__.load({
|
|
|
1434
1404
|
* mapped client-side into the renderable content. Epochs cache per session
|
|
1435
1405
|
* (history is immutable), and OLDER epochs sharing the page cache for free —
|
|
1436
1406
|
* stepping back through epochs walks the same pages. Undefined when no
|
|
1437
|
-
* history face exists
|
|
1438
|
-
*
|
|
1407
|
+
* history face exists — the browser keeps a metadata-only degradation
|
|
1408
|
+
* instead.
|
|
1439
1409
|
*/
|
|
1440
|
-
function makeHeaderFetcher(
|
|
1441
|
-
const read =
|
|
1410
|
+
function makeHeaderFetcher(sessionId) {
|
|
1411
|
+
const read = pageReaderOf(sessionId);
|
|
1442
1412
|
if (read === void 0) return void 0;
|
|
1443
1413
|
const cache = /* @__PURE__ */ new Map();
|
|
1444
1414
|
return async (seq) => {
|
|
@@ -1760,7 +1730,7 @@ window.__ModuleLoader__.load({
|
|
|
1760
1730
|
//#region src/client/components/images.tsx
|
|
1761
1731
|
/**
|
|
1762
1732
|
* Narrow an unknown content block to a durable image ref: accepts both raw message blocks (`{ type: 'image', attachment }`) and the
|
|
1763
|
-
* snapshot's assistant blocks (`{ kind: 'image', attachment }`); everything else null. Lenient on the optional facts —
|
|
1733
|
+
* snapshot's assistant blocks (`{ kind: 'image', attachment }`); everything else null. Lenient on the optional facts — imageUrl reads
|
|
1764
1734
|
* only attachmentId.
|
|
1765
1735
|
*/
|
|
1766
1736
|
function imageRefOf(block) {
|
|
@@ -2913,8 +2883,8 @@ window.__ModuleLoader__.load({
|
|
|
2913
2883
|
const [hoverCat, setHoverCat] = React.useState(null);
|
|
2914
2884
|
const [dockLeft, setDockLeft] = React.useState(0);
|
|
2915
2885
|
const backdropRef = React.useRef(null);
|
|
2916
|
-
const fetchContent = React.useMemo(() => sessionId !== "" ? makeContentFetcher(
|
|
2917
|
-
const fetchHeader = React.useMemo(() => sessionId !== "" ? makeHeaderFetcher(
|
|
2886
|
+
const fetchContent = React.useMemo(() => sessionId !== "" ? makeContentFetcher(sessionId) : void 0, [sessionId]);
|
|
2887
|
+
const fetchHeader = React.useMemo(() => sessionId !== "" ? makeHeaderFetcher(sessionId) : void 0, [sessionId]);
|
|
2918
2888
|
const close = React.useCallback(() => {
|
|
2919
2889
|
if (sessionId === "") return;
|
|
2920
2890
|
modalStoreOf(sessionId).set(false);
|
|
@@ -5122,7 +5092,7 @@ window.__ModuleLoader__.load({
|
|
|
5122
5092
|
return function PluginInfo() {
|
|
5123
5093
|
const [latest, setLatest] = React.useState(null);
|
|
5124
5094
|
React.useEffect(() => {
|
|
5125
|
-
if ("0.
|
|
5095
|
+
if ("0.42.0".includes("-dev")) return;
|
|
5126
5096
|
let on = true;
|
|
5127
5097
|
fetchLatestVersion().then((v) => {
|
|
5128
5098
|
if (on && v) setLatest(v);
|
|
@@ -5131,8 +5101,8 @@ window.__ModuleLoader__.load({
|
|
|
5131
5101
|
on = false;
|
|
5132
5102
|
};
|
|
5133
5103
|
}, []);
|
|
5134
|
-
const update = latest !== null && isNewerVersion(latest, "0.
|
|
5135
|
-
const nameText = "dsh-context (v0.
|
|
5104
|
+
const update = latest !== null && isNewerVersion(latest, "0.42.0") ? latest : null;
|
|
5105
|
+
const nameText = "dsh-context (v0.42.0)";
|
|
5136
5106
|
const nameValue = [nameText];
|
|
5137
5107
|
if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
|
|
5138
5108
|
key: "update",
|
|
@@ -5156,6 +5126,53 @@ window.__ModuleLoader__.load({
|
|
|
5156
5126
|
};
|
|
5157
5127
|
}
|
|
5158
5128
|
//#endregion
|
|
5129
|
+
//#region src/client/components/upgradeGate.tsx
|
|
5130
|
+
/** Sessions whose gate the user already dismissed this browser session. */
|
|
5131
|
+
const dismissed = /* @__PURE__ */ new Set();
|
|
5132
|
+
function makeUpgradeGate(kit) {
|
|
5133
|
+
const { t } = kit;
|
|
5134
|
+
return function UpgradeGate(props) {
|
|
5135
|
+
const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
|
|
5136
|
+
const [closedFor, setClosedFor] = React.useState(null);
|
|
5137
|
+
const closed = closedFor === sessionId || dismissed.has(sessionId);
|
|
5138
|
+
const close = React.useCallback(() => {
|
|
5139
|
+
dismissed.add(sessionId);
|
|
5140
|
+
setClosedFor(sessionId);
|
|
5141
|
+
}, [sessionId]);
|
|
5142
|
+
React.useEffect(() => {
|
|
5143
|
+
if (closed) return void 0;
|
|
5144
|
+
const onKey = (ev) => {
|
|
5145
|
+
if (ev.key !== "Escape") return;
|
|
5146
|
+
ev.preventDefault();
|
|
5147
|
+
ev.stopPropagation();
|
|
5148
|
+
close();
|
|
5149
|
+
};
|
|
5150
|
+
window.addEventListener("keydown", onKey, true);
|
|
5151
|
+
return () => {
|
|
5152
|
+
window.removeEventListener("keydown", onKey, true);
|
|
5153
|
+
};
|
|
5154
|
+
}, [closed, close]);
|
|
5155
|
+
if (closed) return null;
|
|
5156
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
5157
|
+
className: "lc-modal-backdrop",
|
|
5158
|
+
onClick: close
|
|
5159
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
5160
|
+
className: "lc-modal-card lc-gate-card",
|
|
5161
|
+
onClick: (ev) => {
|
|
5162
|
+
ev.stopPropagation();
|
|
5163
|
+
}
|
|
5164
|
+
}, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-head" }, /* @__PURE__ */ React.createElement("span", { className: "lc-modal-title" }, t("gate.title")), /* @__PURE__ */ React.createElement("button", {
|
|
5165
|
+
className: "lc-modal-close",
|
|
5166
|
+
"aria-label": t("cmd.close"),
|
|
5167
|
+
onClick: close
|
|
5168
|
+
}, "×")), /* @__PURE__ */ React.createElement("div", { className: "lc-gate-body" }, t("gate.body", { minimum: props.minimum })), /* @__PURE__ */ React.createElement("div", { className: "lc-gate-versions" }, /* @__PURE__ */ React.createElement("span", { className: "lc-gate-version" }, /* @__PURE__ */ React.createElement("span", { className: "lc-gate-version-label" }, t("gate.current")), /* @__PURE__ */ React.createElement("span", { className: "lc-gate-version-value" }, "v", props.current)), /* @__PURE__ */ React.createElement("span", { className: "lc-gate-version" }, /* @__PURE__ */ React.createElement("span", { className: "lc-gate-version-label" }, t("gate.minimum")), /* @__PURE__ */ React.createElement("span", { className: "lc-gate-version-value" }, "v", props.minimum, /* @__PURE__ */ React.createElement("span", { className: "lc-gate-or-newer" }, t("gate.orNewer"))))), /* @__PURE__ */ React.createElement("div", { className: "lc-gate-actions" }, /* @__PURE__ */ React.createElement("button", {
|
|
5169
|
+
type: "button",
|
|
5170
|
+
className: "lc-gate-ok",
|
|
5171
|
+
onClick: close
|
|
5172
|
+
}, t("gate.ok")))));
|
|
5173
|
+
};
|
|
5174
|
+
}
|
|
5175
|
+
//#endregion
|
|
5159
5176
|
//#region src/client/components/requestDetail.tsx
|
|
5160
5177
|
function makeRequestDetail(kit, StackedBar) {
|
|
5161
5178
|
const { t, fmt, fmtTime, catLabel, eventLabel, eventAt } = kit;
|
|
@@ -6128,6 +6145,7 @@ window.__ModuleLoader__.load({
|
|
|
6128
6145
|
const StatsTiming = makeStatsTiming(kit, Donut);
|
|
6129
6146
|
const StatsTokens = makeStatsTokens(kit, Donut);
|
|
6130
6147
|
const PluginInfo = makePluginInfo(kit);
|
|
6148
|
+
const UpgradeGate = makeUpgradeGate(kit);
|
|
6131
6149
|
const ContextBrowser = makeContextBrowser(kit, StackedBar);
|
|
6132
6150
|
const AgentGraph = makeAgentGraph(ctx, kit);
|
|
6133
6151
|
const ErrorBoundary = makeErrorBoundary(t);
|
|
@@ -6159,8 +6177,8 @@ window.__ModuleLoader__.load({
|
|
|
6159
6177
|
setNodeFocus(null);
|
|
6160
6178
|
}, []);
|
|
6161
6179
|
const loadImage = React.useMemo(() => imageLoaderOf(ctx, typeof sessionId === "string" ? sessionId : void 0), [ctx, sessionId]);
|
|
6162
|
-
const fetchContent = React.useMemo(() => typeof sessionId === "string" && sessionId !== "" ? makeContentFetcher(
|
|
6163
|
-
const fetchHeader = React.useMemo(() => typeof sessionId === "string" && sessionId !== "" ? makeHeaderFetcher(
|
|
6180
|
+
const fetchContent = React.useMemo(() => typeof sessionId === "string" && sessionId !== "" ? makeContentFetcher(sessionId) : void 0, [sessionId]);
|
|
6181
|
+
const fetchHeader = React.useMemo(() => typeof sessionId === "string" && sessionId !== "" ? makeHeaderFetcher(sessionId) : void 0, [sessionId]);
|
|
6164
6182
|
const rootRef = React.useRef(null);
|
|
6165
6183
|
const scrollerRef = React.useRef(null);
|
|
6166
6184
|
const restoredRef = React.useRef(null);
|
|
@@ -6302,6 +6320,7 @@ window.__ModuleLoader__.load({
|
|
|
6302
6320
|
const localeSvc = ctx.get("locale");
|
|
6303
6321
|
const activeLocale = localeSvc !== void 0 && typeof localeSvc.getLocale === "function" ? localeSvc.getLocale().active : "en";
|
|
6304
6322
|
const subtitle = (data.model ?? "") + (data.provider ? " · " + data.provider : "");
|
|
6323
|
+
const gate = unsupportedOf(data.unsupported);
|
|
6305
6324
|
return /* @__PURE__ */ React.createElement("div", {
|
|
6306
6325
|
className: "lc-root",
|
|
6307
6326
|
ref: rootRef
|
|
@@ -6407,7 +6426,11 @@ window.__ModuleLoader__.load({
|
|
|
6407
6426
|
billed: usage !== null ? numOf(usage.uncachedInputTokens) + numOf(usage.outputTokens) + numOf(usage.cacheReadTokens) + numOf(usage.cacheWriteTokens) : null,
|
|
6408
6427
|
requests: requests.length
|
|
6409
6428
|
}
|
|
6410
|
-
}), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer"))
|
|
6429
|
+
}), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")), gate !== null && /* @__PURE__ */ React.createElement(UpgradeGate, {
|
|
6430
|
+
sessionId,
|
|
6431
|
+
current: gate.current,
|
|
6432
|
+
minimum: gate.minimum
|
|
6433
|
+
}));
|
|
6411
6434
|
}
|
|
6412
6435
|
return function ContextView(props) {
|
|
6413
6436
|
return h(ErrorBoundary, null, h(ContextViewBody, props));
|
|
@@ -6419,9 +6442,8 @@ window.__ModuleLoader__.load({
|
|
|
6419
6442
|
* The assistant-message action that jumps to the Context tab at this reply's
|
|
6420
6443
|
* turn. Registered on the harness `conversation.chat.assistant-actions` seat
|
|
6421
6444
|
* (the icon row beside copy/branch), it receives the finalized reply's durable
|
|
6422
|
-
* message id, resolves the matching assistant node's seq off
|
|
6423
|
-
* seat
|
|
6424
|
-
* before it), records it in the viewFocus relay, and activates the Context
|
|
6445
|
+
* message id, resolves the matching assistant node's seq off the `useChat`
|
|
6446
|
+
* node seat, records it in the viewFocus relay, and activates the Context
|
|
6425
6447
|
* tab — where the jump pins the reply's TURN (see contextView's leg 2). An
|
|
6426
6448
|
* unresolvable seq still switches tabs, just without a pin; a message id that
|
|
6427
6449
|
* is not a plain string renders nothing at all.
|
|
@@ -6493,6 +6515,107 @@ window.__ModuleLoader__.load({
|
|
|
6493
6515
|
};
|
|
6494
6516
|
}
|
|
6495
6517
|
//#endregion
|
|
6518
|
+
//#region src/client/components/statsJump.tsx
|
|
6519
|
+
/** The plugin's invisible dock anchor, and the class it brands the stats row with. */
|
|
6520
|
+
const ANCHOR_CLASS = "lc-stats-jump";
|
|
6521
|
+
const ROW_CLASS = "lc-stats-jump-row";
|
|
6522
|
+
/**
|
|
6523
|
+
* The stats row beside `anchor`: the single element child that is neither
|
|
6524
|
+
* the anchor nor a tooltip bubble. Children are untrusted input — one that
|
|
6525
|
+
* throws on attribute access is skipped whole, and two live candidates at
|
|
6526
|
+
* once (another plugin's dock entry, a crash face) answer null so nothing
|
|
6527
|
+
* is ever decorated on a guess.
|
|
6528
|
+
*/
|
|
6529
|
+
function statsRowOf(anchor) {
|
|
6530
|
+
const parent = anchor.parentElement;
|
|
6531
|
+
if (parent === null) return null;
|
|
6532
|
+
let row = null;
|
|
6533
|
+
for (const child of parent.children) {
|
|
6534
|
+
try {
|
|
6535
|
+
if (child === anchor || child.getAttribute("role") === "tooltip") continue;
|
|
6536
|
+
} catch {
|
|
6537
|
+
continue;
|
|
6538
|
+
}
|
|
6539
|
+
if (row !== null) return null;
|
|
6540
|
+
row = child;
|
|
6541
|
+
}
|
|
6542
|
+
return row;
|
|
6543
|
+
}
|
|
6544
|
+
/**
|
|
6545
|
+
* Wire the dock beside `anchor`: decorate today's stats row and re-spot it
|
|
6546
|
+
* on dock churn. Returns the teardown that disconnects the observer and
|
|
6547
|
+
* strips every mark the wiring left on the row.
|
|
6548
|
+
*/
|
|
6549
|
+
function wireDock(anchor, label, activate) {
|
|
6550
|
+
if (anchor === null) return () => {};
|
|
6551
|
+
let wired = null;
|
|
6552
|
+
const unwire = () => {
|
|
6553
|
+
wired?.off();
|
|
6554
|
+
wired = null;
|
|
6555
|
+
};
|
|
6556
|
+
const scan = () => {
|
|
6557
|
+
try {
|
|
6558
|
+
const row = statsRowOf(anchor);
|
|
6559
|
+
if (wired?.row === row) return;
|
|
6560
|
+
unwire();
|
|
6561
|
+
if (row === null || !(row instanceof HTMLElement)) return;
|
|
6562
|
+
row.classList.add(ROW_CLASS);
|
|
6563
|
+
row.setAttribute("role", "button");
|
|
6564
|
+
row.setAttribute("tabindex", "0");
|
|
6565
|
+
row.setAttribute("aria-label", label);
|
|
6566
|
+
const onKey = (e) => {
|
|
6567
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
6568
|
+
e.preventDefault();
|
|
6569
|
+
activate();
|
|
6570
|
+
};
|
|
6571
|
+
const onClick = () => {
|
|
6572
|
+
const selection = anchor.ownerDocument.getSelection();
|
|
6573
|
+
if (selection !== null && !selection.isCollapsed) return;
|
|
6574
|
+
activate();
|
|
6575
|
+
};
|
|
6576
|
+
row.addEventListener("click", onClick);
|
|
6577
|
+
row.addEventListener("keydown", onKey);
|
|
6578
|
+
wired = {
|
|
6579
|
+
row,
|
|
6580
|
+
off() {
|
|
6581
|
+
row.removeEventListener("click", onClick);
|
|
6582
|
+
row.removeEventListener("keydown", onKey);
|
|
6583
|
+
row.classList.remove(ROW_CLASS);
|
|
6584
|
+
row.removeAttribute("role");
|
|
6585
|
+
row.removeAttribute("tabindex");
|
|
6586
|
+
row.removeAttribute("aria-label");
|
|
6587
|
+
}
|
|
6588
|
+
};
|
|
6589
|
+
} catch {}
|
|
6590
|
+
};
|
|
6591
|
+
scan();
|
|
6592
|
+
const parent = anchor.parentElement;
|
|
6593
|
+
if (parent === null) return unwire;
|
|
6594
|
+
const observer = new MutationObserver(scan);
|
|
6595
|
+
observer.observe(parent, { childList: true });
|
|
6596
|
+
return () => {
|
|
6597
|
+
observer.disconnect();
|
|
6598
|
+
unwire();
|
|
6599
|
+
};
|
|
6600
|
+
}
|
|
6601
|
+
/** The dock entry component: the invisible anchor plus the wiring around it. */
|
|
6602
|
+
function makeStatsJump(kit) {
|
|
6603
|
+
const { t } = kit;
|
|
6604
|
+
return function StatsJump() {
|
|
6605
|
+
const anchorRef = React.useRef(null);
|
|
6606
|
+
const label = t("jump.statsLine");
|
|
6607
|
+
React.useEffect(() => wireDock(anchorRef.current, label, () => {
|
|
6608
|
+
activateContextTab(t("tab"));
|
|
6609
|
+
}), [label]);
|
|
6610
|
+
return h("span", {
|
|
6611
|
+
ref: anchorRef,
|
|
6612
|
+
className: ANCHOR_CLASS,
|
|
6613
|
+
hidden: true,
|
|
6614
|
+
"aria-hidden": "true"
|
|
6615
|
+
});
|
|
6616
|
+
};
|
|
6617
|
+
}
|
|
6618
|
+
//#endregion
|
|
6496
6619
|
//#region src/client/viewkit.ts
|
|
6497
6620
|
function makeViewKit(t) {
|
|
6498
6621
|
const { eventLabel, eventAt } = makeEventText(t);
|
|
@@ -6531,7 +6654,7 @@ window.__ModuleLoader__.load({
|
|
|
6531
6654
|
}
|
|
6532
6655
|
//#endregion
|
|
6533
6656
|
//#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/jump.css.mjs
|
|
6534
|
-
const css$11 = ".lc-jump{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.lc-jump svg{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px))}.lc-jump:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}";
|
|
6657
|
+
const css$11 = ".lc-jump{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.lc-jump svg{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px))}.lc-jump:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.lc-stats-jump{display:none}.lc-stats-jump-row{cursor:pointer}.lc-stats-jump-row:hover,.lc-stats-jump-row:focus-visible{text-decoration:underline}";
|
|
6535
6658
|
const tagId$11 = "dsh-context/jump.css";
|
|
6536
6659
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$11) + "]") === null) {
|
|
6537
6660
|
const tag = document.createElement("style");
|
|
@@ -6608,7 +6731,7 @@ window.__ModuleLoader__.load({
|
|
|
6608
6731
|
}
|
|
6609
6732
|
//#endregion
|
|
6610
6733
|
//#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/modal.css.mjs
|
|
6611
|
-
const css$4 = ".lc-modal-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.lc-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(720px,100vw - 48px);max-height:min(82vh,760px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;overflow-y:auto}.lc-modal-head{align-items:baseline;gap:8px;margin-bottom:12px;display:flex}.lc-modal-title{font-size:14px;font-weight:600}.lc-modal-close{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:6px;margin-left:auto;padding:2px 6px;font-family:inherit;font-size:18px;line-height:1}.lc-modal-close:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}";
|
|
6734
|
+
const css$4 = ".lc-modal-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.lc-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(720px,100vw - 48px);max-height:min(82vh,760px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;overflow-y:auto}.lc-modal-head{align-items:baseline;gap:8px;margin-bottom:12px;display:flex}.lc-modal-title{font-size:14px;font-weight:600}.lc-modal-close{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:6px;margin-left:auto;padding:2px 6px;font-family:inherit;font-size:18px;line-height:1}.lc-modal-close:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.lc-gate-card{width:min(440px,100vw - 48px)}.lc-gate-body{color:var(--dsw-alias-label-secondary);line-height:1.7}.lc-gate-versions{gap:24px;margin-top:14px;display:flex}.lc-gate-version{flex-direction:column;gap:2px;display:flex}.lc-gate-version-label{color:var(--dsw-alias-label-secondary);font-size:11px}.lc-gate-version-value{font-weight:600;font-family:var(--dsw-font-family-mono,monospace);color:var(--dsw-alias-label-primary)}.lc-gate-or-newer{color:var(--dsw-alias-label-secondary);margin-left:6px;font-family:inherit;font-size:11px;font-weight:400}.lc-gate-actions{justify-content:flex-end;margin-top:16px;display:flex}.lc-gate-ok{cursor:pointer;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border:0;border-radius:6px;padding:6px 16px;font-family:inherit;font-size:12px}.lc-gate-ok:hover{filter:brightness(1.1)}";
|
|
6612
6735
|
const tagId$4 = "dsh-context/modal.css";
|
|
6613
6736
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
|
|
6614
6737
|
const tag = document.createElement("style");
|
|
@@ -6716,6 +6839,15 @@ window.__ModuleLoader__.load({
|
|
|
6716
6839
|
locale: NS
|
|
6717
6840
|
}, (props) => h(ContextJump, props));
|
|
6718
6841
|
});
|
|
6842
|
+
const StatsJump = makeStatsJump(kit);
|
|
6843
|
+
ctx.slots.inject("conversation.composer.dock", () => {
|
|
6844
|
+
return ctx.slots.register({
|
|
6845
|
+
name: "conversation.composer.dock",
|
|
6846
|
+
id: "stats-jump",
|
|
6847
|
+
order: 10,
|
|
6848
|
+
locale: NS
|
|
6849
|
+
}, (props) => h(StatsJump, props));
|
|
6850
|
+
});
|
|
6719
6851
|
registerContextCommand(ctx, kit);
|
|
6720
6852
|
const ContextModal = makeContextModal(ctx, kit);
|
|
6721
6853
|
ctx.slots.inject("conversation.input.overlay", () => {
|
package/lib/index.d.ts
CHANGED
|
@@ -186,6 +186,17 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
|
186
186
|
type Category = 'user' | 'inject' | 'assistant' | 'tool';
|
|
187
187
|
interface Snapshot {
|
|
188
188
|
ok: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* The host's baseline-gate record, present ONLY when the running harness
|
|
191
|
+
* is below the plugin's supported baseline: the host then folds nothing
|
|
192
|
+
* and every figure in this snapshot is zero/empty. The client keeps
|
|
193
|
+
* rendering the (blank) cards and pops the upgrade gate modal naming
|
|
194
|
+
* `current` (the detected harness version) and `minimum` (the baseline).
|
|
195
|
+
*/
|
|
196
|
+
unsupported?: {
|
|
197
|
+
current: string;
|
|
198
|
+
minimum: string;
|
|
199
|
+
};
|
|
189
200
|
model?: string;
|
|
190
201
|
provider?: string;
|
|
191
202
|
contextWindow?: number;
|
|
@@ -414,12 +425,13 @@ interface HeaderTool {
|
|
|
414
425
|
name: string;
|
|
415
426
|
tokens: number;
|
|
416
427
|
/**
|
|
417
|
-
* The registering plugin's label, when attribution is known:
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* the
|
|
428
|
+
* The registering plugin's label, when attribution is known: the host's
|
|
429
|
+
* best-effort attribution (`mcp:<server>` for MCP tools, or the pinned
|
|
430
|
+
* first-party package map), or a `plugin` field carried by the raw header
|
|
431
|
+
* entry — no supported-baseline harness path writes one, but the read
|
|
432
|
+
* stays defensive for foreign/newer producers. `UNKNOWN_TOOL_SOURCE` marks
|
|
433
|
+
* a tool whose provider predates the attribution hook; absent means
|
|
434
|
+
* nothing is known and the browser shows no tag.
|
|
423
435
|
*/
|
|
424
436
|
plugin?: string;
|
|
425
437
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { dirname, join, normalize } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { z } from "zod";
|
|
5
|
-
import z$1 from "@deepseek-ai/schemastery";
|
|
6
6
|
import { deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
7
|
+
import z$1 from "@deepseek-ai/schemastery";
|
|
7
8
|
/**
|
|
8
9
|
* Recover the MCP server display label from a proxied tool name, or undefined
|
|
9
10
|
* for non-MCP names. `dsh-mcp-client` names tools `mcp__<server>__<rawName>`
|
|
@@ -30,10 +31,11 @@ function pinnedSourceOf(name) {
|
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
32
33
|
* Pinned first-party tool → plugin package map (see header comment). One entry
|
|
33
|
-
* per model-facing name of the shipped tool packages
|
|
34
|
-
*
|
|
35
|
-
* harness range. Packages that mount under
|
|
36
|
-
* (bash/pwsh persistent variants,
|
|
34
|
+
* per model-facing name of the shipped tool packages in the official
|
|
35
|
+
* tool-schema catalog of the supported dsh baseline; names are verified
|
|
36
|
+
* stable across the supported harness range. Packages that mount under
|
|
37
|
+
* distinct names per composition (bash/pwsh persistent variants, the
|
|
38
|
+
* `subagent_fork` fixed-route alias) map to their primary package.
|
|
37
39
|
*/
|
|
38
40
|
const FIRST_PARTY_SOURCES = Object.freeze({
|
|
39
41
|
read: "@deepseek-ai/dsh-tool-fs",
|
|
@@ -288,6 +290,70 @@ function resolveBounds(config) {
|
|
|
288
290
|
return Config.parse(config ?? {});
|
|
289
291
|
}
|
|
290
292
|
//#endregion
|
|
293
|
+
//#region src/shared/version.ts
|
|
294
|
+
/**
|
|
295
|
+
* The harness-version gate's shared arithmetic — the supported dsh baseline
|
|
296
|
+
* and the version compare behind it. Runtime code shared by BOTH halves (the
|
|
297
|
+
* host probes and gates; the client displays what the wire record carries),
|
|
298
|
+
* so this module must stay dependency-free.
|
|
299
|
+
*
|
|
300
|
+
* The baseline mirrors the support matrix (AGENTS.md "Compatibility" and the
|
|
301
|
+
* package's `dsh.compatibility.dshReleases` declaration): the oldest dsh
|
|
302
|
+
* release this plugin works on. A harness BELOW it gets the fallback units
|
|
303
|
+
* (host/fallback.ts) instead of the real folds.
|
|
304
|
+
*/
|
|
305
|
+
/** The oldest supported dsh release (see the matrix note above). */
|
|
306
|
+
const BASELINE_DSH_VERSION = "0.1.2-rc.1";
|
|
307
|
+
/**
|
|
308
|
+
* Release-channel rank at an equal X.Y.Z: a final release outranks its
|
|
309
|
+
* release candidates, which outrank betas, which outrank alphas
|
|
310
|
+
* (正式版 > RC > Beta > Alpha).
|
|
311
|
+
*/
|
|
312
|
+
function channelRank(channel) {
|
|
313
|
+
return channel === "rc" ? 3 : channel === "beta" ? 2 : 1;
|
|
314
|
+
}
|
|
315
|
+
const RELEASE_RANK = 4;
|
|
316
|
+
/**
|
|
317
|
+
* Parse `v?[major].[minor].[patch][-(alpha|beta|rc)[.N]][+build]`, or null
|
|
318
|
+
* when the string is not that shape. Channels other than alpha/beta/rc
|
|
319
|
+
* (nightly, dev, …) do not parse — the gate fails open on them.
|
|
320
|
+
*/
|
|
321
|
+
function parseVersion(version) {
|
|
322
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta|rc)(?:\.(\d+))?)?(?:\+[0-9a-z.-]+)?$/i.exec(version.trim());
|
|
323
|
+
if (match === null) return null;
|
|
324
|
+
const channel = match[4]?.toLowerCase();
|
|
325
|
+
return {
|
|
326
|
+
major: Number(match[1]),
|
|
327
|
+
minor: Number(match[2]),
|
|
328
|
+
patch: Number(match[3]),
|
|
329
|
+
rank: channel === void 0 ? RELEASE_RANK : channelRank(channel),
|
|
330
|
+
serial: match[5] ? Number(match[5]) : 0
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Total order over parsed versions: X.Y.Z numerically first, then the
|
|
335
|
+
* channel rank, then the prerelease serial.
|
|
336
|
+
*/
|
|
337
|
+
function compareParsed(a, b) {
|
|
338
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
339
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
340
|
+
if (a.patch !== b.patch) return a.patch - b.patch;
|
|
341
|
+
if (a.rank !== b.rank) return a.rank - b.rank;
|
|
342
|
+
return a.serial - b.serial;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Whether `version` satisfies the supported baseline. FAIL OPEN by design: a
|
|
346
|
+
* version that cannot be parsed (a dev/nightly harness build) must not blank
|
|
347
|
+
* a working deployment, so it passes — the gate trips only on a proven
|
|
348
|
+
* below-baseline release.
|
|
349
|
+
*/
|
|
350
|
+
function meetsBaseline(version, baseline = BASELINE_DSH_VERSION) {
|
|
351
|
+
const v = parseVersion(version);
|
|
352
|
+
const b = parseVersion(baseline);
|
|
353
|
+
if (v === null || b === null) return true;
|
|
354
|
+
return compareParsed(v, b) >= 0;
|
|
355
|
+
}
|
|
356
|
+
//#endregion
|
|
291
357
|
//#region src/shared/imageTokens.ts
|
|
292
358
|
/**
|
|
293
359
|
* Per-image token estimate for DeepSeek's vision model — a faithful port of
|
|
@@ -622,6 +688,7 @@ const headerToolWireSchema = z.object({
|
|
|
622
688
|
tokens: z.number().int().nonnegative(),
|
|
623
689
|
plugin: z.string().optional()
|
|
624
690
|
}).strict();
|
|
691
|
+
/** Exported for the fallback unit (fallback.ts): one wire contract, one schema. */
|
|
625
692
|
const contextHeadersSchema = z.object({ headers: z.array(z.object({
|
|
626
693
|
seq: z.number(),
|
|
627
694
|
time: z.number(),
|
|
@@ -696,26 +763,6 @@ function createContextHeadersDefinition(resolve) {
|
|
|
696
763
|
};
|
|
697
764
|
}
|
|
698
765
|
//#endregion
|
|
699
|
-
//#region src/host/settings.ts
|
|
700
|
-
/** The namespace is the join key between the Host registration and the browser card. */
|
|
701
|
-
const SETTINGS_NAMESPACE = "dsh-context";
|
|
702
|
-
/** Section schema: also the wire envelope the browser scope validates against. */
|
|
703
|
-
const SettingsSchema = z$1.object({
|
|
704
|
-
defaultGranularity: z$1.union(["step", "turn"]).default("step"),
|
|
705
|
-
defaultTrendMode: z$1.union(["total", "delta"]).default("total").loose(),
|
|
706
|
-
defaultFileSort: z$1.union([
|
|
707
|
-
"count",
|
|
708
|
-
"latest",
|
|
709
|
-
"path"
|
|
710
|
-
]).default("count").loose()
|
|
711
|
-
});
|
|
712
|
-
/** Serve the namespace while a settings provider is composed; inert otherwise. */
|
|
713
|
-
function installSettings(ctx) {
|
|
714
|
-
ctx.inject(["settings"], (sctx) => {
|
|
715
|
-
sctx.settings.register(SETTINGS_NAMESPACE, SettingsSchema);
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
//#endregion
|
|
719
766
|
//#region src/host/fold.ts
|
|
720
767
|
function trimToLastTurns(requests, maxTurns) {
|
|
721
768
|
let runs = 0;
|
|
@@ -921,6 +968,25 @@ function applySurface(st, ev, type, data, message) {
|
|
|
921
968
|
return node;
|
|
922
969
|
}
|
|
923
970
|
/**
|
|
971
|
+
* One provider-reported usage bucket as a billed count, or null when the
|
|
972
|
+
* field carries no readable number. Accepts finite numbers and numeric
|
|
973
|
+
* strings; fractions round (some gateways report fractional counts) and
|
|
974
|
+
* negatives clamp to 0 — a mis-accounting gateway that reports
|
|
975
|
+
* `cached_tokens > prompt_tokens` drives the disjoint uncached-input figure
|
|
976
|
+
* below zero, and one raw figure in the state would fail the wire and state
|
|
977
|
+
* schemas' `.int().nonnegative()` gates on EVERY later delivery, permanently
|
|
978
|
+
* freezing the projection feed for the session (issue #44). NaN, infinities,
|
|
979
|
+
* and non-numeric values read as absent.
|
|
980
|
+
*/
|
|
981
|
+
function tokenCountOf(value) {
|
|
982
|
+
if (typeof value === "number") return Number.isFinite(value) ? Math.max(0, Math.round(value)) : null;
|
|
983
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
984
|
+
const parsed = Number(value);
|
|
985
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.round(parsed)) : null;
|
|
986
|
+
}
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
924
990
|
* The DeepSeek V4 model family a model name prices as — matched on the NAME
|
|
925
991
|
* alone (provider-agnostic: official API, proxies, OpenRouter spellings like
|
|
926
992
|
* `deepseek/deepseek-v4-flash` all land here). Null for any other model:
|
|
@@ -949,7 +1015,9 @@ function isPeakUtc(time) {
|
|
|
949
1015
|
/**
|
|
950
1016
|
* Fold one billed request into the session-cost totals, cloning along the
|
|
951
1017
|
* mutated path only (the untouched branch stays shared with the persisted
|
|
952
|
-
* previous state — the apply contract never mutates it in place).
|
|
1018
|
+
* previous state — the apply contract never mutates it in place). The
|
|
1019
|
+
* buckets arrive sanitized ({@link BilledUsage}), so the totals stay at the
|
|
1020
|
+
* schemas' non-negative safe integers no matter what the provider reported.
|
|
953
1021
|
*/
|
|
954
1022
|
function accumulateCost(st, time, usage) {
|
|
955
1023
|
const family = costFamilyOf(st.model);
|
|
@@ -965,13 +1033,10 @@ function accumulateCost(st, time, usage) {
|
|
|
965
1033
|
};
|
|
966
1034
|
const nextFam = { ...fam };
|
|
967
1035
|
nextFam[period] = {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
cacheRead: b.cacheRead + (usage.cacheReadTokens ?? 0),
|
|
973
|
-
cacheWrite: b.cacheWrite + (usage.cacheWriteTokens ?? 0),
|
|
974
|
-
output: b.output + (usage.outputTokens ?? 0)
|
|
1036
|
+
uncached: b.uncached + usage.input,
|
|
1037
|
+
cacheRead: b.cacheRead + usage.cacheRead,
|
|
1038
|
+
cacheWrite: b.cacheWrite + usage.cacheWrite,
|
|
1039
|
+
output: b.output + usage.output
|
|
975
1040
|
};
|
|
976
1041
|
const next = { ...prev };
|
|
977
1042
|
next[family] = nextFam;
|
|
@@ -1194,11 +1259,22 @@ function applyTimeline(state, event, bounds) {
|
|
|
1194
1259
|
};
|
|
1195
1260
|
if (data && typeof data.turn === "number") record.turn = data.turn;
|
|
1196
1261
|
if (data && typeof data.step === "number") record.step = data.step;
|
|
1197
|
-
if (usage && typeof usage
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1262
|
+
if (usage !== null && typeof usage === "object") {
|
|
1263
|
+
const input = tokenCountOf(usage.inputTokens);
|
|
1264
|
+
const cacheRead = tokenCountOf(usage.cacheReadTokens);
|
|
1265
|
+
const cacheWrite = tokenCountOf(usage.cacheWriteTokens);
|
|
1266
|
+
const output = tokenCountOf(usage.outputTokens);
|
|
1267
|
+
if (input !== null || cacheRead !== null || cacheWrite !== null || output !== null) {
|
|
1268
|
+
record.prompt = (input ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0);
|
|
1269
|
+
if (cacheRead !== null) record.cacheRead = cacheRead;
|
|
1270
|
+
if (output !== null) record.output = output;
|
|
1271
|
+
accumulateCost(s, event.time, {
|
|
1272
|
+
input: input ?? 0,
|
|
1273
|
+
cacheRead: cacheRead ?? 0,
|
|
1274
|
+
cacheWrite: cacheWrite ?? 0,
|
|
1275
|
+
output: output ?? 0
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1202
1278
|
}
|
|
1203
1279
|
s.requests.push(record);
|
|
1204
1280
|
const timing = ensureTiming(s);
|
|
@@ -1441,8 +1517,15 @@ const timingTotalsSchema = z.object({
|
|
|
1441
1517
|
toolCalls: z.number().int().nonnegative(),
|
|
1442
1518
|
tools: z.record(z.string(), toolTimingSchema)
|
|
1443
1519
|
}).strict();
|
|
1520
|
+
/** The baseline-gate record the fallback unit serves (see fallback.ts). */
|
|
1521
|
+
const unsupportedSchema = z.object({
|
|
1522
|
+
current: z.string(),
|
|
1523
|
+
minimum: z.string()
|
|
1524
|
+
}).strict();
|
|
1525
|
+
/** Exported for the fallback unit (fallback.ts): one wire contract, one schema. */
|
|
1444
1526
|
const contextTimelineSchema = z.object({
|
|
1445
1527
|
ok: z.literal(true),
|
|
1528
|
+
unsupported: unsupportedSchema.optional(),
|
|
1446
1529
|
model: z.string().optional(),
|
|
1447
1530
|
provider: z.string().optional(),
|
|
1448
1531
|
contextWindow: z.number().optional(),
|
|
@@ -1463,7 +1546,7 @@ const contextTimelineSchema = z.object({
|
|
|
1463
1546
|
archiveFloor: z.number().int().nonnegative().optional()
|
|
1464
1547
|
}).strict();
|
|
1465
1548
|
/**
|
|
1466
|
-
* The persisted fold-state schema (the
|
|
1549
|
+
* The persisted fold-state schema (the registry's `stateSchema`
|
|
1467
1550
|
* contract). Validates the plain-JSON `TimelineState` before a checkpoint
|
|
1468
1551
|
* row seeds a fold — the same shape guarantee the projection cache's
|
|
1469
1552
|
* plain-JSON precondition already enforces at write time.
|
|
@@ -1533,14 +1616,233 @@ function createContextTimelineDefinition(config) {
|
|
|
1533
1616
|
},
|
|
1534
1617
|
init: () => createTimelineState(),
|
|
1535
1618
|
apply: (state, event) => applyTimeline(state, event, bounds),
|
|
1536
|
-
stateVersion:
|
|
1619
|
+
stateVersion: 13
|
|
1537
1620
|
};
|
|
1538
1621
|
}
|
|
1539
1622
|
//#endregion
|
|
1623
|
+
//#region src/host/fallback.ts
|
|
1624
|
+
/**
|
|
1625
|
+
* The baseline gate's fallback projection units (see host/index.ts).
|
|
1626
|
+
*
|
|
1627
|
+
* On a harness below the supported baseline (shared/version.ts) the plugin
|
|
1628
|
+
* registers these INSTEAD of the real folds: the log's event shapes on such
|
|
1629
|
+
* a harness are outside the compat matrix, so nothing is parsed at all —
|
|
1630
|
+
* `apply` is the identity over an opaque empty state, and `view` serves a
|
|
1631
|
+
* fixed value whose timeline snapshot carries the gate record (`unsupported`:
|
|
1632
|
+
* the detected harness version and the baseline). The client's cards render
|
|
1633
|
+
* the blank data and its gate modal urges the upgrade; the registry pipeline
|
|
1634
|
+
* (fold, cache, push feed) keeps working end to end, so nothing hangs on a
|
|
1635
|
+
* loading screen.
|
|
1636
|
+
*
|
|
1637
|
+
* The definition carries BOTH registry contract generations: the modern
|
|
1638
|
+
* `stateSchema` + `wire` block (dsh 0.1.1-rc.1+, the realistic below-baseline
|
|
1639
|
+
* case) and the pre-0.1.1 top-level `schema` + `view` aliases that line's
|
|
1640
|
+
* registry reads instead. Each registry ignores the other generation's
|
|
1641
|
+
* fields, so one definition serves the gate on every harness that can
|
|
1642
|
+
* deliver projections to clients at all.
|
|
1643
|
+
*
|
|
1644
|
+
* `stateVersion` is pinned at 1. Downgrade/upgrade cache choreography: a
|
|
1645
|
+
* downgrade to a gated harness refolds the timeline key from scratch (no
|
|
1646
|
+
* ver-1 rows exist for it) and seeds the headers key from the real unit's
|
|
1647
|
+
* ver-1 rows — stripped to the empty state, which the gate never reads. An
|
|
1648
|
+
* upgrade back discards the fallback's ver-1 timeline rows and REJECTS its
|
|
1649
|
+
* ver-1 headers rows at the real unit's stricter state schema, refolding
|
|
1650
|
+
* both from the log. Nothing stale survives in either direction.
|
|
1651
|
+
*/
|
|
1652
|
+
/**
|
|
1653
|
+
* The opaque fold state: the gate folds nothing, so any cached row seeds it
|
|
1654
|
+
* (strip-mode object — never a discard, never a throw), and the plain-JSON
|
|
1655
|
+
* cache-write gate trivially holds.
|
|
1656
|
+
*/
|
|
1657
|
+
const fallbackStateSchema = z.object({});
|
|
1658
|
+
/** One gate unit: identity fold over the opaque state, constant view, both contract generations. */
|
|
1659
|
+
function fallbackDefinition(key, wireSchema, value) {
|
|
1660
|
+
const view = () => value;
|
|
1661
|
+
return {
|
|
1662
|
+
key,
|
|
1663
|
+
stateSchema: fallbackStateSchema,
|
|
1664
|
+
init: () => ({}),
|
|
1665
|
+
apply: (state) => state,
|
|
1666
|
+
wire: {
|
|
1667
|
+
viewSchema: wireSchema,
|
|
1668
|
+
view
|
|
1669
|
+
},
|
|
1670
|
+
schema: wireSchema,
|
|
1671
|
+
view,
|
|
1672
|
+
stateVersion: 1
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* The fallback `contextTimeline` unit: serves the fixed zeroed snapshot
|
|
1677
|
+
* naming the detected harness `current` version against the baseline.
|
|
1678
|
+
*/
|
|
1679
|
+
function createFallbackTimelineDefinition(current) {
|
|
1680
|
+
return fallbackDefinition("contextTimeline", contextTimelineSchema, {
|
|
1681
|
+
ok: true,
|
|
1682
|
+
unsupported: {
|
|
1683
|
+
current,
|
|
1684
|
+
minimum: BASELINE_DSH_VERSION
|
|
1685
|
+
},
|
|
1686
|
+
current: {
|
|
1687
|
+
system: 0,
|
|
1688
|
+
tools: 0,
|
|
1689
|
+
user: 0,
|
|
1690
|
+
inject: 0,
|
|
1691
|
+
assistant: 0,
|
|
1692
|
+
tool: 0,
|
|
1693
|
+
total: 0
|
|
1694
|
+
},
|
|
1695
|
+
requests: [],
|
|
1696
|
+
events: [],
|
|
1697
|
+
nodes: [],
|
|
1698
|
+
droppedNodes: 0,
|
|
1699
|
+
archive: []
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* The fallback `contextHeaders` unit: an empty epoch list — the browser's
|
|
1704
|
+
* header sections degrade to their metadata-free rendering.
|
|
1705
|
+
*/
|
|
1706
|
+
function createFallbackHeadersDefinition() {
|
|
1707
|
+
return fallbackDefinition("contextHeaders", contextHeadersSchema, { headers: [] });
|
|
1708
|
+
}
|
|
1709
|
+
//#endregion
|
|
1710
|
+
//#region src/host/settings.ts
|
|
1711
|
+
/** The namespace is the join key between the Host registration and the browser card. */
|
|
1712
|
+
const SETTINGS_NAMESPACE = "dsh-context";
|
|
1713
|
+
/** Section schema: also the wire envelope the browser scope validates against. */
|
|
1714
|
+
const SettingsSchema = z$1.object({
|
|
1715
|
+
defaultGranularity: z$1.union(["step", "turn"]).default("step"),
|
|
1716
|
+
defaultTrendMode: z$1.union(["total", "delta"]).default("total").loose(),
|
|
1717
|
+
defaultFileSort: z$1.union([
|
|
1718
|
+
"count",
|
|
1719
|
+
"latest",
|
|
1720
|
+
"path"
|
|
1721
|
+
]).default("count").loose()
|
|
1722
|
+
});
|
|
1723
|
+
/** Serve the namespace while a settings provider is composed; inert otherwise. */
|
|
1724
|
+
function installSettings(ctx) {
|
|
1725
|
+
ctx.inject(["settings"], (sctx) => {
|
|
1726
|
+
sctx.settings.register(SETTINGS_NAMESPACE, SettingsSchema);
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
//#endregion
|
|
1730
|
+
//#region src/host/version.ts
|
|
1731
|
+
/**
|
|
1732
|
+
* Runtime harness-version probe behind the baseline gate (host/index.ts).
|
|
1733
|
+
*
|
|
1734
|
+
* The probe never imports a harness package for its version; it reads
|
|
1735
|
+
* manifests through Node resolution from two anchors, in order:
|
|
1736
|
+
*
|
|
1737
|
+
* 1. The harness home's healed `profiles/node_modules` mirror (located via
|
|
1738
|
+
* the app-boot `dshHomePath` service) — it symlinks/proxies the running
|
|
1739
|
+
* installation's dependency closure, so it names the true release even
|
|
1740
|
+
* when this plugin is a `link:`-installed dev checkout.
|
|
1741
|
+
* 2. This module itself — in a real profile install the plugin sits inside
|
|
1742
|
+
* the profile's node_modules and walk-up resolution reaches the same
|
|
1743
|
+
* mirror; this anchor also covers harnesses too old to provide the home
|
|
1744
|
+
* service.
|
|
1745
|
+
*
|
|
1746
|
+
* The `@deepseek-ai/dsh` CLI package is probed ONLY through the home anchor:
|
|
1747
|
+
* the plugin never depends on it, so a hit from the module anchor can only
|
|
1748
|
+
* be an ambient install above the plugin's tree (e.g. a global copy under
|
|
1749
|
+
* ~/node_modules) — not necessarily the RUNNING harness.
|
|
1750
|
+
*
|
|
1751
|
+
* Every step is guarded: any failure (absent service, unresolvable package,
|
|
1752
|
+
* unreadable/invalid manifest, non-string version) degrades to `undefined`,
|
|
1753
|
+
* and the gate treats an unknown version as SATISFIED — a probe misfire must
|
|
1754
|
+
* never blank a working deployment.
|
|
1755
|
+
*/
|
|
1756
|
+
/**
|
|
1757
|
+
* Library packages whose manifest version IS the harness release version
|
|
1758
|
+
* (the dsh monorepo versions every package in lockstep). From the plugin's
|
|
1759
|
+
* own tree they resolve to its pinned devDependencies in dev and to the
|
|
1760
|
+
* healed profiles mirror in real installs — both correct — so they are safe
|
|
1761
|
+
* to probe from either anchor.
|
|
1762
|
+
*/
|
|
1763
|
+
const LIBRARY_PROBE_PACKAGES = ["@deepseek-ai/dsh-session-projection", "@deepseek-ai/dsh-session"];
|
|
1764
|
+
/** Home-anchor probe order: the user-facing CLI version first, then the libraries. */
|
|
1765
|
+
const HOME_PROBE_PACKAGES = ["@deepseek-ai/dsh", ...LIBRARY_PROBE_PACKAGES];
|
|
1766
|
+
/** One manifest's `version`, or undefined on any read/shape failure. */
|
|
1767
|
+
function versionOfManifest(manifestPath, expectedName) {
|
|
1768
|
+
try {
|
|
1769
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
1770
|
+
if (parsed === null || typeof parsed !== "object") return void 0;
|
|
1771
|
+
const record = parsed;
|
|
1772
|
+
if (expectedName !== void 0 && record.name !== expectedName) return void 0;
|
|
1773
|
+
return typeof record.version === "string" && record.version !== "" ? record.version : void 0;
|
|
1774
|
+
} catch {
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
/** Probe the package's published `./package.json` subpath. */
|
|
1779
|
+
function versionViaManifest(resolve, packageName) {
|
|
1780
|
+
try {
|
|
1781
|
+
return versionOfManifest(resolve(packageName + "/package.json"));
|
|
1782
|
+
} catch {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Probe via the package's entry point, ascending to its owning manifest.
|
|
1788
|
+
* Covers the packaged-executable module proxies: their generated manifests
|
|
1789
|
+
* carry the real version but export only entry stubs, and Node's exports
|
|
1790
|
+
* gate refuses the direct `./package.json` subpath.
|
|
1791
|
+
*/
|
|
1792
|
+
function versionViaEntry(resolve, packageName) {
|
|
1793
|
+
let dir;
|
|
1794
|
+
try {
|
|
1795
|
+
dir = dirname(resolve(packageName));
|
|
1796
|
+
} catch {
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
for (;;) {
|
|
1800
|
+
const version = versionOfManifest(join(dir, "package.json"), packageName);
|
|
1801
|
+
if (version !== void 0) return version;
|
|
1802
|
+
const parent = dirname(dir);
|
|
1803
|
+
if (parent === dir) return void 0;
|
|
1804
|
+
dir = parent;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
/** One anchor's answer: the first probe package that yields a version. */
|
|
1808
|
+
function probeAnchor(resolve, packageNames) {
|
|
1809
|
+
for (const packageName of packageNames) {
|
|
1810
|
+
const version = versionViaManifest(resolve, packageName) ?? versionViaEntry(resolve, packageName);
|
|
1811
|
+
if (version !== void 0) return version;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* The running harness's version string, or undefined when nothing answers
|
|
1816
|
+
* (the gate fails open on it — see the header note).
|
|
1817
|
+
* @param selfUrl - the module anchor (injectable for hermetic tests).
|
|
1818
|
+
*/
|
|
1819
|
+
function detectHarnessVersion(ctx, selfUrl = import.meta.url) {
|
|
1820
|
+
try {
|
|
1821
|
+
const homePath = ctx.get("dshHomePath");
|
|
1822
|
+
if (typeof homePath === "function") {
|
|
1823
|
+
const req = createRequire(homePath("profiles", "dsh-context-version-probe.cjs"));
|
|
1824
|
+
const home = probeAnchor((specifier) => req.resolve(specifier), HOME_PROBE_PACKAGES);
|
|
1825
|
+
if (home !== void 0) return home;
|
|
1826
|
+
}
|
|
1827
|
+
} catch {}
|
|
1828
|
+
try {
|
|
1829
|
+
const req = createRequire(selfUrl);
|
|
1830
|
+
return probeAnchor((specifier) => req.resolve(specifier), LIBRARY_PROBE_PACKAGES);
|
|
1831
|
+
} catch {
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
//#endregion
|
|
1540
1836
|
//#region src/host/index.ts
|
|
1541
1837
|
const name = "dsh-context";
|
|
1542
1838
|
const inject = ["sessionProjections"];
|
|
1543
1839
|
function apply(ctx, config) {
|
|
1840
|
+
const harnessVersion = detectHarnessVersion(ctx);
|
|
1841
|
+
if (harnessVersion !== void 0 && !meetsBaseline(harnessVersion)) {
|
|
1842
|
+
ctx.sessionProjections.register(createFallbackTimelineDefinition(harnessVersion));
|
|
1843
|
+
ctx.sessionProjections.register(createFallbackHeadersDefinition());
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1544
1846
|
const attribution = createToolAttribution(ctx);
|
|
1545
1847
|
ctx.sessionProjections.register(createContextTimelineDefinition(config));
|
|
1546
1848
|
ctx.sessionProjections.register(createContextHeadersDefinition((name) => attribution.ownerOf(name)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.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
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|
|
@@ -62,11 +62,6 @@
|
|
|
62
62
|
},
|
|
63
63
|
"compatibility": {
|
|
64
64
|
"dshReleases": {
|
|
65
|
-
"0.1.1-rc.2": "compatible",
|
|
66
|
-
"0.1.2-alpha.2": "compatible",
|
|
67
|
-
"0.1.2-alpha.3": "compatible",
|
|
68
|
-
"0.1.2-alpha.4": "compatible",
|
|
69
|
-
"0.1.2-alpha.5": "compatible",
|
|
70
65
|
"0.1.2-rc.1": "compatible"
|
|
71
66
|
}
|
|
72
67
|
}
|
|
@@ -85,11 +80,11 @@
|
|
|
85
80
|
"zod": "^4.4.3"
|
|
86
81
|
},
|
|
87
82
|
"peerDependencies": {
|
|
88
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
89
|
-
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.
|
|
90
|
-
"@deepseek-ai/dsh-session": ">=0.1.
|
|
91
|
-
"@deepseek-ai/dsh-settings": ">=0.1.
|
|
92
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
83
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
84
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.2-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-session": ">=0.1.2-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-settings": ">=0.1.2-rc.1",
|
|
87
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
93
88
|
"react": "^18.3.1"
|
|
94
89
|
},
|
|
95
90
|
"peerDependenciesMeta": {
|
|
@@ -101,12 +96,14 @@
|
|
|
101
96
|
}
|
|
102
97
|
},
|
|
103
98
|
"devDependencies": {
|
|
104
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
105
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-
|
|
106
|
-
"@deepseek-ai/dsh-
|
|
107
|
-
"@deepseek-ai/dsh-
|
|
108
|
-
"@deepseek-ai/dsh-
|
|
109
|
-
"@deepseek-ai/
|
|
99
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
|
|
101
|
+
"@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
|
|
102
|
+
"@deepseek-ai/dsh-scope": "0.1.2-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
|
105
|
+
"@deepseek-ai/dsh-settings": "0.1.2-rc.1",
|
|
106
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
110
107
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
111
108
|
"@types/node": "^26.2.0",
|
|
112
109
|
"@types/react": "^18.3.31",
|