dsh-session-bridge 0.2.1 → 0.3.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 +52 -7
- package/README.zh.md +35 -5
- package/dsh.plugin.json +2 -1
- package/lib/index.js +438 -28
- package/package.json +1 -1
- package/src/core.ts +180 -7
- package/src/monitor.ts +126 -1
- package/src/tools.ts +145 -9
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.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-
|
|
7
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-home-paths@0.1.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-in_7832b76c982cc1477c0eac59a4954baf/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.2-
|
|
2203
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.1.2-rc.1_@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.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-
|
|
2348
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.1.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-i_486cdbb63f2ab3a8eaad87d2b6e5a4b7/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.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-
|
|
2560
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypto@0.1.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-i_45952c52282327ee28c4189b1e05c80c/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,7 +2570,7 @@ 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.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-
|
|
2573
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-invaria_0a6acdb1edc9f1b3caaaafba5a0cd464/node_modules/@deepseek-ai/dsh-brand/lib/index.js
|
|
2574
2574
|
/**
|
|
2575
2575
|
* Duplicate-install-safe nominal string helpers.
|
|
2576
2576
|
*
|
|
@@ -3187,11 +3187,11 @@ 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.2-
|
|
3190
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.1.2-rc.1_@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.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-
|
|
3194
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-invariant_cc3ae850d309c6dee6d11afd096d0893/node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
3195
3195
|
/**
|
|
3196
3196
|
* Detach and deep-freeze a message whose identity already exists.
|
|
3197
3197
|
* @param message - complete message, including its stable identity.
|
|
@@ -8966,7 +8966,7 @@ function superRefine(fn, params) {
|
|
|
8966
8966
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
8967
8967
|
}
|
|
8968
8968
|
//#endregion
|
|
8969
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage@0.1.2-
|
|
8969
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage@0.1.2-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-storage/lib/index.js
|
|
8970
8970
|
/**
|
|
8971
8971
|
* Backend-facing vocabulary of the storage hub: a backend owns one medium
|
|
8972
8972
|
* (a file-tree root, a database file) and exposes operation groups over it.
|
|
@@ -8977,7 +8977,7 @@ function superRefine(fn, params) {
|
|
|
8977
8977
|
/** Allowed format for unit and table names: safe as a file name and as a SQL identifier segment without escaping. */
|
|
8978
8978
|
const UNIT_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
8979
8979
|
//#endregion
|
|
8980
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage-domain@0.1.2-
|
|
8980
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-storage-domain@0.1.2-rc.1_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-i_9f44d404e0b20c2f6e8599234336f27b/node_modules/@deepseek-ai/dsh-storage-domain/lib/index.js
|
|
8981
8981
|
/**
|
|
8982
8982
|
* Domain declaration vocabulary. A spec object is the single source of a
|
|
8983
8983
|
* domain's identity, layout, and record schemas: the owning package defines
|
|
@@ -9144,6 +9144,18 @@ function blockText(content) {
|
|
|
9144
9144
|
}
|
|
9145
9145
|
return parts.length === 0 ? void 0 : parts.join("\n");
|
|
9146
9146
|
}
|
|
9147
|
+
/** 提取指定类型的 content block 文本(如 reasoning),用于从消息 content 里读出思维链。 */
|
|
9148
|
+
function blockTextByType(content, wantedType) {
|
|
9149
|
+
if (!Array.isArray(content)) return void 0;
|
|
9150
|
+
const parts = [];
|
|
9151
|
+
for (const raw of content) {
|
|
9152
|
+
const block = raw;
|
|
9153
|
+
if (block !== null && typeof block === "object" && block.type === wantedType && typeof block.text === "string") {
|
|
9154
|
+
if (block.text !== "") parts.push(block.text);
|
|
9155
|
+
}
|
|
9156
|
+
}
|
|
9157
|
+
return parts.length === 0 ? void 0 : parts.join("\n");
|
|
9158
|
+
}
|
|
9147
9159
|
function countImages(content) {
|
|
9148
9160
|
if (!Array.isArray(content)) return 0;
|
|
9149
9161
|
let n = 0;
|
|
@@ -9181,7 +9193,7 @@ function foldMessages(events) {
|
|
|
9181
9193
|
} else if (event.type === "assistant/message") {
|
|
9182
9194
|
const message = event.data.message;
|
|
9183
9195
|
const text = message === null ? void 0 : blockText(message.content);
|
|
9184
|
-
const reasoning = message === null ? void 0 : blockText(message.reasoning);
|
|
9196
|
+
const reasoning = message === null ? void 0 : blockTextByType(message.content, "reasoning") ?? blockText(message.reasoning);
|
|
9185
9197
|
const images = message === null ? 0 : countImages(message.content);
|
|
9186
9198
|
const toolCalls = message === null ? [] : toolNamesOf(message.toolCalls);
|
|
9187
9199
|
if (text === void 0 && reasoning === void 0 && toolCalls.length === 0) continue;
|
|
@@ -9198,6 +9210,111 @@ function foldMessages(events) {
|
|
|
9198
9210
|
}
|
|
9199
9211
|
return rows;
|
|
9200
9212
|
}
|
|
9213
|
+
/** 按已完成输出段落折叠事件日志:每个 assistant/message 割一段,返回 sinceSeq 后的所有段落(事件序)。 */
|
|
9214
|
+
function segmentsSince(events, sinceSeq = 0) {
|
|
9215
|
+
const list = asEventList(events);
|
|
9216
|
+
const out = [];
|
|
9217
|
+
let openTurn = false;
|
|
9218
|
+
for (const event of list) {
|
|
9219
|
+
if (event.type === "turn/start") {
|
|
9220
|
+
openTurn = true;
|
|
9221
|
+
continue;
|
|
9222
|
+
}
|
|
9223
|
+
if (event.type === "turn/end") {
|
|
9224
|
+
openTurn = false;
|
|
9225
|
+
continue;
|
|
9226
|
+
}
|
|
9227
|
+
if (event.type !== "assistant/message") continue;
|
|
9228
|
+
if (event.seq <= sinceSeq) continue;
|
|
9229
|
+
const message = event.data.message;
|
|
9230
|
+
const text = message === null ? void 0 : blockText(message.content);
|
|
9231
|
+
const reasoning = message === null ? void 0 : blockTextByType(message.content, "reasoning") ?? blockText(message.reasoning);
|
|
9232
|
+
const toolCalls = message === null ? [] : toolNamesOf(message.toolCalls);
|
|
9233
|
+
const turn = event.data?.turn;
|
|
9234
|
+
const step = event.data?.step;
|
|
9235
|
+
const seg = {
|
|
9236
|
+
seq: event.seq,
|
|
9237
|
+
time: event.time,
|
|
9238
|
+
turn: typeof turn === "number" ? turn : 0,
|
|
9239
|
+
step: typeof step === "number" ? step : 0,
|
|
9240
|
+
toolCalls,
|
|
9241
|
+
openTurn
|
|
9242
|
+
};
|
|
9243
|
+
if (text !== void 0) seg.text = text;
|
|
9244
|
+
if (reasoning !== void 0) seg.reasoning = reasoning;
|
|
9245
|
+
out.push(seg);
|
|
9246
|
+
}
|
|
9247
|
+
return out;
|
|
9248
|
+
}
|
|
9249
|
+
/** 取自 sinceSeq 之后最新一个已完成输出段落(无则 null)。 */
|
|
9250
|
+
function latestSegmentSince(events, sinceSeq = 0) {
|
|
9251
|
+
const segs = segmentsSince(events, sinceSeq);
|
|
9252
|
+
return segs.length === 0 ? null : segs[segs.length - 1] ?? null;
|
|
9253
|
+
}
|
|
9254
|
+
/**
|
|
9255
|
+
* 对一个 live 会话的事件日志折叠出其"实时思维链":
|
|
9256
|
+
* - 优先聚合流式 assistant/chunk 事件里的 reasoning-delta(turn 中途即可见、增量,
|
|
9257
|
+
* 这正是监控思维链并按规则提前终止所需的粒度);
|
|
9258
|
+
* - 同时聚合同窗口的 text-delta 增量;
|
|
9259
|
+
* - 若没有进行中的 chunk(会话空闲、或该 provider 不流式推理/无推理),返回 null,
|
|
9260
|
+
* 调用方应回落为 foldMessages 里的已定型 message.reasoning。
|
|
9261
|
+
* 纯读、无副作用;事件形状不做任何假设,缺失/异常一律安全处理。
|
|
9262
|
+
*/
|
|
9263
|
+
function liveReasoningSnapshot(events) {
|
|
9264
|
+
const list = asEventList(events);
|
|
9265
|
+
let lastFinalizedSeq = -1;
|
|
9266
|
+
let reasoning = "";
|
|
9267
|
+
let text = "";
|
|
9268
|
+
let seq = -1;
|
|
9269
|
+
let time = 0;
|
|
9270
|
+
let turn = 0;
|
|
9271
|
+
let step = 0;
|
|
9272
|
+
let foundDelta = false;
|
|
9273
|
+
for (const event of list) {
|
|
9274
|
+
if (event.type === "assistant/message") {
|
|
9275
|
+
lastFinalizedSeq = event.seq;
|
|
9276
|
+
continue;
|
|
9277
|
+
}
|
|
9278
|
+
if (event.type !== "assistant/chunk") continue;
|
|
9279
|
+
if (event.seq <= lastFinalizedSeq) continue;
|
|
9280
|
+
const data = event.data;
|
|
9281
|
+
if (data === null || data === void 0 || typeof data !== "object") continue;
|
|
9282
|
+
const c = data.chunk;
|
|
9283
|
+
if (c === null || typeof c !== "object" || typeof c.type !== "string") continue;
|
|
9284
|
+
if (c.type === "reasoning-delta" || c.type === "text-delta") {
|
|
9285
|
+
if (typeof c.text !== "string") continue;
|
|
9286
|
+
if (c.type === "reasoning-delta") reasoning += c.text;
|
|
9287
|
+
else text += c.text;
|
|
9288
|
+
if (typeof data.turn === "number") turn = data.turn;
|
|
9289
|
+
if (typeof data.step === "number") step = data.step;
|
|
9290
|
+
foundDelta = true;
|
|
9291
|
+
if (event.seq > seq) seq = event.seq;
|
|
9292
|
+
if (event.time > time) time = event.time;
|
|
9293
|
+
}
|
|
9294
|
+
}
|
|
9295
|
+
if (!foundDelta) return null;
|
|
9296
|
+
return {
|
|
9297
|
+
turn,
|
|
9298
|
+
step,
|
|
9299
|
+
reasoning,
|
|
9300
|
+
text,
|
|
9301
|
+
seq,
|
|
9302
|
+
time
|
|
9303
|
+
};
|
|
9304
|
+
}
|
|
9305
|
+
/** 思维链尾巴默认截断上限(字符)。 */
|
|
9306
|
+
const REASONING_TAIL_LIMIT = 4e3;
|
|
9307
|
+
/**
|
|
9308
|
+
* 组装一条受字符上限约束的"思维链尾巴"用于展示/摘要:
|
|
9309
|
+
* 优先进行中的 liveReasoning,否则最近已定型的 lastReasoning;
|
|
9310
|
+
* 超出上限时截断并附省略标记。两者皆无返回 undefined。
|
|
9311
|
+
*/
|
|
9312
|
+
function reasoningTailOf(liveReasoning, lastReasoning, limit = REASONING_TAIL_LIMIT) {
|
|
9313
|
+
const source = liveReasoning ?? lastReasoning;
|
|
9314
|
+
if (source === void 0 || source === "") return void 0;
|
|
9315
|
+
if (source.length <= limit) return source;
|
|
9316
|
+
return source.slice(0, limit) + "…";
|
|
9317
|
+
}
|
|
9201
9318
|
/**
|
|
9202
9319
|
* 会话标题:优先 session/title 事件(last-wins,与 UI 一致);
|
|
9203
9320
|
* 无标题事件时回落为第一条用户消息(截断 80 字符)。
|
|
@@ -9249,8 +9366,10 @@ async function waitForReply(opts) {
|
|
|
9249
9366
|
const started = Date.now();
|
|
9250
9367
|
const deadline = started + opts.timeoutMs;
|
|
9251
9368
|
const requireTurnEnd = opts.requireTurnEnd ?? false;
|
|
9369
|
+
const waitForSegment = opts.waitForSegment ?? false;
|
|
9252
9370
|
let latest = null;
|
|
9253
9371
|
let textReply = null;
|
|
9372
|
+
let segment = null;
|
|
9254
9373
|
let turnEnded = false;
|
|
9255
9374
|
for (;;) {
|
|
9256
9375
|
if (opts.signal !== void 0 && opts.signal.aborted) break;
|
|
@@ -9260,22 +9379,38 @@ async function waitForReply(opts) {
|
|
|
9260
9379
|
if (latest === null || row.seq > latest.seq) latest = row;
|
|
9261
9380
|
if (row.text !== void 0 && (textReply === null || row.seq > textReply.seq)) textReply = row;
|
|
9262
9381
|
}
|
|
9382
|
+
if (waitForSegment) {
|
|
9383
|
+
const seg = latestSegmentSince(events, opts.baselineSeq);
|
|
9384
|
+
if (seg !== null && (segment === null || seg.seq > segment.seq)) segment = seg;
|
|
9385
|
+
}
|
|
9263
9386
|
if (requireTurnEnd && latest !== null) {
|
|
9264
9387
|
for (const event of events) if (event.seq > latest.seq && event.type === "turn/end") {
|
|
9265
9388
|
turnEnded = true;
|
|
9266
9389
|
break;
|
|
9267
9390
|
}
|
|
9268
9391
|
}
|
|
9269
|
-
|
|
9392
|
+
let done;
|
|
9393
|
+
if (requireTurnEnd) done = latest !== null && turnEnded;
|
|
9394
|
+
else if (waitForSegment) done = segment !== null;
|
|
9395
|
+
else done = textReply !== null;
|
|
9396
|
+
if (done) break;
|
|
9270
9397
|
if (Date.now() >= deadline) break;
|
|
9271
9398
|
await sleep(100);
|
|
9272
9399
|
}
|
|
9273
|
-
|
|
9400
|
+
let message = waitForSegment && segment !== null ? {
|
|
9401
|
+
seq: segment.seq,
|
|
9402
|
+
time: segment.time,
|
|
9403
|
+
role: "assistant",
|
|
9404
|
+
images: 0,
|
|
9405
|
+
...segment.text !== void 0 ? { text: segment.text } : {},
|
|
9406
|
+
...segment.reasoning !== void 0 ? { reasoning: segment.reasoning } : {},
|
|
9407
|
+
...segment.toolCalls.length > 0 ? { toolCalls: segment.toolCalls } : {}
|
|
9408
|
+
} : textReply ?? latest;
|
|
9274
9409
|
return {
|
|
9275
9410
|
message,
|
|
9276
9411
|
seq: message === null ? opts.baselineSeq : message.seq,
|
|
9277
9412
|
turnEnded,
|
|
9278
|
-
timedOut: requireTurnEnd ? latest !== null && !turnEnded : textReply === null,
|
|
9413
|
+
timedOut: requireTurnEnd ? latest !== null && !turnEnded : waitForSegment ? segment === null : textReply === null,
|
|
9279
9414
|
aborted: opts.signal !== void 0 && opts.signal.aborted,
|
|
9280
9415
|
waitedMs: Date.now() - started
|
|
9281
9416
|
};
|
|
@@ -9351,13 +9486,18 @@ function statusSnapshot(ctx, agent) {
|
|
|
9351
9486
|
}
|
|
9352
9487
|
const recent = rows.slice(-8);
|
|
9353
9488
|
let lastAssistantText;
|
|
9489
|
+
let lastReasoning;
|
|
9354
9490
|
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
9355
9491
|
const row = rows[i];
|
|
9356
|
-
if (row !== void 0 && row.role === "assistant"
|
|
9357
|
-
lastAssistantText = row.text;
|
|
9358
|
-
|
|
9492
|
+
if (row !== void 0 && row.role === "assistant") {
|
|
9493
|
+
if (lastAssistantText === void 0 && row.text !== void 0) lastAssistantText = row.text;
|
|
9494
|
+
if (lastReasoning === void 0 && row.reasoning !== void 0) lastReasoning = row.reasoning;
|
|
9495
|
+
if (lastAssistantText !== void 0 && lastReasoning !== void 0) break;
|
|
9359
9496
|
}
|
|
9360
9497
|
}
|
|
9498
|
+
const live = liveReasoningSnapshot(events);
|
|
9499
|
+
const liveReasoning = live !== null && live.reasoning !== "" ? live.reasoning : void 0;
|
|
9500
|
+
const reasoningTail = reasoningTailOf(liveReasoning, lastReasoning);
|
|
9361
9501
|
const inbox = agent.inbox;
|
|
9362
9502
|
const cwd = agent.session.header.cwd;
|
|
9363
9503
|
return {
|
|
@@ -9375,6 +9515,9 @@ function statusSnapshot(ctx, agent) {
|
|
|
9375
9515
|
nextTurnCount: inbox?.nextTurn?.length ?? 0,
|
|
9376
9516
|
nextStepCount: inbox?.nextStep?.length ?? 0,
|
|
9377
9517
|
...lastAssistantText === void 0 ? {} : { lastAssistantText },
|
|
9518
|
+
...lastReasoning === void 0 ? {} : { lastReasoning },
|
|
9519
|
+
...liveReasoning === void 0 ? {} : { liveReasoning },
|
|
9520
|
+
...reasoningTail === void 0 ? {} : { reasoningTail },
|
|
9378
9521
|
messageCount: rows.length,
|
|
9379
9522
|
recent
|
|
9380
9523
|
};
|
|
@@ -9429,6 +9572,16 @@ var SessionMonitor = class {
|
|
|
9429
9572
|
start(config) {
|
|
9430
9573
|
const existing = this.entries.get(config.sessionId);
|
|
9431
9574
|
const now = Date.now();
|
|
9575
|
+
const cotInit = { rules: (config.coTRules ?? []).reduce((acc, rule, index) => {
|
|
9576
|
+
const key = String(index) + ":" + rule.match + ":" + rule.field + ":" + rule.value;
|
|
9577
|
+
acc[key] = {
|
|
9578
|
+
hits: 0,
|
|
9579
|
+
lastFiredAt: null,
|
|
9580
|
+
lastMatch: false,
|
|
9581
|
+
lastSubject: ""
|
|
9582
|
+
};
|
|
9583
|
+
return acc;
|
|
9584
|
+
}, {}) };
|
|
9432
9585
|
const entry = existing !== void 0 ? {
|
|
9433
9586
|
...existing,
|
|
9434
9587
|
config,
|
|
@@ -9442,8 +9595,10 @@ var SessionMonitor = class {
|
|
|
9442
9595
|
lastActionAt: null,
|
|
9443
9596
|
lastNote: "监控已启动",
|
|
9444
9597
|
done: false,
|
|
9445
|
-
cycles: 0
|
|
9598
|
+
cycles: 0,
|
|
9599
|
+
cot: cotInit
|
|
9446
9600
|
};
|
|
9601
|
+
if (entry.cot === void 0) entry.cot = cotInit;
|
|
9447
9602
|
this.entries.set(config.sessionId, entry);
|
|
9448
9603
|
this.ensureTimer();
|
|
9449
9604
|
this.log(entry, "monitor start session=" + config.sessionId + " interval=" + config.intervalMs + " stalled=" + String(config.stalledMs ?? 6e4) + " maxStuck=" + String(config.maxStuckCycles ?? 3));
|
|
@@ -9569,6 +9724,7 @@ var SessionMonitor = class {
|
|
|
9569
9724
|
this.log(entry, "STALL(#" + entry.stuckCount + ") → steer: " + steer);
|
|
9570
9725
|
return;
|
|
9571
9726
|
}
|
|
9727
|
+
if (this.evaluateCoTRules(entry, snapshot, sessionId)) return;
|
|
9572
9728
|
if (entry.stuckCount !== 0) entry.stuckCount = 0;
|
|
9573
9729
|
entry.lastAction = "steady";
|
|
9574
9730
|
entry.lastActionAt = Date.now();
|
|
@@ -9593,6 +9749,76 @@ var SessionMonitor = class {
|
|
|
9593
9749
|
if (!text) return false;
|
|
9594
9750
|
return keywords.some((kw) => kw !== "" && text.includes(kw));
|
|
9595
9751
|
}
|
|
9752
|
+
/**
|
|
9753
|
+
* 评估思维链(CoT)规则。规则只对 live 会话有意义:running 时用进行中的
|
|
9754
|
+
* reasoning-delta 流,空闲时回落到已定型 lastReasoning。not-contains 规则作用在
|
|
9755
|
+
* reasoning 字段时,若该会话本就没有推理(非推理模型/尚未产出),不判定为命中,
|
|
9756
|
+
* 避免误 cancel 一个根本不产生思维链的会话。
|
|
9757
|
+
* @returns true 表示本 tick 已被某条规则触发并处理(steer/cancel),调用方应 return。
|
|
9758
|
+
*/
|
|
9759
|
+
evaluateCoTRules(entry, snapshot, sessionId) {
|
|
9760
|
+
const rules = entry.config.coTRules ?? [];
|
|
9761
|
+
if (rules.length === 0) return false;
|
|
9762
|
+
const minHits = entry.config.cotMinHits ?? 1;
|
|
9763
|
+
const hasLive = (snapshot.liveReasoning ?? "") !== "";
|
|
9764
|
+
const subject = {
|
|
9765
|
+
reasoning: snapshot.liveReasoning ?? snapshot.lastReasoning ?? "",
|
|
9766
|
+
text: snapshot.lastAssistantText ?? "",
|
|
9767
|
+
hasReasoning: hasLive || (snapshot.lastReasoning ?? "") !== ""
|
|
9768
|
+
};
|
|
9769
|
+
const now = Date.now();
|
|
9770
|
+
const rulesState = entry.cot;
|
|
9771
|
+
let engaged = false;
|
|
9772
|
+
rules.forEach((rule, index) => {
|
|
9773
|
+
if (engaged) return;
|
|
9774
|
+
const key = String(index) + ":" + rule.match + ":" + rule.field + ":" + rule.value;
|
|
9775
|
+
const st = rulesState.rules[key] ?? {
|
|
9776
|
+
hits: 0,
|
|
9777
|
+
lastFiredAt: null,
|
|
9778
|
+
lastMatch: false
|
|
9779
|
+
};
|
|
9780
|
+
if (rule.field === "reasoning" && rule.match === "not-contains" && !subject.hasReasoning) {
|
|
9781
|
+
st.lastMatch = false;
|
|
9782
|
+
st.hits = 0;
|
|
9783
|
+
return;
|
|
9784
|
+
}
|
|
9785
|
+
let hit;
|
|
9786
|
+
if (rule.field === "reasoning") hit = rule.match === "contains" ? subject.reasoning.includes(rule.value) : !subject.reasoning.includes(rule.value);
|
|
9787
|
+
else if (rule.field === "text") hit = rule.match === "contains" ? subject.text.includes(rule.value) : !subject.text.includes(rule.value);
|
|
9788
|
+
else {
|
|
9789
|
+
const reasonHas = subject.reasoning.includes(rule.value);
|
|
9790
|
+
const textHas = subject.text.includes(rule.value);
|
|
9791
|
+
hit = rule.match === "contains" ? reasonHas || textHas : !reasonHas && !textHas;
|
|
9792
|
+
}
|
|
9793
|
+
st.lastMatch = hit;
|
|
9794
|
+
st.hits = hit ? st.hits + 1 : 0;
|
|
9795
|
+
this.log(entry, "CoT rule " + key + " hit=" + String(hit) + " hits=" + String(st.hits) + "/" + String(minHits));
|
|
9796
|
+
if (!(hit && st.hits >= minHits)) return;
|
|
9797
|
+
const coolMs = Math.max(entry.config.intervalMs, 5e3) * 2;
|
|
9798
|
+
if (st.lastFiredAt !== null && now - st.lastFiredAt < coolMs) return;
|
|
9799
|
+
st.lastFiredAt = now;
|
|
9800
|
+
if (rule.action === "cancel") {
|
|
9801
|
+
const reason = "reasoning-rule:" + rule.value;
|
|
9802
|
+
cancelLiveSession(this.ctx, sessionId, false, reason);
|
|
9803
|
+
entry.lastAction = "cot-cancel";
|
|
9804
|
+
entry.lastActionAt = Date.now();
|
|
9805
|
+
entry.lastNote = "思维链规则触发 cancel: " + rule.value;
|
|
9806
|
+
entry.stuckCount = 0;
|
|
9807
|
+
this.log(entry, "COT-CANCEL session=" + sessionId + " rule=" + key + " value=" + rule.value);
|
|
9808
|
+
} else {
|
|
9809
|
+
const steer = rule.message !== void 0 && rule.message.trim() !== "" ? rule.message.trim() : "监控判定:思维链/回复匹配规则「" + rule.value + "」,请据此调整方向并继续推进任务。";
|
|
9810
|
+
sendLiveMessage(this.ctx, sessionId, steer, "steer");
|
|
9811
|
+
entry.lastAction = "cot-steer";
|
|
9812
|
+
entry.lastActionAt = Date.now();
|
|
9813
|
+
entry.lastNote = "思维链规则触发 steer: " + rule.value;
|
|
9814
|
+
entry.stuckCount = 0;
|
|
9815
|
+
this.log(entry, "COT-STEER session=" + sessionId + " rule=" + key + " steer=" + steer);
|
|
9816
|
+
}
|
|
9817
|
+
engaged = true;
|
|
9818
|
+
st.hits = 0;
|
|
9819
|
+
});
|
|
9820
|
+
return engaged;
|
|
9821
|
+
}
|
|
9596
9822
|
hasLlm() {
|
|
9597
9823
|
return this.ctx.llm !== void 0;
|
|
9598
9824
|
}
|
|
@@ -9648,7 +9874,7 @@ var SessionMonitor = class {
|
|
|
9648
9874
|
}
|
|
9649
9875
|
};
|
|
9650
9876
|
//#endregion
|
|
9651
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-
|
|
9877
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1.2-alpha.2_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-invaria_51e6fefef6ddce4f991b3d02344a1a72/node_modules/@deepseek-ai/dsh-scope/lib/index.js
|
|
9652
9878
|
/**
|
|
9653
9879
|
* Shared insertion-ordered storage and effect ownership for scope-aware registries.
|
|
9654
9880
|
*
|
|
@@ -9927,7 +10153,7 @@ function scopeTarget(base, key) {
|
|
|
9927
10153
|
return carrier;
|
|
9928
10154
|
}
|
|
9929
10155
|
//#endregion
|
|
9930
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1.2-alpha.
|
|
10156
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1.2-alpha.2_96d3de6f0f5da790676b5bfb20e4a981/node_modules/@deepseek-ai/dsh-tools/lib/index.js
|
|
9931
10157
|
/**
|
|
9932
10158
|
* Enforced JSON Schema subset shared by tool outputs, generated PTC mode
|
|
9933
10159
|
* types, subagents, and workflows. The subset accepts any JSON root, an
|
|
@@ -13429,7 +13655,7 @@ function toolAbortedBeforeDispatchResult(prior) {
|
|
|
13429
13655
|
};
|
|
13430
13656
|
}
|
|
13431
13657
|
//#endregion
|
|
13432
|
-
//#region node_modules/.pnpm/@deepseek-ai+dsh-agent@0.1.2-alpha.
|
|
13658
|
+
//#region node_modules/.pnpm/@deepseek-ai+dsh-agent@0.1.2-alpha.2_82401c175458b799db42b9e8f7dd7700/node_modules/@deepseek-ai/dsh-agent/lib/index.js
|
|
13433
13659
|
/**
|
|
13434
13660
|
* Agent-scoped model selection shared by runtime entry points.
|
|
13435
13661
|
* @module @deepseek-ai/dsh-agent/model-selection
|
|
@@ -13831,7 +14057,7 @@ function registerResume(env) {
|
|
|
13831
14057
|
function registerWait(env) {
|
|
13832
14058
|
env.ctx.tools.register(defineTool({
|
|
13833
14059
|
name: "session_bridge_wait",
|
|
13834
|
-
description: "Wait for a session next assistant
|
|
14060
|
+
description: "Wait for a session next assistant output: blocks (polling the session log) until a NEW assistant output appears after sinceSeq (default: the latest seq at call time). waitFor=reply returns as soon as a new assistant TEXT reply is readable; waitFor=segment returns as soon as any new COMPLETED output segment appears (an assistant/message step — text, reasoning, or tool-call turn), i.e. it does NOT wait for the whole turn, so you can observe the chain-of-thought/output paragraph by paragraph as it is produced. Returns the output summary, or timedOut/aborted when the deadline or caller cancellation ends the wait. Use it to consume output produced asynchronously by another session (e.g. a session you sent a message to, or one working on its own).",
|
|
13835
14061
|
parameters: {
|
|
13836
14062
|
sessionId: {
|
|
13837
14063
|
type: "string",
|
|
@@ -13849,6 +14075,11 @@ function registerWait(env) {
|
|
|
13849
14075
|
requireTurnEnd: {
|
|
13850
14076
|
type: "boolean",
|
|
13851
14077
|
description: "When true, wait for the reply turn/end to settle before returning (default false; false returns as soon as the reply text is readable)."
|
|
14078
|
+
},
|
|
14079
|
+
waitFor: {
|
|
14080
|
+
type: "string",
|
|
14081
|
+
enum: ["reply", "segment"],
|
|
14082
|
+
description: "reply (default) waits for a new assistant TEXT reply; segment waits for any new completed output segment (an assistant/message step, incl. reasoning/tool turns) and returns it immediately, without waiting for the whole turn."
|
|
13852
14083
|
}
|
|
13853
14084
|
},
|
|
13854
14085
|
output: {
|
|
@@ -13877,9 +14108,13 @@ function registerWait(env) {
|
|
|
13877
14108
|
if (typeof args.sessionId !== "string" || args.sessionId.trim() === "") throw new Error("invalid sessionId: expected a non-empty string");
|
|
13878
14109
|
const agent = liveAgent(env, args.sessionId);
|
|
13879
14110
|
if (agent === void 0) throw new Error("session " + JSON.stringify(args.sessionId) + " is not live — call session_bridge_resume first (waiting requires a live session)");
|
|
14111
|
+
const waitSegment = args.waitFor === "segment";
|
|
13880
14112
|
let baseline;
|
|
13881
14113
|
if (typeof args.sinceSeq === "number" && Number.isInteger(args.sinceSeq) && args.sinceSeq >= 0) baseline = args.sinceSeq;
|
|
13882
|
-
else {
|
|
14114
|
+
else if (waitSegment) {
|
|
14115
|
+
const segs = segmentsSince(sessionEvents(agent.session));
|
|
14116
|
+
baseline = segs.length === 0 ? -1 : segs[segs.length - 1]?.seq ?? -1;
|
|
14117
|
+
} else {
|
|
13883
14118
|
let lastText = -1;
|
|
13884
14119
|
for (const row of foldMessages(sessionEvents(agent.session))) if (row.text !== void 0) lastText = row.seq;
|
|
13885
14120
|
baseline = lastText;
|
|
@@ -13889,7 +14124,8 @@ function registerWait(env) {
|
|
|
13889
14124
|
baselineSeq: baseline,
|
|
13890
14125
|
timeoutMs: clampTimeout(args.timeoutMs),
|
|
13891
14126
|
signal: exec.signal,
|
|
13892
|
-
requireTurnEnd: args.requireTurnEnd === true
|
|
14127
|
+
requireTurnEnd: args.requireTurnEnd === true,
|
|
14128
|
+
...waitSegment ? { waitForSegment: true } : {}
|
|
13893
14129
|
});
|
|
13894
14130
|
env.registry.touch(args.sessionId);
|
|
13895
14131
|
return asJson({
|
|
@@ -13900,6 +14136,83 @@ function registerWait(env) {
|
|
|
13900
14136
|
}
|
|
13901
14137
|
}));
|
|
13902
14138
|
}
|
|
14139
|
+
function registerSegments(env) {
|
|
14140
|
+
env.ctx.tools.register(defineTool({
|
|
14141
|
+
name: "session_bridge_segments",
|
|
14142
|
+
description: "Read the completed output segments of a session incrementally - each finished assistant step (one assistant/message paragraph), without waiting for the whole turn. Pass sinceSeq to page forward. Use this to watch a session produce its chain-of-thought / output paragraph by paragraph as each step finishes.",
|
|
14143
|
+
parameters: {
|
|
14144
|
+
sessionId: {
|
|
14145
|
+
type: "string",
|
|
14146
|
+
required: true,
|
|
14147
|
+
description: "Session id to read segments from (live or offline)."
|
|
14148
|
+
},
|
|
14149
|
+
sinceSeq: {
|
|
14150
|
+
type: "number",
|
|
14151
|
+
description: "Only return completed segments after this event seq (paging cursor)."
|
|
14152
|
+
},
|
|
14153
|
+
limit: {
|
|
14154
|
+
type: "number",
|
|
14155
|
+
description: "Maximum number of segments to return (default 10, max 50)."
|
|
14156
|
+
}
|
|
14157
|
+
},
|
|
14158
|
+
output: {
|
|
14159
|
+
schema: {
|
|
14160
|
+
type: "object",
|
|
14161
|
+
additionalProperties: true
|
|
14162
|
+
},
|
|
14163
|
+
render: (_args, value) => {
|
|
14164
|
+
const segs = value.segments ?? [];
|
|
14165
|
+
if (segs.length === 0) return [{
|
|
14166
|
+
type: "text",
|
|
14167
|
+
text: "(no completed segments)"
|
|
14168
|
+
}];
|
|
14169
|
+
return [{
|
|
14170
|
+
type: "text",
|
|
14171
|
+
text: segs.map((sg) => {
|
|
14172
|
+
const head = "seg #" + String(sg.seq) + " (turn " + String(sg.turn) + " step " + String(sg.step) + ")";
|
|
14173
|
+
const text = typeof sg.text === "string" ? sg.text : "";
|
|
14174
|
+
const reason = typeof sg.reasoning === "string" && sg.reasoning !== "" ? " [reasoning " + sg.reasoning.length + " chars]" : "";
|
|
14175
|
+
return head + (Array.isArray(sg.toolCalls) && sg.toolCalls.length > 0 ? " [tools: " + sg.toolCalls.join(",") + "]" : "") + reason + ": " + text.slice(0, 160);
|
|
14176
|
+
}).join("\n")
|
|
14177
|
+
}];
|
|
14178
|
+
}
|
|
14179
|
+
},
|
|
14180
|
+
async execute(args) {
|
|
14181
|
+
const agent = liveAgent(env, args.sessionId);
|
|
14182
|
+
let events;
|
|
14183
|
+
let live;
|
|
14184
|
+
if (agent !== void 0) {
|
|
14185
|
+
events = sessionEvents(agent.session);
|
|
14186
|
+
live = true;
|
|
14187
|
+
} else try {
|
|
14188
|
+
events = (await env.ctx.sessionPersistence.inspect(args.sessionId)).events;
|
|
14189
|
+
live = false;
|
|
14190
|
+
} catch (error) {
|
|
14191
|
+
throw new Error(String(error));
|
|
14192
|
+
}
|
|
14193
|
+
const sinceSeq = typeof args.sinceSeq === "number" && Number.isInteger(args.sinceSeq) && args.sinceSeq >= 0 ? args.sinceSeq : 0;
|
|
14194
|
+
clampLimit(args.limit, 10, 50);
|
|
14195
|
+
const segs = segmentsSince(events, sinceSeq);
|
|
14196
|
+
env.registry.touch(args.sessionId);
|
|
14197
|
+
const nextCursorSeq = segs.length === 0 ? sinceSeq : segs[segs.length - 1]?.seq ?? sinceSeq;
|
|
14198
|
+
return asJson({
|
|
14199
|
+
sessionId: args.sessionId,
|
|
14200
|
+
live,
|
|
14201
|
+
nextCursorSeq,
|
|
14202
|
+
segments: segs.map((sg) => ({
|
|
14203
|
+
seq: sg.seq,
|
|
14204
|
+
time: sg.time,
|
|
14205
|
+
turn: sg.turn,
|
|
14206
|
+
step: sg.step,
|
|
14207
|
+
openTurn: sg.openTurn,
|
|
14208
|
+
...sg.text === void 0 ? {} : { text: sg.text },
|
|
14209
|
+
...sg.reasoning === void 0 ? {} : { reasoning: sg.reasoning },
|
|
14210
|
+
...sg.toolCalls.length > 0 ? { toolCalls: sg.toolCalls } : {}
|
|
14211
|
+
}))
|
|
14212
|
+
});
|
|
14213
|
+
}
|
|
14214
|
+
}));
|
|
14215
|
+
}
|
|
13903
14216
|
function registerRead(env) {
|
|
13904
14217
|
env.ctx.tools.register(defineTool({
|
|
13905
14218
|
name: "session_bridge_read",
|
|
@@ -14180,6 +14493,7 @@ function registerBridgeTools(env) {
|
|
|
14180
14493
|
registerSend(env);
|
|
14181
14494
|
registerResume(env);
|
|
14182
14495
|
registerWait(env);
|
|
14496
|
+
registerSegments(env);
|
|
14183
14497
|
registerRead(env);
|
|
14184
14498
|
registerFind(env);
|
|
14185
14499
|
registerStatus(env);
|
|
@@ -14187,11 +14501,33 @@ function registerBridgeTools(env) {
|
|
|
14187
14501
|
registerMonitor(env);
|
|
14188
14502
|
registerArchive(env);
|
|
14189
14503
|
}
|
|
14504
|
+
/** 校验并规范化 coRules 参数为 CoTRule[];非法项抛清晰错误。 */
|
|
14505
|
+
function parseCoRules(raw) {
|
|
14506
|
+
return raw.map((rule, index) => {
|
|
14507
|
+
return {
|
|
14508
|
+
match: rule.match === "contains" || rule.match === "not-contains" ? rule.match : (() => {
|
|
14509
|
+
throw new Error("coRules[" + index + "].match must be \"contains\" or \"not-contains\"");
|
|
14510
|
+
})(),
|
|
14511
|
+
field: rule.field === "reasoning" || rule.field === "text" || rule.field === "both" ? rule.field : (() => {
|
|
14512
|
+
throw new Error("coRules[" + index + "].field must be \"reasoning\", \"text\" or \"both\"");
|
|
14513
|
+
})(),
|
|
14514
|
+
action: rule.action === "steer" || rule.action === "cancel" ? rule.action : (() => {
|
|
14515
|
+
throw new Error("coRules[" + index + "].action must be \"steer\" or \"cancel\"");
|
|
14516
|
+
})(),
|
|
14517
|
+
value: typeof rule.value === "string" && rule.value.trim() !== "" ? rule.value.trim() : (() => {
|
|
14518
|
+
throw new Error("coRules[" + index + "].value must be a non-empty string");
|
|
14519
|
+
})(),
|
|
14520
|
+
...typeof rule.message === "string" && rule.message.trim() !== "" ? { message: rule.message.trim() } : {}
|
|
14521
|
+
};
|
|
14522
|
+
});
|
|
14523
|
+
}
|
|
14190
14524
|
function renderMonitorState(entry) {
|
|
14191
14525
|
const lines = [];
|
|
14192
14526
|
lines.push(`monitoring ${entry.config.sessionId}${entry.config.label === void 0 ? "" : " (\"" + entry.config.label + "\")"}`);
|
|
14193
14527
|
lines.push(`interval: ${entry.config.intervalMs}ms | stalled: ${String(entry.config.stalledMs ?? 6e4)}ms | maxStuck: ${String(entry.config.maxStuckCycles ?? 3)}`);
|
|
14194
14528
|
lines.push(`cycles: ${entry.cycles} | stuck: ${entry.stuckCount} | lastAction: ${entry.lastAction}`);
|
|
14529
|
+
const cotCount = (entry.config.coTRules ?? []).length;
|
|
14530
|
+
if (cotCount > 0) lines.push(`coRules: ${cotCount} rule(s) (minHits ${String(entry.config.cotMinHits ?? 1)})`);
|
|
14195
14531
|
if (entry.lastNote !== "") lines.push(`note: ${entry.lastNote}`);
|
|
14196
14532
|
if (entry.done) lines.push("done: yes");
|
|
14197
14533
|
return lines;
|
|
@@ -14199,7 +14535,7 @@ function renderMonitorState(entry) {
|
|
|
14199
14535
|
function registerMonitor(env) {
|
|
14200
14536
|
env.ctx.tools.register(defineTool({
|
|
14201
14537
|
name: "session_bridge_monitor_start",
|
|
14202
|
-
description: "Start a background watchdog on a main session: poll its progress at an interval, and automatically schedule — steer the session when it stalls (or, with useLlm, when it drifts off-track), cancel it when it stays stuck past maxStuckCycles, and stop when a done keyword appears while idle. This is the \"monitor worker\" that watches a main task thread and corrects/stops it. Uses session_bridge_status-style facts; pass sessionId of a live session. Returns the watchdog state.",
|
|
14538
|
+
description: "Start a background watchdog on a main session: poll its progress at an interval, and automatically schedule — steer the session when it stalls (or, with useLlm, when it drifts off-track), cancel it when it stays stuck past maxStuckCycles, and stop when a done keyword appears while idle. It can also enforce chain-of-thought rules via coRules: e.g. coRules=[{match:\"not-contains\",field:\"reasoning\",value:\"I'm\",action:\"cancel\"}] stops the session the moment its live reasoning stops containing I'm (evaluated on each poll while running). This is the \"monitor worker\" that watches a main task thread and corrects/stops it. Uses session_bridge_status-style facts; pass sessionId of a live session. Returns the watchdog state.",
|
|
14203
14539
|
parameters: {
|
|
14204
14540
|
sessionId: {
|
|
14205
14541
|
type: "string",
|
|
@@ -14238,6 +14574,51 @@ function registerMonitor(env) {
|
|
|
14238
14574
|
label: {
|
|
14239
14575
|
type: "string",
|
|
14240
14576
|
description: "Optional human label for logs/display."
|
|
14577
|
+
},
|
|
14578
|
+
coRules: {
|
|
14579
|
+
type: "array",
|
|
14580
|
+
items: {
|
|
14581
|
+
type: "object",
|
|
14582
|
+
additionalProperties: false,
|
|
14583
|
+
properties: {
|
|
14584
|
+
match: {
|
|
14585
|
+
type: "string",
|
|
14586
|
+
required: true,
|
|
14587
|
+
enum: ["contains", "not-contains"],
|
|
14588
|
+
description: "contains = must include value in the chosen field; not-contains = must NOT include it."
|
|
14589
|
+
},
|
|
14590
|
+
field: {
|
|
14591
|
+
type: "string",
|
|
14592
|
+
required: true,
|
|
14593
|
+
enum: [
|
|
14594
|
+
"reasoning",
|
|
14595
|
+
"text",
|
|
14596
|
+
"both"
|
|
14597
|
+
],
|
|
14598
|
+
description: "Match on reasoning (chain-of-thought), text (reply), or both (either)."
|
|
14599
|
+
},
|
|
14600
|
+
value: {
|
|
14601
|
+
type: "string",
|
|
14602
|
+
required: true,
|
|
14603
|
+
description: "The substring to match (non-empty)."
|
|
14604
|
+
},
|
|
14605
|
+
action: {
|
|
14606
|
+
type: "string",
|
|
14607
|
+
required: true,
|
|
14608
|
+
enum: ["steer", "cancel"],
|
|
14609
|
+
description: "steer injects a guiding message; cancel terminates the session."
|
|
14610
|
+
},
|
|
14611
|
+
message: {
|
|
14612
|
+
type: "string",
|
|
14613
|
+
description: "Custom steer text (default is a guidance prompt)."
|
|
14614
|
+
}
|
|
14615
|
+
}
|
|
14616
|
+
},
|
|
14617
|
+
description: "Chain-of-thought rules: when a rule stays matched for cotMinHits consecutive polls (default 1), trigger its action. Example: [{match:\"not-contains\",field:\"reasoning\",value:\"I'm\",action:\"cancel\"}] stops the session the moment its live reasoning no longer contains I'm."
|
|
14618
|
+
},
|
|
14619
|
+
cotMinHits: {
|
|
14620
|
+
type: "number",
|
|
14621
|
+
description: "How many consecutive matched polls before a CoT rule fires (default 1 = immediately)."
|
|
14241
14622
|
}
|
|
14242
14623
|
},
|
|
14243
14624
|
output: {
|
|
@@ -14268,7 +14649,9 @@ function registerMonitor(env) {
|
|
|
14268
14649
|
...args.useLlm === true ? { useLlm: true } : {},
|
|
14269
14650
|
...typeof args.onStallSteer === "string" && args.onStallSteer.trim() !== "" ? { onStallSteer: args.onStallSteer.trim() } : {},
|
|
14270
14651
|
...typeof args.onOffTrackSteer === "string" && args.onOffTrackSteer.trim() !== "" ? { onOffTrackSteer: args.onOffTrackSteer.trim() } : {},
|
|
14271
|
-
...typeof args.label === "string" && args.label.trim() !== "" ? { label: args.label.trim() } : {}
|
|
14652
|
+
...typeof args.label === "string" && args.label.trim() !== "" ? { label: args.label.trim() } : {},
|
|
14653
|
+
...Array.isArray(args.coRules) && args.coRules.length > 0 ? { coTRules: parseCoRules(args.coRules) } : {},
|
|
14654
|
+
...typeof args.cotMinHits === "number" && Number.isInteger(args.cotMinHits) && args.cotMinHits >= 1 ? { cotMinHits: Math.floor(args.cotMinHits) } : {}
|
|
14272
14655
|
};
|
|
14273
14656
|
const entry = env.monitor.start(config);
|
|
14274
14657
|
env.registry.touch(args.sessionId);
|
|
@@ -14340,7 +14723,19 @@ function registerMonitor(env) {
|
|
|
14340
14723
|
}
|
|
14341
14724
|
}));
|
|
14342
14725
|
}
|
|
14343
|
-
/**
|
|
14726
|
+
/** 按 reasoning 选项裁剪快照里的思维链字段(避免入 token 时有 diff 语义差)。 */
|
|
14727
|
+
function pruneReasoning(snapshot, mode) {
|
|
14728
|
+
if (mode === "tail") return snapshot;
|
|
14729
|
+
const rest = { ...snapshot };
|
|
14730
|
+
delete rest.reasoningTail;
|
|
14731
|
+
if (mode === "none") {
|
|
14732
|
+
delete rest.lastReasoning;
|
|
14733
|
+
delete rest.liveReasoning;
|
|
14734
|
+
} else if (mode === "last") delete rest.liveReasoning;
|
|
14735
|
+
else delete rest.lastReasoning;
|
|
14736
|
+
return rest;
|
|
14737
|
+
}
|
|
14738
|
+
/** 渲染监控快照为一行摘要:运行态 + openTurn + 卡住/待处理 + 最新回复(+ 思维链预览)。 */
|
|
14344
14739
|
function renderStatus(snapshot, stalledMsThreshold) {
|
|
14345
14740
|
const lines = [];
|
|
14346
14741
|
const runLabel = snapshot.running === "running" ? "running" : "idle";
|
|
@@ -14353,12 +14748,16 @@ function renderStatus(snapshot, stalledMsThreshold) {
|
|
|
14353
14748
|
}
|
|
14354
14749
|
if (snapshot.pendingWork) lines.push(`pendingWork: ${snapshot.nextTurnCount} turn + ${snapshot.nextStepCount} step`);
|
|
14355
14750
|
if (snapshot.lastAssistantText !== void 0) lines.push(`lastReply: ${snapshot.lastAssistantText}`);
|
|
14751
|
+
if (snapshot.reasoningTail !== void 0 && snapshot.reasoningTail !== "") {
|
|
14752
|
+
const preview = snapshot.reasoningTail.length > 160 ? snapshot.reasoningTail.slice(0, 160) + "…" : snapshot.reasoningTail;
|
|
14753
|
+
lines.push(`reasoning: ${preview}`);
|
|
14754
|
+
}
|
|
14356
14755
|
return lines;
|
|
14357
14756
|
}
|
|
14358
14757
|
function registerStatus(env) {
|
|
14359
14758
|
env.ctx.tools.register(defineTool({
|
|
14360
14759
|
name: "session_bridge_status",
|
|
14361
|
-
description: "Inspect a session's live progress for monitoring/scheduling. Returns running/idle, whether a turn is open, last turn number, time since the last event (for stall detection), pending queued work, and the latest text reply. When stalledMsThreshold is given, marks the session as stalled when the time since the last event exceeds it. Pass sessionId of a live session (use session_bridge_find to locate; session_bridge_resume to bring an offline one online). Use this as the \"observe\" step of a monitor→decide→steer/cancel loop.",
|
|
14760
|
+
description: "Inspect a session's live progress for monitoring/scheduling. Returns running/idle, whether a turn is open, last turn number, time since the last event (for stall detection), pending queued work, and the latest text reply. It also surfaces the session's chain-of-thought: lastReasoning is the most recent finalized reasoning block, liveReasoning is the in-flight reasoning streamed for the current handled turn (reasoning-delta), and reasoningTail is a compact merged preview. reasoning=none drops all three to keep tokens small. When stalledMsThreshold is given, marks the session as stalled when the time since the last event exceeds it. Pass sessionId of a live session (use session_bridge_find to locate; session_bridge_resume to bring an offline one online). Use this as the \"observe\" step of a monitor→decide→steer/cancel loop.",
|
|
14362
14761
|
parameters: {
|
|
14363
14762
|
sessionId: {
|
|
14364
14763
|
type: "string",
|
|
@@ -14372,6 +14771,16 @@ function registerStatus(env) {
|
|
|
14372
14771
|
recent: {
|
|
14373
14772
|
type: "number",
|
|
14374
14773
|
description: "Number of recent messages to include in the snapshot (default 8, max 20)."
|
|
14774
|
+
},
|
|
14775
|
+
reasoning: {
|
|
14776
|
+
type: "string",
|
|
14777
|
+
enum: [
|
|
14778
|
+
"none",
|
|
14779
|
+
"last",
|
|
14780
|
+
"live",
|
|
14781
|
+
"tail"
|
|
14782
|
+
],
|
|
14783
|
+
description: "Which chain-of-thought fields to include: tail (default) returns lastReasoning/liveReasoning/reasoningTail; last only the finalized reasoning; live only the in-flight reasoning; none drops all reasoning fields."
|
|
14375
14784
|
}
|
|
14376
14785
|
},
|
|
14377
14786
|
output: {
|
|
@@ -14397,7 +14806,8 @@ function registerStatus(env) {
|
|
|
14397
14806
|
const agent = liveAgent(env, args.sessionId);
|
|
14398
14807
|
if (agent === void 0) throw new Error("session " + JSON.stringify(args.sessionId) + " is not live — call session_bridge_resume first (status requires a live session)");
|
|
14399
14808
|
const threshold = typeof args.stalledMsThreshold === "number" && Number.isFinite(args.stalledMsThreshold) && args.stalledMsThreshold >= 0 ? args.stalledMsThreshold : 6e4;
|
|
14400
|
-
const
|
|
14809
|
+
const reasoningMode = args.reasoning === "none" || args.reasoning === "last" || args.reasoning === "live" ? args.reasoning : "tail";
|
|
14810
|
+
const snapshot = pruneReasoning(statusSnapshot(env.ctx, agent), reasoningMode);
|
|
14401
14811
|
const shown = typeof args.recent === "number" && Number.isInteger(args.recent) && args.recent >= 0 ? Math.min(args.recent, 20) : 8;
|
|
14402
14812
|
const envCwd = agent.session.header.cwd;
|
|
14403
14813
|
env.registry.touch(args.sessionId, {
|