dsh-context 0.44.0 → 0.45.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/lib/client.js +2613 -1255
- package/lib/index.js +80 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
7
7
|
import z$1 from "@deepseek-ai/schemastery";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
8
9
|
/**
|
|
9
10
|
* Recover the MCP server display label from a proxied tool name, or undefined
|
|
10
11
|
* for non-MCP names. `dsh-mcp-client` names tools `mcp__<server>__<rawName>`
|
|
@@ -2305,6 +2306,84 @@ function installSettings(ctx) {
|
|
|
2305
2306
|
});
|
|
2306
2307
|
}
|
|
2307
2308
|
//#endregion
|
|
2309
|
+
//#region src/host/stepIdentity.ts
|
|
2310
|
+
/**
|
|
2311
|
+
* Step-boundary message identity guard (issue #51 compatibility).
|
|
2312
|
+
*
|
|
2313
|
+
* The harness refuses to LOAD any session whose durable log carries a
|
|
2314
|
+
* `user/message` event whose message lacks a non-empty string `id`
|
|
2315
|
+
* (`assertMessageEventShape`) — while the runtime append path runs no such
|
|
2316
|
+
* check. One unidentified message therefore persists silently and permanently
|
|
2317
|
+
* bricks the session at its next load (`... failed validation: session event
|
|
2318
|
+
* at seq N lacks an identified message`). The observed archive held a wrap-up
|
|
2319
|
+
* notice whose `id` (and `summary`) had been stripped upstream of the append —
|
|
2320
|
+
* a shape no shipped harness producer emits, i.e. a delivery-boundary rebuild
|
|
2321
|
+
* outside the audited paths of both this plugin and the harness.
|
|
2322
|
+
*
|
|
2323
|
+
* This guard hardens the durability boundary from the plugin side. The agent
|
|
2324
|
+
* loop appends every step-boundary `user/message` from the `agent/pre-step`
|
|
2325
|
+
* decision's `messages` — the one seam all claimed inbox input (prompts,
|
|
2326
|
+
* steering, tool-deferred context) flows through before it persists. A
|
|
2327
|
+
* prepended listener sits OUTERMOST in that waterfall, so after `next()`
|
|
2328
|
+
* resolves it sees the final message list: any message that would persist
|
|
2329
|
+
* unidentified gets a fresh id; everything else passes by reference, and the
|
|
2330
|
+
* decision object is copied only when a mint happened.
|
|
2331
|
+
*
|
|
2332
|
+
* Fail-open by contract: a hostile entry (one that throws on property access),
|
|
2333
|
+
* a missing list, or any unexpected shape leaves the decision verbatim — a
|
|
2334
|
+
* guard must never break the turn it protects. Minted ids carry the `dshctx-`
|
|
2335
|
+
* prefix so a backfilled message stays identifiable in the wild.
|
|
2336
|
+
*/
|
|
2337
|
+
/** Prefix marking an id this guard minted (forensically distinguishable). */
|
|
2338
|
+
const MINTED_ID_PREFIX = "dshctx-";
|
|
2339
|
+
/**
|
|
2340
|
+
* Whether the message fails the harness's restore-time identity check (a
|
|
2341
|
+
* non-empty string id). Non-object entries are unfixable (an id needs a
|
|
2342
|
+
* container) and stay verbatim.
|
|
2343
|
+
*/
|
|
2344
|
+
function lacksId(message) {
|
|
2345
|
+
if (typeof message !== "object" || message === null) return false;
|
|
2346
|
+
const id = message.id;
|
|
2347
|
+
return typeof id !== "string" || id === "";
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Mint ids for the messages that would persist unidentified.
|
|
2351
|
+
* @param messages - the decision's message list, in append order.
|
|
2352
|
+
* @returns the rewritten list (untouched entries by reference), or undefined
|
|
2353
|
+
* when every entry already carries an identity.
|
|
2354
|
+
*/
|
|
2355
|
+
function identifiedMessages(messages) {
|
|
2356
|
+
let copy;
|
|
2357
|
+
for (const [index, message] of messages.entries()) {
|
|
2358
|
+
if (!lacksId(message)) {
|
|
2359
|
+
copy?.push(message);
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
copy ??= messages.slice(0, index);
|
|
2363
|
+
copy.push({
|
|
2364
|
+
...message,
|
|
2365
|
+
id: MINTED_ID_PREFIX + randomUUID()
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
return copy;
|
|
2369
|
+
}
|
|
2370
|
+
/** Arm the prepended `agent/pre-step` guard on the plugin's context. */
|
|
2371
|
+
function watchStepIdentity(ctx) {
|
|
2372
|
+
ctx.on("agent/pre-step", async (_input, next) => {
|
|
2373
|
+
const decision = await next();
|
|
2374
|
+
try {
|
|
2375
|
+
if (decision.kind !== "enter" || !Array.isArray(decision.messages)) return decision;
|
|
2376
|
+
const identified = identifiedMessages(decision.messages);
|
|
2377
|
+
return identified === void 0 ? decision : {
|
|
2378
|
+
...decision,
|
|
2379
|
+
messages: identified
|
|
2380
|
+
};
|
|
2381
|
+
} catch {
|
|
2382
|
+
return decision;
|
|
2383
|
+
}
|
|
2384
|
+
}, { prepend: true });
|
|
2385
|
+
}
|
|
2386
|
+
//#endregion
|
|
2308
2387
|
//#region src/host/version.ts
|
|
2309
2388
|
/**
|
|
2310
2389
|
* Runtime harness-version probe behind the baseline gate (host/index.ts).
|
|
@@ -2422,6 +2501,7 @@ function apply(ctx, config) {
|
|
|
2422
2501
|
return;
|
|
2423
2502
|
}
|
|
2424
2503
|
const attribution = createToolAttribution(ctx);
|
|
2504
|
+
watchStepIdentity(ctx);
|
|
2425
2505
|
const gate = watchDetailChannel(ctx, resolveBounds(config));
|
|
2426
2506
|
ctx.sessionProjections.register(createContextTimelineDefinition(config, () => gate.live));
|
|
2427
2507
|
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.45.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": {
|