dsh-session-bridge 0.3.0 → 0.3.1
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/dsh.plugin.json +2 -2
- package/lib/index.js +259 -52
- package/package.json +20 -20
- package/src/core.ts +77 -13
- package/src/tools.ts +20 -16
package/dsh.plugin.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-session-bridge",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"main": "./lib/index.js",
|
|
5
5
|
"description": "会话桥:创建主会话 / 向任意会话发消息 / 等待回复 / 读取消息 / 按名或 id 查找会话(支持跨工作区);另含监控/调度主任务与归档会话。",
|
|
6
6
|
"engines": {
|
|
7
|
-
"dsh": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0"
|
|
7
|
+
"dsh": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0"
|
|
8
8
|
},
|
|
9
9
|
"contributes": {
|
|
10
10
|
"tools": [
|
package/lib/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-home-paths@0.1.
|
|
7
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-home-paths@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-home-paths/lib/index.js
|
|
8
8
|
/**
|
|
9
9
|
* Shared filesystem path helpers for DeepSeek Harness user data.
|
|
10
10
|
*
|
|
@@ -2200,7 +2200,7 @@ var Service = class Service {
|
|
|
2200
2200
|
}
|
|
2201
2201
|
};
|
|
2202
2202
|
//#endregion
|
|
2203
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.1.
|
|
2203
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
|
|
2204
2204
|
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
2205
2205
|
/**
|
|
2206
2206
|
* One Remote call failure: a real Error carrying its stable code and typed
|
|
@@ -2345,7 +2345,7 @@ function validateName(subject, value) {
|
|
|
2345
2345
|
if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
|
|
2346
2346
|
}
|
|
2347
2347
|
//#endregion
|
|
2348
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.1.
|
|
2348
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
|
|
2349
2349
|
/** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
|
|
2350
2350
|
/**
|
|
2351
2351
|
* Mark an unreachable closed-union branch.
|
|
@@ -2557,7 +2557,7 @@ function deepFreeze(value) {
|
|
|
2557
2557
|
return value;
|
|
2558
2558
|
}
|
|
2559
2559
|
//#endregion
|
|
2560
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypto@0.1.
|
|
2560
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypto@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
|
|
2561
2561
|
/**
|
|
2562
2562
|
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
2563
2563
|
* @returns the UUID string.
|
|
@@ -2570,16 +2570,16 @@ function randomUUID$1() {
|
|
|
2570
2570
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
2571
2571
|
}
|
|
2572
2572
|
//#endregion
|
|
2573
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.
|
|
2573
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-brand/lib/index.js
|
|
2574
2574
|
/**
|
|
2575
|
-
* Duplicate-install-safe nominal
|
|
2575
|
+
* Duplicate-install-safe nominal primitive helpers.
|
|
2576
2576
|
*
|
|
2577
|
-
* A brand makes structurally
|
|
2578
|
-
* level: a `SessionId` cannot be passed where a `ToolCallId` is
|
|
2579
|
-
*
|
|
2580
|
-
* serialization
|
|
2577
|
+
* A brand makes structurally identical strings or numbers non-interchangeable
|
|
2578
|
+
* at the type level: a `SessionId` cannot be passed where a `ToolCallId` is
|
|
2579
|
+
* expected, and an event sequence cannot be passed as a log offset. Comparison,
|
|
2580
|
+
* logging, and serialization retain the underlying primitive behavior.
|
|
2581
2581
|
*
|
|
2582
|
-
* This package owns no concrete
|
|
2582
|
+
* This package owns no concrete domain value and keeps no runtime identity or mutable
|
|
2583
2583
|
* state, so independently installed copies produce interchangeable values.
|
|
2584
2584
|
*
|
|
2585
2585
|
* @module @deepseek-ai/dsh-brand
|
|
@@ -3187,11 +3187,19 @@ defineMethod("transform", [
|
|
|
3187
3187
|
"preserve"
|
|
3188
3188
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
3189
3189
|
//#endregion
|
|
3190
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.1.
|
|
3190
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
|
|
3191
3191
|
/** Largest delay Node schedules without clamping it to one millisecond. */
|
|
3192
3192
|
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
3193
3193
|
//#endregion
|
|
3194
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.
|
|
3194
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
3195
|
+
/**
|
|
3196
|
+
* Bound one `notice` summary to {@link CONTEXT_SUMMARY_MAX_CHARS}.
|
|
3197
|
+
* @param summary - the producer's one-line account, of any length.
|
|
3198
|
+
* @returns the account, ellipsized when it exceeds the bound.
|
|
3199
|
+
*/
|
|
3200
|
+
function boundContextSummary(summary) {
|
|
3201
|
+
return summary.length <= 120 ? summary : `${summary.slice(0, 119)}…`;
|
|
3202
|
+
}
|
|
3195
3203
|
/**
|
|
3196
3204
|
* Detach and deep-freeze a message whose identity already exists.
|
|
3197
3205
|
* @param message - complete message, including its stable identity.
|
|
@@ -3457,6 +3465,9 @@ function errorMessage$1(error) {
|
|
|
3457
3465
|
function harnessErrorCode(error) {
|
|
3458
3466
|
return error instanceof HarnessError ? error.code : "UNKNOWN";
|
|
3459
3467
|
}
|
|
3468
|
+
function quoted(value) {
|
|
3469
|
+
return JSON.stringify(value);
|
|
3470
|
+
}
|
|
3460
3471
|
/**
|
|
3461
3472
|
* Stable text shown to a model that cannot accept one durable image reference.
|
|
3462
3473
|
* @param ref - durable normalized attachment omitted from the request.
|
|
@@ -3476,6 +3487,76 @@ function textOnlyImageText(ref) {
|
|
|
3476
3487
|
function contentHasImage(content) {
|
|
3477
3488
|
return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
|
|
3478
3489
|
}
|
|
3490
|
+
/**
|
|
3491
|
+
* True when typed model content contains a file block, walking nested
|
|
3492
|
+
* tool-result content on the same recursion every file policy shares.
|
|
3493
|
+
* Reads current content on every call without retaining scan results.
|
|
3494
|
+
* @param content - typed model content blocks.
|
|
3495
|
+
* @returns whether any nested block is a file.
|
|
3496
|
+
*/
|
|
3497
|
+
function contentHasFile(content) {
|
|
3498
|
+
for (const block of content) if (block.type === "file" || block.type === "tool-result" && contentHasFile(block.content)) return true;
|
|
3499
|
+
return false;
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* Stable model-facing handle for one durable file reference: the address of
|
|
3503
|
+
* the verbatim stored copy and the instruction to read it on demand. This is
|
|
3504
|
+
* the only representation a provider ever receives for a file.
|
|
3505
|
+
* @param ref - durable verbatim file reference.
|
|
3506
|
+
* @param readonlyPath - execution-world path of the stored copy, when resolvable.
|
|
3507
|
+
* @returns deterministic handle text naming the file, its size, and its address.
|
|
3508
|
+
*/
|
|
3509
|
+
function fileHandleText(ref, readonlyPath) {
|
|
3510
|
+
const digest = String(ref.attachmentId).slice(7, 15);
|
|
3511
|
+
const identity = `File ${quoted(ref.name)} (${ref.bytes} bytes, sha256:${digest})`;
|
|
3512
|
+
if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
|
|
3513
|
+
return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
|
|
3514
|
+
}
|
|
3515
|
+
/** Replace every file occurrence, including nested tool results, with handle text. */
|
|
3516
|
+
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
3517
|
+
let next;
|
|
3518
|
+
for (const [index, block] of blocks.entries()) {
|
|
3519
|
+
if (block.type === "file") {
|
|
3520
|
+
next ??= blocks.slice(0, index);
|
|
3521
|
+
next.push({
|
|
3522
|
+
type: "text",
|
|
3523
|
+
text: fileHandleText(block.attachment, resolvePath(block.attachment))
|
|
3524
|
+
});
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
if (block.type === "tool-result") {
|
|
3528
|
+
const content = replaceFilesWithHandles(block.content, resolvePath);
|
|
3529
|
+
if (content !== block.content) {
|
|
3530
|
+
next ??= blocks.slice(0, index);
|
|
3531
|
+
next.push({
|
|
3532
|
+
...block,
|
|
3533
|
+
content
|
|
3534
|
+
});
|
|
3535
|
+
continue;
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
next?.push(block);
|
|
3539
|
+
}
|
|
3540
|
+
return next ?? blocks;
|
|
3541
|
+
}
|
|
3542
|
+
/**
|
|
3543
|
+
* Project durable file history into deterministic handle text for every model
|
|
3544
|
+
* route. Unlike images, no provider receives file blocks natively, so this
|
|
3545
|
+
* projection is unconditional in request assembly.
|
|
3546
|
+
* @param messages - complete request history.
|
|
3547
|
+
* @param resolvePath - resolve one reference's current execution-world read path.
|
|
3548
|
+
* @returns the original list without files, otherwise shallow message copies with handle text.
|
|
3549
|
+
*/
|
|
3550
|
+
function projectFilesToText(messages, resolvePath) {
|
|
3551
|
+
if (!messages.some((message) => contentHasFile(message.content))) return messages;
|
|
3552
|
+
return messages.map((message) => {
|
|
3553
|
+
const content = replaceFilesWithHandles(message.content, resolvePath);
|
|
3554
|
+
return content === message.content ? message : {
|
|
3555
|
+
...message,
|
|
3556
|
+
content
|
|
3557
|
+
};
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3479
3560
|
/** Replace every image occurrence, including nested tool results, for a text-only model. */
|
|
3480
3561
|
function replaceImagesForTextModel(blocks) {
|
|
3481
3562
|
let next;
|
|
@@ -3916,6 +3997,15 @@ var LlmError = class extends HarnessError {
|
|
|
3916
3997
|
imageRequestPricing(provider, model) {
|
|
3917
3998
|
return this.adapters.get(provider)?.adapter.imageRequestPricing(provider, model);
|
|
3918
3999
|
}
|
|
4000
|
+
/**
|
|
4001
|
+
* Resolve the exact text one durable file occurrence contributes to every
|
|
4002
|
+
* provider request in the current execution environment.
|
|
4003
|
+
* @param ref - durable verbatim file reference from model history.
|
|
4004
|
+
* @returns the same deterministic handle text used at adapter dispatch.
|
|
4005
|
+
*/
|
|
4006
|
+
fileRequestText(ref) {
|
|
4007
|
+
return fileHandleText(ref, this.fileReadPath(ref));
|
|
4008
|
+
}
|
|
3919
4009
|
/** Detach typed adapter-owned modality metadata. */
|
|
3920
4010
|
detachedModalities(modalities) {
|
|
3921
4011
|
return modalities === void 0 ? void 0 : [...modalities];
|
|
@@ -3965,6 +4055,8 @@ var LlmError = class extends HarnessError {
|
|
|
3965
4055
|
const context = resolved.context;
|
|
3966
4056
|
if (context !== void 0 && (!Number.isInteger(context.contextWindow) || context.contextWindow <= 0)) throw new LlmError(`adapter returned invalid context metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_CONTEXT");
|
|
3967
4057
|
const inputModalities = this.detachedModalities(resolved.inputModalities);
|
|
4058
|
+
const systemPromptUpdate = resolved.systemPromptUpdate;
|
|
4059
|
+
if (systemPromptUpdate !== void 0 && systemPromptUpdate !== "in-history") throw new LlmError(`adapter returned invalid system prompt update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
3968
4060
|
const defaultMaxTokens = resolved.defaultMaxTokens;
|
|
3969
4061
|
if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
|
|
3970
4062
|
const info = {
|
|
@@ -3974,7 +4066,8 @@ var LlmError = class extends HarnessError {
|
|
|
3974
4066
|
...resolved.description === void 0 ? {} : { description: resolved.description },
|
|
3975
4067
|
...inputModalities === void 0 ? {} : { inputModalities },
|
|
3976
4068
|
...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
|
|
3977
|
-
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens }
|
|
4069
|
+
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
|
|
4070
|
+
...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate }
|
|
3978
4071
|
};
|
|
3979
4072
|
const reasoning = resolved.reasoning;
|
|
3980
4073
|
if (reasoning === void 0) return info;
|
|
@@ -4068,6 +4161,7 @@ var LlmError = class extends HarnessError {
|
|
|
4068
4161
|
adapterDefaults,
|
|
4069
4162
|
...context === void 0 ? {} : { context },
|
|
4070
4163
|
...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
|
|
4164
|
+
...modelInfo.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
|
|
4071
4165
|
stream: (options) => {
|
|
4072
4166
|
if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
|
|
4073
4167
|
if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
@@ -4109,6 +4203,20 @@ var LlmError = class extends HarnessError {
|
|
|
4109
4203
|
return Object.isFrozen(options) ? deepFreeze(filtered) : filtered;
|
|
4110
4204
|
}
|
|
4111
4205
|
/**
|
|
4206
|
+
* Resolve the current execution-world read path of one durable file
|
|
4207
|
+
* reference through the mounted attachment and filesystem providers.
|
|
4208
|
+
*/
|
|
4209
|
+
fileReadPath(ref) {
|
|
4210
|
+
let hostPath;
|
|
4211
|
+
try {
|
|
4212
|
+
hostPath = this.ctx.get("attachments")?.fileHostPath(ref);
|
|
4213
|
+
} catch {
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
if (hostPath === void 0) return void 0;
|
|
4217
|
+
return this.ctx.get("fs")?.processPathFromHostPath(hostPath);
|
|
4218
|
+
}
|
|
4219
|
+
/**
|
|
4112
4220
|
* Final adapter boundary. Adapter selection, dispatch, iterator construction,
|
|
4113
4221
|
* and iteration failures become one terminal failure chunk. Middleware and
|
|
4114
4222
|
* downstream consumer failures remain thrown plugin or consumer errors.
|
|
@@ -4139,13 +4247,16 @@ var LlmError = class extends HarnessError {
|
|
|
4139
4247
|
...options,
|
|
4140
4248
|
...resolvedConfig
|
|
4141
4249
|
};
|
|
4142
|
-
|
|
4250
|
+
let projectedMessages = resolvedOptions.messages;
|
|
4251
|
+
if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
|
|
4252
|
+
if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
|
|
4253
|
+
const projectedOptions = projectedMessages === resolvedOptions.messages ? resolvedOptions : Object.isFrozen(resolvedOptions) ? deepFreeze({
|
|
4143
4254
|
...resolvedOptions,
|
|
4144
|
-
messages:
|
|
4255
|
+
messages: projectedMessages
|
|
4145
4256
|
}) : {
|
|
4146
4257
|
...resolvedOptions,
|
|
4147
|
-
messages:
|
|
4148
|
-
}
|
|
4258
|
+
messages: projectedMessages
|
|
4259
|
+
};
|
|
4149
4260
|
iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
|
|
4150
4261
|
} catch (error) {
|
|
4151
4262
|
yield adapterFailureChunk(error, options.signal);
|
|
@@ -8966,7 +9077,7 @@ function superRefine(fn, params) {
|
|
|
8966
9077
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
8967
9078
|
}
|
|
8968
9079
|
//#endregion
|
|
8969
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage@0.1.
|
|
9080
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-storage/lib/index.js
|
|
8970
9081
|
/**
|
|
8971
9082
|
* Backend-facing vocabulary of the storage hub: a backend owns one medium
|
|
8972
9083
|
* (a file-tree root, a database file) and exposes operation groups over it.
|
|
@@ -8977,7 +9088,7 @@ function superRefine(fn, params) {
|
|
|
8977
9088
|
/** Allowed format for unit and table names: safe as a file name and as a SQL identifier segment without escaping. */
|
|
8978
9089
|
const UNIT_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
8979
9090
|
//#endregion
|
|
8980
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage-domain@0.1.
|
|
9091
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage-domain@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+ds_8f094eabec231701d08ef59df76cca5d/node_modules/@deepseek-ai/dsh-storage-domain/lib/index.js
|
|
8981
9092
|
/**
|
|
8982
9093
|
* Domain declaration vocabulary. A spec object is the single source of a
|
|
8983
9094
|
* domain's identity, layout, and record schemas: the owning package defines
|
|
@@ -9121,6 +9232,27 @@ function sessionEvents(session) {
|
|
|
9121
9232
|
function asEventList(events) {
|
|
9122
9233
|
return Array.isArray(events) ? events : [];
|
|
9123
9234
|
}
|
|
9235
|
+
/**
|
|
9236
|
+
* 读取一个持久化(离线)会话的事件日志与头部元数据。兼容两代 persistence API:
|
|
9237
|
+
* - 旧版:`sessionPersistence.inspect(id)` 直接返回 { events, meta };
|
|
9238
|
+
* - dsh 0.1.5 之后:`inspect` 已删除,改用 `open(id, 'read')` → `handle.read()`
|
|
9239
|
+
* (+ `handle.header`),读句柄不抢写所有权,读后必须 close()。
|
|
9240
|
+
* 会话不存在时两个 API 都抛错,调用方按原语义处理。
|
|
9241
|
+
*/
|
|
9242
|
+
async function inspectPersistedSession(ctx, id) {
|
|
9243
|
+
const persistence = ctx.sessionPersistence;
|
|
9244
|
+
if (typeof persistence.inspect === "function") return await persistence.inspect(id);
|
|
9245
|
+
const handle = await persistence.open(id, "read");
|
|
9246
|
+
try {
|
|
9247
|
+
const { events } = await handle.read();
|
|
9248
|
+
return {
|
|
9249
|
+
events,
|
|
9250
|
+
meta: handle.header
|
|
9251
|
+
};
|
|
9252
|
+
} finally {
|
|
9253
|
+
await handle.close();
|
|
9254
|
+
}
|
|
9255
|
+
}
|
|
9124
9256
|
/** agent.followup / steer 接受的用户消息值。 */
|
|
9125
9257
|
function userMessage(text) {
|
|
9126
9258
|
return {
|
|
@@ -9252,12 +9384,14 @@ function latestSegmentSince(events, sinceSeq = 0) {
|
|
|
9252
9384
|
return segs.length === 0 ? null : segs[segs.length - 1] ?? null;
|
|
9253
9385
|
}
|
|
9254
9386
|
/**
|
|
9255
|
-
* 对一个 live 会话的事件日志折叠出其"
|
|
9256
|
-
* -
|
|
9257
|
-
*
|
|
9258
|
-
* -
|
|
9259
|
-
* -
|
|
9260
|
-
*
|
|
9387
|
+
* 对一个 live 会话的事件日志折叠出其"最近推理/文本增量流"。兼容两代日志形状:
|
|
9388
|
+
* - dsh 0.1.5 之后:turn 内不再发布独立 chunk 事件,流式增量内嵌在已定型
|
|
9389
|
+
* assistant/message(或未定型 assistant/attempt)事件的 `stream` 记录里
|
|
9390
|
+
* (reasoning-chunks / text-chunks 批量段 + 原始 chunk:reasoning-delta / text-delta);
|
|
9391
|
+
* - 0.1.2-rc 之前:独立的 assistant/chunk 事件(turn 中途即可见、增量,
|
|
9392
|
+
* 这正是旧版监控思维链并按规则提前终止所需的粒度)。
|
|
9393
|
+
* 两者兼读、取最新一份;无任何增量(会话空闲、或该 provider 不流式推理/无推理)
|
|
9394
|
+
* 返回 null,调用方应回落为 foldMessages 里已定型的 message.reasoning。
|
|
9261
9395
|
* 纯读、无副作用;事件形状不做任何假设,缺失/异常一律安全处理。
|
|
9262
9396
|
*/
|
|
9263
9397
|
function liveReasoningSnapshot(events) {
|
|
@@ -9271,11 +9405,41 @@ function liveReasoningSnapshot(events) {
|
|
|
9271
9405
|
let step = 0;
|
|
9272
9406
|
let foundDelta = false;
|
|
9273
9407
|
for (const event of list) {
|
|
9274
|
-
|
|
9275
|
-
|
|
9408
|
+
const type = event.type;
|
|
9409
|
+
if (type === "assistant/message" || type === "assistant/attempt") {
|
|
9410
|
+
const data = event.data;
|
|
9411
|
+
if (data !== null && data !== void 0 && typeof data === "object" && Array.isArray(data.stream)) {
|
|
9412
|
+
let r = "";
|
|
9413
|
+
let t = "";
|
|
9414
|
+
for (const raw of data.stream) {
|
|
9415
|
+
const record = raw;
|
|
9416
|
+
if (record === null || typeof record !== "object") continue;
|
|
9417
|
+
if (record.type === "reasoning-chunks" && Array.isArray(record.texts)) {
|
|
9418
|
+
for (const s of record.texts) if (typeof s === "string") r += s;
|
|
9419
|
+
} else if (record.type === "text-chunks" && Array.isArray(record.texts)) {
|
|
9420
|
+
for (const s of record.texts) if (typeof s === "string") t += s;
|
|
9421
|
+
} else if (record.type === "chunk") {
|
|
9422
|
+
const c = record.chunk;
|
|
9423
|
+
if (c !== null && typeof c === "object" && typeof c.type === "string" && typeof c.text === "string") {
|
|
9424
|
+
if (c.type === "reasoning-delta") r += c.text;
|
|
9425
|
+
else if (c.type === "text-delta") t += c.text;
|
|
9426
|
+
}
|
|
9427
|
+
}
|
|
9428
|
+
}
|
|
9429
|
+
if (r !== "" || t !== "") {
|
|
9430
|
+
if (typeof data.turn === "number") turn = data.turn;
|
|
9431
|
+
if (typeof data.step === "number") step = data.step;
|
|
9432
|
+
reasoning = r;
|
|
9433
|
+
text = t;
|
|
9434
|
+
seq = event.seq;
|
|
9435
|
+
time = event.time;
|
|
9436
|
+
foundDelta = true;
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
if (type === "assistant/message") lastFinalizedSeq = event.seq;
|
|
9276
9440
|
continue;
|
|
9277
9441
|
}
|
|
9278
|
-
if (
|
|
9442
|
+
if (type !== "assistant/chunk") continue;
|
|
9279
9443
|
if (event.seq <= lastFinalizedSeq) continue;
|
|
9280
9444
|
const data = event.data;
|
|
9281
9445
|
if (data === null || data === void 0 || typeof data !== "object") continue;
|
|
@@ -9511,7 +9675,7 @@ function statusSnapshot(ctx, agent) {
|
|
|
9511
9675
|
lastTurn,
|
|
9512
9676
|
lastActivityAt,
|
|
9513
9677
|
stalledMs: lastActivityAt === null ? null : Date.now() - lastActivityAt,
|
|
9514
|
-
pendingWork: inbox?.hasPending ??
|
|
9678
|
+
pendingWork: inbox?.hasPending === true || (inbox?.nextTurn?.length ?? 0) + (inbox?.nextStep?.length ?? 0) > 0,
|
|
9515
9679
|
nextTurnCount: inbox?.nextTurn?.length ?? 0,
|
|
9516
9680
|
nextStepCount: inbox?.nextStep?.length ?? 0,
|
|
9517
9681
|
...lastAssistantText === void 0 ? {} : { lastAssistantText },
|
|
@@ -9874,7 +10038,7 @@ var SessionMonitor = class {
|
|
|
9874
10038
|
}
|
|
9875
10039
|
};
|
|
9876
10040
|
//#endregion
|
|
9877
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1.
|
|
10041
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1.5-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-invaria_a35c65afc1061b4908fad5a531841731/node_modules/@deepseek-ai/dsh-scope/lib/index.js
|
|
9878
10042
|
/**
|
|
9879
10043
|
* Shared insertion-ordered storage and effect ownership for scope-aware registries.
|
|
9880
10044
|
*
|
|
@@ -10153,7 +10317,7 @@ function scopeTarget(base, key) {
|
|
|
10153
10317
|
return carrier;
|
|
10154
10318
|
}
|
|
10155
10319
|
//#endregion
|
|
10156
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1.
|
|
10320
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1.5-alpha.2_2b754afda902b21956a20e8a701ed6ef/node_modules/@deepseek-ai/dsh-tools/lib/index.js
|
|
10157
10321
|
/**
|
|
10158
10322
|
* Enforced JSON Schema subset shared by tool outputs, generated PTC mode
|
|
10159
10323
|
* types, subagents, and workflows. The subset accepts any JSON root, an
|
|
@@ -11330,7 +11494,7 @@ function createRunCodeTool(registry, options) {
|
|
|
11330
11494
|
if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
|
|
11331
11495
|
const normalized = jsonNormalizeArgs(rawArgs);
|
|
11332
11496
|
const n = ++dispatches;
|
|
11333
|
-
const subCallId = brandString(`${String(exec.callId)}:
|
|
11497
|
+
const subCallId = brandString(`${String(exec.callId)}:ptc:${n}`);
|
|
11334
11498
|
const input = {
|
|
11335
11499
|
callId: subCallId,
|
|
11336
11500
|
rootCallId: exec.rootCallId,
|
|
@@ -11362,7 +11526,7 @@ function createRunCodeTool(registry, options) {
|
|
|
11362
11526
|
isError: result.isError,
|
|
11363
11527
|
content: result.content
|
|
11364
11528
|
});
|
|
11365
|
-
agent.session.append("tool/
|
|
11529
|
+
agent.session.append("tool/ptc-dispatch", {
|
|
11366
11530
|
rootCallId: exec.rootCallId,
|
|
11367
11531
|
parentCallId: exec.callId,
|
|
11368
11532
|
subCallId,
|
|
@@ -11384,7 +11548,7 @@ function createRunCodeTool(registry, options) {
|
|
|
11384
11548
|
reject(/* @__PURE__ */ new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`));
|
|
11385
11549
|
},
|
|
11386
11550
|
async start() {
|
|
11387
|
-
exec.agent?.session.append("tool/
|
|
11551
|
+
exec.agent?.session.append("tool/ptc-dispatch-start", {
|
|
11388
11552
|
rootCallId: exec.rootCallId,
|
|
11389
11553
|
parentCallId: exec.callId,
|
|
11390
11554
|
subCallId,
|
|
@@ -11418,7 +11582,7 @@ function createRunCodeTool(registry, options) {
|
|
|
11418
11582
|
content: result.content,
|
|
11419
11583
|
source: {
|
|
11420
11584
|
kind: "plugin",
|
|
11421
|
-
plugin: "tools-
|
|
11585
|
+
plugin: "tools-ptc"
|
|
11422
11586
|
}
|
|
11423
11587
|
}));
|
|
11424
11588
|
for (const context of result.additionalContexts ?? []) exec.deferContext(context);
|
|
@@ -12822,8 +12986,7 @@ var ToolRuntime = class extends Service {
|
|
|
12822
12986
|
* reads return the same flavor — but a reload that swapped in a second
|
|
12823
12987
|
* language between them would hand a program written against one SDK to the
|
|
12824
12988
|
* other. Binding it is deferred until a second backend ships (the first
|
|
12825
|
-
* point it is testable)
|
|
12826
|
-
* [language-dispatch note](../../../../.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.md).
|
|
12989
|
+
* point it is testable).
|
|
12827
12990
|
*/
|
|
12828
12991
|
requireCodeRuntime(mode) {
|
|
12829
12992
|
const runtime = this.ctx.get("codeRuntime");
|
|
@@ -12909,9 +13072,9 @@ var ToolRuntime = class extends Service {
|
|
|
12909
13072
|
* A restriction filters what a scope inherits — the global layer and every
|
|
12910
13073
|
* ancestor layer on its chain — and never what its OWN layer registers.
|
|
12911
13074
|
* That exemption is what a per-child capability filter has to keep intact:
|
|
12912
|
-
* the delegation runtime registers a child's
|
|
12913
|
-
*
|
|
12914
|
-
*
|
|
13075
|
+
* the delegation runtime registers a child's structured-output tool into the
|
|
13076
|
+
* child's own layer, and a filter naming the capabilities the child may use
|
|
13077
|
+
* must not strip the machinery it answers through.
|
|
12915
13078
|
*
|
|
12916
13079
|
* Reading the exempt set as "the global layer" instead of "not mine" held
|
|
12917
13080
|
* only while every model-facing tool sat in the host composition. Once
|
|
@@ -13029,7 +13192,7 @@ var ToolRuntime = class extends Service {
|
|
|
13029
13192
|
}
|
|
13030
13193
|
/**
|
|
13031
13194
|
* Run the `tools/ptc-dispatch-log` waterfall over one settled sub-dispatch
|
|
13032
|
-
* and return the content the bridge should log on `tool/
|
|
13195
|
+
* and return the content the bridge should log on `tool/ptc-dispatch`.
|
|
13033
13196
|
* Contained: when a listener throws, the method logs the original settled
|
|
13034
13197
|
* content; that failure must not fail the dispatch or omit the settle event. Private:
|
|
13035
13198
|
* the ONE consumer is the `run_code` bridge this registry constructs, which
|
|
@@ -13655,11 +13818,33 @@ function toolAbortedBeforeDispatchResult(prior) {
|
|
|
13655
13818
|
};
|
|
13656
13819
|
}
|
|
13657
13820
|
//#endregion
|
|
13658
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-agent@0.1.
|
|
13821
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-agent@0.1.5-alpha.2_cfc1690176e5c008b05d52ad11161b75/node_modules/@deepseek-ai/dsh-agent/lib/index.js
|
|
13659
13822
|
/**
|
|
13660
13823
|
* Agent-scoped model selection shared by runtime entry points.
|
|
13661
13824
|
* @module @deepseek-ai/dsh-agent/model-selection
|
|
13662
13825
|
*/
|
|
13826
|
+
function sameRoute(left, right) {
|
|
13827
|
+
return left.provider === right.provider && left.model === right.model;
|
|
13828
|
+
}
|
|
13829
|
+
function routeLabel(route, other) {
|
|
13830
|
+
return route.provider === other.provider ? route.model : `${route.provider}/${route.model}`;
|
|
13831
|
+
}
|
|
13832
|
+
function modelSwitchNotice(previous, selected) {
|
|
13833
|
+
const from = routeLabel(previous, selected);
|
|
13834
|
+
const to = routeLabel(selected, previous);
|
|
13835
|
+
return createUserMessage({
|
|
13836
|
+
content: [{
|
|
13837
|
+
type: "text",
|
|
13838
|
+
text: `[model changed: assistant turns above this point were generated by ${from}; the session continues with ${to}]`
|
|
13839
|
+
}],
|
|
13840
|
+
source: {
|
|
13841
|
+
kind: "plugin",
|
|
13842
|
+
plugin: "model-selection",
|
|
13843
|
+
form: "notice",
|
|
13844
|
+
summary: boundContextSummary(`${from} → ${to}`)
|
|
13845
|
+
}
|
|
13846
|
+
});
|
|
13847
|
+
}
|
|
13663
13848
|
/**
|
|
13664
13849
|
* Couple one mutable selection to Agent-scoped prompt assembly and request routing.
|
|
13665
13850
|
* Prompt assembly snapshots the selected model before delegating, then applies
|
|
@@ -13668,9 +13853,15 @@ function toolAbortedBeforeDispatchResult(prior) {
|
|
|
13668
13853
|
* surfaces. An absent selected effort clears any inherited effort, restoring
|
|
13669
13854
|
* the selected model's provider/default behavior.
|
|
13670
13855
|
*
|
|
13856
|
+
* A provider/model change appends a durable user-role notice to the next
|
|
13857
|
+
* admitted request. It compares the assembled selection with the latest
|
|
13858
|
+
* request header; effort-only changes and empty no-request decisions add no
|
|
13859
|
+
* notice. Failure before header persistence repeats the notice on the next
|
|
13860
|
+
* request.
|
|
13861
|
+
*
|
|
13671
13862
|
* @param agentCtx - The selected Agent's scoped context.
|
|
13672
13863
|
* @param selection - Mutable selection owned by the calling entry point.
|
|
13673
|
-
* @returns Disposer for
|
|
13864
|
+
* @returns Disposer for all scoped waterfall listeners.
|
|
13674
13865
|
*/
|
|
13675
13866
|
function installModelSelection(agentCtx, selection) {
|
|
13676
13867
|
const disposeAssembly = agentCtx.on("system-prompt/assemble", async (_assembly, _context, next) => {
|
|
@@ -13699,9 +13890,22 @@ function installModelSelection(agentCtx, selection) {
|
|
|
13699
13890
|
...selected.reasoningEffort === void 0 ? {} : { reasoningEffort: selected.reasoningEffort }
|
|
13700
13891
|
};
|
|
13701
13892
|
});
|
|
13893
|
+
const disposeNotice = agentCtx.on("agent/pre-step", async ({ agent, messages, signal, step }, next) => {
|
|
13894
|
+
const decision = await next();
|
|
13895
|
+
if (decision.kind === "reject" || signal.aborted) return decision;
|
|
13896
|
+
if (decision.messages.length === 0 && (step === 1 || messages.length > 0)) return decision;
|
|
13897
|
+
const selected = selection.assembled;
|
|
13898
|
+
const previous = agent.session.requestHeader()?.config;
|
|
13899
|
+
if (selected === void 0 || previous === void 0 || sameRoute(selected, previous)) return decision;
|
|
13900
|
+
return {
|
|
13901
|
+
...decision,
|
|
13902
|
+
messages: [...decision.messages, modelSwitchNotice(previous, selected)]
|
|
13903
|
+
};
|
|
13904
|
+
}, { prepend: true });
|
|
13702
13905
|
return () => {
|
|
13703
13906
|
disposeAssembly();
|
|
13704
13907
|
disposeRequest();
|
|
13908
|
+
disposeNotice();
|
|
13705
13909
|
};
|
|
13706
13910
|
}
|
|
13707
13911
|
//#endregion
|
|
@@ -14022,7 +14226,11 @@ function registerResume(env) {
|
|
|
14022
14226
|
}
|
|
14023
14227
|
let headers = [];
|
|
14024
14228
|
try {
|
|
14025
|
-
headers = await env.ctx.sessionPersistence.list()
|
|
14229
|
+
headers = (await env.ctx.sessionPersistence.list()).map((rec) => ({
|
|
14230
|
+
id: rec.header.id,
|
|
14231
|
+
...rec.header.cwd === void 0 ? {} : { cwd: rec.header.cwd },
|
|
14232
|
+
createdAt: rec.header.createdAt
|
|
14233
|
+
}));
|
|
14026
14234
|
} catch (error) {
|
|
14027
14235
|
throw new Error("session persistence unavailable: " + (error instanceof Error ? error.message : String(error)));
|
|
14028
14236
|
}
|
|
@@ -14185,7 +14393,7 @@ function registerSegments(env) {
|
|
|
14185
14393
|
events = sessionEvents(agent.session);
|
|
14186
14394
|
live = true;
|
|
14187
14395
|
} else try {
|
|
14188
|
-
events = (await env.ctx
|
|
14396
|
+
events = (await inspectPersistedSession(env.ctx, args.sessionId)).events;
|
|
14189
14397
|
live = false;
|
|
14190
14398
|
} catch (error) {
|
|
14191
14399
|
throw new Error(String(error));
|
|
@@ -14279,7 +14487,7 @@ function registerRead(env) {
|
|
|
14279
14487
|
} else {
|
|
14280
14488
|
let inspection;
|
|
14281
14489
|
try {
|
|
14282
|
-
inspection = await env.ctx
|
|
14490
|
+
inspection = await inspectPersistedSession(env.ctx, args.sessionId);
|
|
14283
14491
|
} catch (error) {
|
|
14284
14492
|
throw new Error("cannot read session " + JSON.stringify(args.sessionId) + ": " + (error instanceof Error ? error.message : String(error)));
|
|
14285
14493
|
}
|
|
@@ -14403,7 +14611,8 @@ function registerFind(env) {
|
|
|
14403
14611
|
}
|
|
14404
14612
|
if (args.liveOnly !== true) try {
|
|
14405
14613
|
const persistence = env.ctx.sessionPersistence;
|
|
14406
|
-
for (const
|
|
14614
|
+
for (const rec of await persistence.list()) {
|
|
14615
|
+
const header = rec.header;
|
|
14407
14616
|
if (items.some((item) => item.sessionId === header.id)) continue;
|
|
14408
14617
|
items.push({
|
|
14409
14618
|
sessionId: header.id,
|
|
@@ -14439,13 +14648,12 @@ function registerFind(env) {
|
|
|
14439
14648
|
const wsFilter = typeof args.workspaceId === "string" ? args.workspaceId.trim() : "";
|
|
14440
14649
|
const cwdFilter = typeof args.cwd === "string" ? args.cwd.trim().toLowerCase() : "";
|
|
14441
14650
|
if (titleFilter !== "" || query !== "" && items.some((item) => !item.live)) {
|
|
14442
|
-
const persistence = env.ctx.sessionPersistence;
|
|
14443
14651
|
let inspected = 0;
|
|
14444
14652
|
for (const item of items) {
|
|
14445
14653
|
if (inspected >= 30) break;
|
|
14446
14654
|
if (item.live || item.title !== void 0) continue;
|
|
14447
14655
|
try {
|
|
14448
|
-
item.title = titleOf((await
|
|
14656
|
+
item.title = titleOf((await inspectPersistedSession(env.ctx, item.sessionId)).events);
|
|
14449
14657
|
inspected += 1;
|
|
14450
14658
|
} catch {
|
|
14451
14659
|
inspected += 1;
|
|
@@ -14950,7 +15158,6 @@ function registerArchive(env) {
|
|
|
14950
15158
|
async execute(args) {
|
|
14951
15159
|
const items = env.ctx.workspaceRegistry.archivedSessionIds.map(String).map((id) => ({ sessionId: id }));
|
|
14952
15160
|
if (args.resolveTitles === true && items.length > 0) {
|
|
14953
|
-
const persistence = env.ctx.sessionPersistence;
|
|
14954
15161
|
const registryTitles = /* @__PURE__ */ new Map();
|
|
14955
15162
|
try {
|
|
14956
15163
|
const records = await env.registry.all();
|
|
@@ -14962,7 +15169,7 @@ function registerArchive(env) {
|
|
|
14962
15169
|
continue;
|
|
14963
15170
|
}
|
|
14964
15171
|
try {
|
|
14965
|
-
item.title = titleOf((await
|
|
15172
|
+
item.title = titleOf((await inspectPersistedSession(env.ctx, item.sessionId)).events);
|
|
14966
15173
|
} catch {}
|
|
14967
15174
|
}
|
|
14968
15175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-bridge",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "会话桥(dsh-session-bridge):通过提示词创建新的主会话(顶层 UI 会话)、向任意会话发送消息、等待会话的下一条回复、读取会话消息,并按会话名或 id 跨工作区查找会话;此外支持监控/调度主任务与归档会话。",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -52,28 +52,28 @@
|
|
|
52
52
|
},
|
|
53
53
|
"license": "MIT",
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
56
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
57
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
58
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
59
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
60
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
61
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
62
|
-
"@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
55
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
56
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
57
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
58
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
59
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
60
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
61
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
62
|
+
"@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
63
63
|
"cordis": "^4.0.0-rc.7"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
67
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
68
|
-
"@deepseek-ai/dsh-brand": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
69
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
70
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
71
|
-
"@deepseek-ai/dsh-scope": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
72
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
73
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
74
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
75
|
-
"@deepseek-ai/dsh-util-values": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
76
|
-
"@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0",
|
|
66
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
67
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
68
|
+
"@deepseek-ai/dsh-brand": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
69
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
70
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
71
|
+
"@deepseek-ai/dsh-scope": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
72
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
73
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
74
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
75
|
+
"@deepseek-ai/dsh-util-values": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
76
|
+
"@deepseek-ai/dsh-workspace": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.5-0",
|
|
77
77
|
"@types/node": "^24.0.0",
|
|
78
78
|
"cordis": "^4.0.0-rc.7",
|
|
79
79
|
"tsdown": "^0.22.14",
|
package/src/core.ts
CHANGED
|
@@ -21,7 +21,9 @@ export interface LiveAgentLike {
|
|
|
21
21
|
whenIdle?(): Promise<void>
|
|
22
22
|
readonly ctx: Context
|
|
23
23
|
readonly inbox?: {
|
|
24
|
-
hasPending
|
|
24
|
+
// dsh 0.1.2 及之前:`hasPending` getter;dsh 0.1.5 起已删除,仅有
|
|
25
|
+
// nextTurn / nextStep 只读数组(Inbox 成为 driver 持有的接口)。
|
|
26
|
+
hasPending?: boolean
|
|
25
27
|
nextTurn?: readonly unknown[]
|
|
26
28
|
nextStep?: readonly unknown[]
|
|
27
29
|
}
|
|
@@ -86,6 +88,34 @@ export function asEventList(events: unknown): readonly SessionEvent[] {
|
|
|
86
88
|
return Array.isArray(events) ? events : []
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* 读取一个持久化(离线)会话的事件日志与头部元数据。兼容两代 persistence API:
|
|
93
|
+
* - 旧版:`sessionPersistence.inspect(id)` 直接返回 { events, meta };
|
|
94
|
+
* - dsh 0.1.5 之后:`inspect` 已删除,改用 `open(id, 'read')` → `handle.read()`
|
|
95
|
+
* (+ `handle.header`),读句柄不抢写所有权,读后必须 close()。
|
|
96
|
+
* 会话不存在时两个 API 都抛错,调用方按原语义处理。
|
|
97
|
+
*/
|
|
98
|
+
export async function inspectPersistedSession(ctx: Context, id: string): Promise<{ events: readonly SessionEvent[]; meta: { cwd?: string } }> {
|
|
99
|
+
const persistence = ctx.sessionPersistence as unknown as {
|
|
100
|
+
inspect?: (sid: string) => Promise<{ events: readonly SessionEvent[]; meta: { cwd?: string } }>
|
|
101
|
+
open?: (sid: string, access: 'read') => Promise<{
|
|
102
|
+
header: { cwd?: string }
|
|
103
|
+
read(): Promise<{ events: readonly SessionEvent[] }>
|
|
104
|
+
close(): Promise<void>
|
|
105
|
+
}>
|
|
106
|
+
}
|
|
107
|
+
if (typeof persistence.inspect === 'function') {
|
|
108
|
+
return await persistence.inspect(id)
|
|
109
|
+
}
|
|
110
|
+
const handle = await persistence.open!(id, 'read')
|
|
111
|
+
try {
|
|
112
|
+
const { events } = await handle.read()
|
|
113
|
+
return { events, meta: handle.header }
|
|
114
|
+
} finally {
|
|
115
|
+
await handle.close()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
89
119
|
/** agent.followup / steer 接受的用户消息值。 */
|
|
90
120
|
export function userMessage(text: string): unknown {
|
|
91
121
|
return {
|
|
@@ -237,18 +267,20 @@ export interface CoTLiveSlice {
|
|
|
237
267
|
}
|
|
238
268
|
|
|
239
269
|
/**
|
|
240
|
-
* 对一个 live 会话的事件日志折叠出其"
|
|
241
|
-
* -
|
|
242
|
-
*
|
|
243
|
-
* -
|
|
244
|
-
* -
|
|
245
|
-
*
|
|
270
|
+
* 对一个 live 会话的事件日志折叠出其"最近推理/文本增量流"。兼容两代日志形状:
|
|
271
|
+
* - dsh 0.1.5 之后:turn 内不再发布独立 chunk 事件,流式增量内嵌在已定型
|
|
272
|
+
* assistant/message(或未定型 assistant/attempt)事件的 `stream` 记录里
|
|
273
|
+
* (reasoning-chunks / text-chunks 批量段 + 原始 chunk:reasoning-delta / text-delta);
|
|
274
|
+
* - 0.1.2-rc 之前:独立的 assistant/chunk 事件(turn 中途即可见、增量,
|
|
275
|
+
* 这正是旧版监控思维链并按规则提前终止所需的粒度)。
|
|
276
|
+
* 两者兼读、取最新一份;无任何增量(会话空闲、或该 provider 不流式推理/无推理)
|
|
277
|
+
* 返回 null,调用方应回落为 foldMessages 里已定型的 message.reasoning。
|
|
246
278
|
* 纯读、无副作用;事件形状不做任何假设,缺失/异常一律安全处理。
|
|
247
279
|
*/
|
|
248
280
|
export function liveReasoningSnapshot(events: readonly SessionEvent[]): CoTLiveSlice | null {
|
|
249
281
|
const list = asEventList(events)
|
|
250
|
-
//
|
|
251
|
-
//
|
|
282
|
+
// 旧格式只累积"最近一条已定型 assistant/message 之后"的流式增量(in-flight 窗口);
|
|
283
|
+
// 新格式的 stream 随定型事件落地,直接以最新携带 stream 的事件为准。
|
|
252
284
|
let lastFinalizedSeq = -1
|
|
253
285
|
let reasoning = ''
|
|
254
286
|
let text = ''
|
|
@@ -258,11 +290,42 @@ export function liveReasoningSnapshot(events: readonly SessionEvent[]): CoTLiveS
|
|
|
258
290
|
let step = 0
|
|
259
291
|
let foundDelta = false
|
|
260
292
|
for (const event of list) {
|
|
261
|
-
|
|
262
|
-
|
|
293
|
+
const type = event.type as string
|
|
294
|
+
if (type === 'assistant/message' || type === 'assistant/attempt') {
|
|
295
|
+
const data = event.data as { turn?: unknown; step?: unknown; stream?: unknown } | null | undefined
|
|
296
|
+
if (data !== null && data !== undefined && typeof data === 'object' && Array.isArray(data.stream)) {
|
|
297
|
+
let r = ''
|
|
298
|
+
let t = ''
|
|
299
|
+
for (const raw of data.stream as unknown[]) {
|
|
300
|
+
const record = raw as { type?: unknown; texts?: unknown; text?: unknown; chunk?: unknown } | null
|
|
301
|
+
if (record === null || typeof record !== 'object') continue
|
|
302
|
+
if (record.type === 'reasoning-chunks' && Array.isArray(record.texts)) {
|
|
303
|
+
for (const s of record.texts as unknown[]) if (typeof s === 'string') r += s
|
|
304
|
+
} else if (record.type === 'text-chunks' && Array.isArray(record.texts)) {
|
|
305
|
+
for (const s of record.texts as unknown[]) if (typeof s === 'string') t += s
|
|
306
|
+
} else if (record.type === 'chunk') {
|
|
307
|
+
const c = record.chunk as { type?: unknown; text?: unknown } | null
|
|
308
|
+
if (c !== null && typeof c === 'object' && typeof c.type === 'string' && typeof c.text === 'string') {
|
|
309
|
+
if (c.type === 'reasoning-delta') r += c.text
|
|
310
|
+
else if (c.type === 'text-delta') t += c.text
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (r !== '' || t !== '') {
|
|
315
|
+
if (typeof data.turn === 'number') turn = data.turn
|
|
316
|
+
if (typeof data.step === 'number') step = data.step
|
|
317
|
+
reasoning = r
|
|
318
|
+
text = t
|
|
319
|
+
seq = event.seq
|
|
320
|
+
time = event.time
|
|
321
|
+
foundDelta = true
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (type === 'assistant/message') lastFinalizedSeq = event.seq
|
|
263
325
|
continue
|
|
264
326
|
}
|
|
265
|
-
|
|
327
|
+
// 旧格式:独立 assistant/chunk 事件。
|
|
328
|
+
if (type !== 'assistant/chunk') continue
|
|
266
329
|
if (event.seq <= lastFinalizedSeq) continue
|
|
267
330
|
const data = event.data as { turn?: unknown; step?: unknown; chunk?: unknown } | null | undefined
|
|
268
331
|
if (data === null || data === undefined || typeof data !== 'object') continue
|
|
@@ -559,7 +622,8 @@ export function statusSnapshot(ctx: Context, agent: LiveAgentLike): BridgeStatus
|
|
|
559
622
|
lastTurn,
|
|
560
623
|
lastActivityAt,
|
|
561
624
|
stalledMs: lastActivityAt === null ? null : Date.now() - lastActivityAt,
|
|
562
|
-
|
|
625
|
+
// 双兼容:0.1.2 用 hasPending getter;0.1.5 起只有 nextTurn/nextStep 数组。
|
|
626
|
+
pendingWork: inbox?.hasPending === true || (inbox?.nextTurn?.length ?? 0) + (inbox?.nextStep?.length ?? 0) > 0,
|
|
563
627
|
nextTurnCount: inbox?.nextTurn?.length ?? 0,
|
|
564
628
|
nextStepCount: inbox?.nextStep?.length ?? 0,
|
|
565
629
|
...(lastAssistantText === undefined ? {} : { lastAssistantText }),
|
package/src/tools.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Context } from '@deepseek-ai/cordis'
|
|
|
7
7
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
8
8
|
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
|
|
9
9
|
import type {} from '@deepseek-ai/dsh-agent-presets'
|
|
10
|
-
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
|
10
|
+
import type { Session, SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
|
|
11
11
|
import * as agentApi from '@deepseek-ai/dsh-agent'
|
|
12
12
|
import type {
|
|
13
13
|
BridgeFindItem,
|
|
@@ -15,11 +15,11 @@ import type {
|
|
|
15
15
|
BridgeStatusSnapshot,
|
|
16
16
|
BridgeWaitResult,
|
|
17
17
|
LiveAgentLike,
|
|
18
|
-
WaitForReplyOptions,
|
|
19
18
|
} from './core.ts'
|
|
20
19
|
import {
|
|
21
20
|
attachSessionToWorkspace,
|
|
22
21
|
foldMessages,
|
|
22
|
+
inspectPersistedSession,
|
|
23
23
|
maxSeq,
|
|
24
24
|
resolveTargetCwd,
|
|
25
25
|
segmentsSince,
|
|
@@ -102,10 +102,10 @@ interface WaitArgs {
|
|
|
102
102
|
timeoutMs?: number
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
async function maybeWait(env: BridgeEnv, session:
|
|
105
|
+
async function maybeWait(env: BridgeEnv, session: Session, baselineSeq: number, args: WaitArgs, signal: AbortSignal): Promise<ReturnType<typeof renderWait> | undefined> {
|
|
106
106
|
if (args.waitForReply !== true) return undefined
|
|
107
107
|
const result = await waitForReply({
|
|
108
|
-
session
|
|
108
|
+
session,
|
|
109
109
|
baselineSeq,
|
|
110
110
|
timeoutMs: clampTimeout(args.timeoutMs),
|
|
111
111
|
signal,
|
|
@@ -354,8 +354,13 @@ function registerResume(env: BridgeEnv): void {
|
|
|
354
354
|
}
|
|
355
355
|
let headers: readonly { id: string; cwd?: string; createdAt: number }[] = []
|
|
356
356
|
try {
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
// dsh >= 0.1.5-alpha.1:list() 返回 { header, ... } 快照;映射回平铺字段。
|
|
358
|
+
const persistence = env.ctx.sessionPersistence as unknown as { list(): Promise<readonly { header: { id: string; cwd?: string; createdAt: number } }[]> }
|
|
359
|
+
headers = (await persistence.list()).map((rec) => ({
|
|
360
|
+
id: rec.header.id,
|
|
361
|
+
...(rec.header.cwd === undefined ? {} : { cwd: rec.header.cwd }),
|
|
362
|
+
createdAt: rec.header.createdAt,
|
|
363
|
+
}))
|
|
359
364
|
} catch (error) {
|
|
360
365
|
throw new Error('session persistence unavailable: ' + (error instanceof Error ? error.message : String(error)))
|
|
361
366
|
}
|
|
@@ -502,8 +507,7 @@ function registerSegments(env: BridgeEnv): void {
|
|
|
502
507
|
live = true
|
|
503
508
|
} else {
|
|
504
509
|
try {
|
|
505
|
-
const
|
|
506
|
-
const inspection = await persistence.inspect(args.sessionId)
|
|
510
|
+
const inspection = await inspectPersistedSession(env.ctx, args.sessionId)
|
|
507
511
|
events = inspection.events
|
|
508
512
|
live = false
|
|
509
513
|
} catch (error) {
|
|
@@ -580,8 +584,7 @@ function registerRead(env: BridgeEnv): void {
|
|
|
580
584
|
} else {
|
|
581
585
|
let inspection: { events: readonly SessionEvent[]; meta: { cwd?: string } }
|
|
582
586
|
try {
|
|
583
|
-
|
|
584
|
-
inspection = await persistence.inspect(args.sessionId)
|
|
587
|
+
inspection = await inspectPersistedSession(env.ctx, args.sessionId)
|
|
585
588
|
} catch (error) {
|
|
586
589
|
throw new Error('cannot read session ' + JSON.stringify(args.sessionId) + ': ' + (error instanceof Error ? error.message : String(error)))
|
|
587
590
|
}
|
|
@@ -685,8 +688,11 @@ function registerFind(env: BridgeEnv): void {
|
|
|
685
688
|
}
|
|
686
689
|
if (args.liveOnly !== true) {
|
|
687
690
|
try {
|
|
688
|
-
|
|
689
|
-
|
|
691
|
+
// dsh >= 0.1.5-alpha.1:list() 返回 { header: SessionHeader, revision, ... } 快照,
|
|
692
|
+
// 不再平铺 id/cwd;旧版直接返回 header 平铺字段。两者都按新形状读,向下兼容。
|
|
693
|
+
const persistence = env.ctx.sessionPersistence as unknown as { list(): Promise<readonly { header: { id: string; cwd?: string; parentSession?: string; origin?: 'subagent'; createdAt: number; agentPreset?: string } }[]> }
|
|
694
|
+
for (const rec of await persistence.list()) {
|
|
695
|
+
const header = rec.header
|
|
690
696
|
if (items.some((item) => item.sessionId === header.id)) continue
|
|
691
697
|
items.push({
|
|
692
698
|
sessionId: header.id,
|
|
@@ -720,13 +726,12 @@ function registerFind(env: BridgeEnv): void {
|
|
|
720
726
|
const cwdFilter = typeof args.cwd === 'string' ? args.cwd.trim().toLowerCase() : ''
|
|
721
727
|
const needsOfflineTitle = titleFilter !== '' || (query !== '' && items.some((item) => !item.live))
|
|
722
728
|
if (needsOfflineTitle) {
|
|
723
|
-
const persistence = env.ctx.sessionPersistence as unknown as { inspect(id: string): Promise<{ events: readonly SessionEvent[] }> }
|
|
724
729
|
let inspected = 0
|
|
725
730
|
for (const item of items) {
|
|
726
731
|
if (inspected >= 30) break
|
|
727
732
|
if (item.live || item.title !== undefined) continue
|
|
728
733
|
try {
|
|
729
|
-
const inspection = await
|
|
734
|
+
const inspection = await inspectPersistedSession(env.ctx, item.sessionId)
|
|
730
735
|
item.title = titleOf(inspection.events)
|
|
731
736
|
inspected += 1
|
|
732
737
|
} catch {
|
|
@@ -1118,7 +1123,6 @@ function registerArchive(env: BridgeEnv): void {
|
|
|
1118
1123
|
const archived = env.ctx.workspaceRegistry.archivedSessionIds.map(String)
|
|
1119
1124
|
const items: Array<{ sessionId: string; title?: string }> = archived.map((id) => ({ sessionId: id }))
|
|
1120
1125
|
if (args.resolveTitles === true && items.length > 0) {
|
|
1121
|
-
const persistence = env.ctx.sessionPersistence as unknown as { inspect(id: string): Promise<{ events: readonly SessionEvent[] }> }
|
|
1122
1126
|
const registryTitles = new Map<string, string>()
|
|
1123
1127
|
try {
|
|
1124
1128
|
const records = await env.registry.all()
|
|
@@ -1130,7 +1134,7 @@ function registerArchive(env: BridgeEnv): void {
|
|
|
1130
1134
|
continue
|
|
1131
1135
|
}
|
|
1132
1136
|
try {
|
|
1133
|
-
const inspection = await
|
|
1137
|
+
const inspection = await inspectPersistedSession(env.ctx, item.sessionId)
|
|
1134
1138
|
item.title = titleOf(inspection.events)
|
|
1135
1139
|
} catch { /* offline title unavailable */ }
|
|
1136
1140
|
}
|