@shgroup/dsh-serenity-hooks 1.93.0 → 1.94.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/dsh.plugin.json +1 -1
- package/lib/index.js +62 -9
- package/lib/{session-D5cEPEUY.js → session-DFNtReQY.js} +36 -161
- package/lib/trajectory/session.d.ts +36 -0
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.94.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as
|
|
|
4
4
|
import { A as namingTitleFor, B as summarize, C as createSession, E as getActiveSessionInfo, F as resolveSessionByTitle, G as readTrajectoryFile, H as useSession, I as sessionEvents, J as localHuman, K as isoLocal, L as sessionsRoot, M as parseTrajectoryCode, O as listActiveTrajectories, P as readTrajectoryUid, R as setActiveSessionInfo, S as clearActiveSessionInfo, T as findSession, U as TRAJECTORY_FILENAME, V as trajectoryDescOf, W as TRAJECTORY_LEGACY_FILENAMES, Y as localIdStamp, b as UID_FILENAME, c as replacesDefaultIdentity, i as listTrajectoryConstraints, j as parseSessionContextFromEvents, k as listSessions, l as trajectoryCodeForSession, n as findDirNameByCode, o as readTrajectoryConstraintAt, q as localFileStamp, r as isTrajectoryConstrained, s as readTrajectoryConstraintByCode, t as TRAJECTORY_CONSTRAINT_FILENAME, u as trajectoryConstraintForSession, v as COGNITIVE_TRAJECTORY_ACTIONS, w as extractSessionMdPathFromText, x as archiveSession, z as showSession } from "./profile-zxakCNXy.js";
|
|
5
5
|
import { n as checkLocalstoreGitCompliance, o as readStore, s as runLocalStore, t as LOCALSTORE_SCOPES } from "./service-DDmHSglV.js";
|
|
6
6
|
import { _ as resolveConstraintSystemPrompt, a as listBoundSessionIds, d as constraintCognitiveTrajectory, f as constraintSystemPromptSource, g as profileWriteWhitelist, h as profileToolWhitelist, i as carrierState, l as TRAJECTORY_CONSTRAINT_HINT, n as PLUGIN_SOURCE, o as readLastBound, r as appendBound, s as resolveSessionCognitiveTrajectoryLabel, t as ACC_MESSAGE_KIND, v as ACC_VERSION, y as findPackageRoot } from "./message-source-BAMwXu6i.js";
|
|
7
|
-
import { A as buildResourceIndex, C as verifyPassword, D as recordToolUsage, E as verifyTotpCode, F as hasTrajectoryPrompt, M as isSafeSkillName, N as truncateContent, O as REFERENCE_DIR, P as TRAJECTORY_PROMPT_FILENAME, S as toWire, T as MECHANISM_PORTS, _ as registerEntrySkillSectionGlobal, a as trajectoryConstraintFor, b as projectKnownWorkspaces, c as trajectoryWorkspaceMdPath, d as CHEAP_WORKER_GUIDE, f as providerAvailabilityError, g as registerEntrySkillSection, h as registerCognitiveTrajectorySkillSection, i as getTrajectoryAgent, j as buildResourceNameSuffix, k as SKILL_ONTOLOGY_FILE, l as unregisterTrajectorySession, m as splitModel, n as
|
|
7
|
+
import { A as buildResourceIndex, C as verifyPassword, D as recordToolUsage, E as verifyTotpCode, F as hasTrajectoryPrompt, M as isSafeSkillName, N as truncateContent, O as REFERENCE_DIR, P as TRAJECTORY_PROMPT_FILENAME, S as toWire, T as MECHANISM_PORTS, _ as registerEntrySkillSectionGlobal, a as trajectoryConstraintFor, b as projectKnownWorkspaces, c as trajectoryWorkspaceMdPath, d as CHEAP_WORKER_GUIDE, f as providerAvailabilityError, g as registerEntrySkillSection, h as registerCognitiveTrajectorySkillSection, i as getTrajectoryAgent, j as buildResourceNameSuffix, k as SKILL_ONTOLOGY_FILE, l as unregisterTrajectorySession, m as splitModel, n as deliverToTrajectory, o as trajectoryConstraintMsmGate, p as requireWhitelistedModel, r as ensureWorkspacePromptSection, s as trajectoryConstraintParticipates, t as createTrajectoryAgent, u as workspaceCognitiveTrajectoryLine, v as applyWirePatch, w as FACE_PORTS, x as readAdvancedSettings, y as migrateLegacyLocalstore } from "./session-DFNtReQY.js";
|
|
8
8
|
import { i as sessionsRootDir, n as findSessionLog, r as hasSessionLogById } from "./cleanup-CPl1SSaK.js";
|
|
9
9
|
import { i as runImBridge, n as isImWiredTrajectory, o as readSimpleSettings, r as registerImChannel, s as registerSettingsSection, t as imChannelIds } from "./channels-CuCsUfcY.js";
|
|
10
10
|
import { a as readFeishuCredential, c as readWeixinSettings, d as weixinInboundDir, f as weixinSessionIdForTrajectory, i as matchWeixinRoute, n as extractWeixinText, r as hasVoiceItem, s as readWeixinCredential, t as extractWeixinMedia, u as sanitizeFileName$1 } from "./route-DxIKWeN9.js";
|
|
@@ -20231,15 +20231,22 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
|
|
|
20231
20231
|
try {
|
|
20232
20232
|
const sessionId = weixinSessionIdForTrajectory(trajectory);
|
|
20233
20233
|
/**
|
|
20234
|
-
* 🔴 **同会话串行化**(2026-10-21
|
|
20234
|
+
* 🔴 **同会话串行化**(2026-10-21 引入;**2026-10-24 理由已变,见下**)。
|
|
20235
20235
|
*
|
|
20236
|
-
*
|
|
20237
|
-
*
|
|
20238
|
-
* ⇒
|
|
20236
|
+
* ## 原理由(已失效,留档 R↓)
|
|
20237
|
+
* `askTrajectory` 的成功判据是"`assistant/message` **条数增长**",两条入站并发时
|
|
20238
|
+
* 后一条的快照包含前一条尚未落盘的产出 ⇒ 前一轮的产出满足后一条的判据 ⇒
|
|
20239
|
+
* 兜底可能拿到**错轮**的答案(真机实测:把内部过程话发给了家人)。
|
|
20239
20240
|
*
|
|
20240
|
-
*
|
|
20241
|
-
*
|
|
20242
|
-
*
|
|
20241
|
+
* ## 🔴 现在的理由(owner 2026-10-24 令「正在跑就直接进、不排队」之后)
|
|
20242
|
+
* 入站改走 `deliverToTrajectory`(**不等回答**)⇒ 上面那条"错轮答案"的路径**已不存在**。
|
|
20243
|
+
* 锁**仍然需要**,但它现在防的是另一件事:
|
|
20244
|
+
* **`createTrajectoryAgent` 的"查-没有-再建"** —— 两条入站同时到达且都发现"没有载体"时,
|
|
20245
|
+
* 会各自建一个 ⇒ **同一轨迹两个载体**(撞 D-族「一条轨迹至多一个载体」的底线)。
|
|
20246
|
+
* ⇒ 锁的**覆盖范围应当只包住"投递"这一步**,⛔ 不该再包住整轮等待
|
|
20247
|
+
* (那正是 owner 说的"排队")。投递本身是同步的几十微秒,串行化代价可忽略。
|
|
20248
|
+
*
|
|
20249
|
+
* ⚠️ 粒度 = **一个会话同一时刻只处理一条入站的投递**(不同轨迹各跑各的)。
|
|
20243
20250
|
*/
|
|
20244
20251
|
releaseLock = await acquireSessionLock(sessionId);
|
|
20245
20252
|
const existing = getTrajectoryAgent(sessionId);
|
|
@@ -20355,7 +20362,53 @@ async function handleInboundEvent(ctx, root, accountId, io, event, preset) {
|
|
|
20355
20362
|
})).catch((err) => {
|
|
20356
20363
|
console.log(`[serenity-hooks] im hook incoming error: ${err instanceof Error ? err.message : String(err)}`);
|
|
20357
20364
|
});
|
|
20358
|
-
|
|
20365
|
+
/**
|
|
20366
|
+
* 🔴🔴 **2026-10-24(owner 令):正在跑就**直接进**,不排队。**
|
|
20367
|
+
*
|
|
20368
|
+
* owner 原话:「当用户消息进来,且绑定轨迹正在活动时,消息是排队而不是直接进入,
|
|
20369
|
+
* 应当改为直接进入,这样确保来回通信的流畅性」。
|
|
20370
|
+
*
|
|
20371
|
+
* **修前**:`await askTrajectory(...)` = `followup()`(排队语义)+ **等整轮跑完**。
|
|
20372
|
+
* ⇒ 用户连说两句,第二句要等第一句那一整轮结束才被看见 ⇒ **来回通信卡顿**。
|
|
20373
|
+
*
|
|
20374
|
+
* **为什么现在可以不等**:D238(同日,出站只剩 `im-bridge`)之后,
|
|
20375
|
+
* 那个 `answer` **已经没有消费方**(上一版这里就是 `void result`)——
|
|
20376
|
+
* 整轮等待**只剩"把后一条堵在前面"这一个副作用**。
|
|
20377
|
+
*
|
|
20378
|
+
* **投递形态**(`deliverToTrajectory`,与 `interrupted.ts` 的 `createResumeOne` 同判据):
|
|
20379
|
+
* 在跑 ⇒ `steer`(当场进当前轮,模型同一次生成里就看到);
|
|
20380
|
+
* 空闲 ⇒ `followup`(立刻起一轮);`steer` 缺失 ⇒ 排队也比丢掉好。
|
|
20381
|
+
*/
|
|
20382
|
+
const delivered = deliverToTrajectory(ref.agent, question);
|
|
20383
|
+
/**
|
|
20384
|
+
* 🔴🔴 **2026-10-24(owner 令,D238):这里不再有任何"桥转发最终文本"的分支。**
|
|
20385
|
+
*
|
|
20386
|
+
* 删掉的两段(历史留档,R↓):
|
|
20387
|
+
* ① `manualOutput` 的兜底转发(原 `fallbackOnNoSend` + `source:'reply-fallback'`);
|
|
20388
|
+
* ② 自动模式的直接转发(原 `io.sendText` + `source:'reply'` 缺省)。
|
|
20389
|
+
*
|
|
20390
|
+
* **留下的唯一事实**:投递之后**什么都不发** —— 出站只可能由 agent
|
|
20391
|
+
* 自己调 `im-bridge` 产生(`source:'proactive'`)。
|
|
20392
|
+
*
|
|
20393
|
+
* ⚠️ `delivered.how` 要记:它回答"这条消息是怎么进去的"
|
|
20394
|
+
* (`steer` = 当场进当前轮 / `followup` = 排进下一轮 / `none` = 无通道)。
|
|
20395
|
+
* **不记它就无法从日志上分清"不排队"到底生效了没有**(D216 同族)。
|
|
20396
|
+
*/
|
|
20397
|
+
if (delivered.how === "none") console.log(`[serenity-hooks] im 投递失败:会话 ${sessionId} 既无 steer 也无 followup(宿主契约漂移)`);
|
|
20398
|
+
else console.log(`[serenity-hooks] im 投递:会话 ${sessionId} → ${delivered.how}(不排队;出站走 im-bridge)`);
|
|
20399
|
+
/**
|
|
20400
|
+
* 🔴🔴 **2026-10-24(owner 令「不排队」):锁在这里就放,⛔ 不再等整轮。**
|
|
20401
|
+
*
|
|
20402
|
+
* 投递已在上一步**同步**完成(`steer` / `followup` 都是同步调用)⇒
|
|
20403
|
+
* 锁要保护的"查载体-没有-再建"那一段**已经结束**。
|
|
20404
|
+
* ⇒ 从这里往后(typingStop、日志、hook 回调)没有任何"必须串行"的事,
|
|
20405
|
+
* 让锁继续持有 = **把下一条消息堵在这里** = owner 说的"排队"。
|
|
20406
|
+
*
|
|
20407
|
+
* ⚠️ 早期释放是**安全**的:下面 `finally` 里的 `releaseLock?.()` 有 `released` 幂等判据
|
|
20408
|
+
* (见 `acquireSessionLock` 的返回),重复调用是空操作 ⇒ 不会二次释放。
|
|
20409
|
+
*/
|
|
20410
|
+
releaseLock?.();
|
|
20411
|
+
releaseLock = null;
|
|
20359
20412
|
} finally {
|
|
20360
20413
|
await io.typingStop?.({ toUserId: fromUserId });
|
|
20361
20414
|
}
|
|
@@ -142,47 +142,6 @@ function trajectoryPromptBlock(text) {
|
|
|
142
142
|
].join("\n");
|
|
143
143
|
}
|
|
144
144
|
//#endregion
|
|
145
|
-
//#region src/trajectory/carrier.ts
|
|
146
|
-
/**
|
|
147
|
-
* 等待 agent 空闲或销毁。
|
|
148
|
-
* @param ctx 宿主插件上下文(事件通道来源;**结构化访问**,故类型为 `unknown`,见文件头 I2 段)
|
|
149
|
-
* @param agent 目标 agent
|
|
150
|
-
* @returns 空闲、agent 销毁、或会话销毁时结算(永不 reject)
|
|
151
|
-
*/
|
|
152
|
-
function waitAgentIdle(ctx, agent) {
|
|
153
|
-
return new Promise((resolve) => {
|
|
154
|
-
let settled = false;
|
|
155
|
-
const disposers = [];
|
|
156
|
-
const finish = () => {
|
|
157
|
-
if (settled) return;
|
|
158
|
-
settled = true;
|
|
159
|
-
for (const dispose of disposers) try {
|
|
160
|
-
dispose();
|
|
161
|
-
} catch {}
|
|
162
|
-
resolve();
|
|
163
|
-
};
|
|
164
|
-
const subscribe = (name, cb) => {
|
|
165
|
-
try {
|
|
166
|
-
const off = ctx.on?.(name, cb);
|
|
167
|
-
if (typeof off === "function") disposers.push(off);
|
|
168
|
-
} catch {}
|
|
169
|
-
};
|
|
170
|
-
subscribe("agent/status", (payload) => {
|
|
171
|
-
const p = payload;
|
|
172
|
-
if (p?.agent === agent && p?.status === "idle") finish();
|
|
173
|
-
});
|
|
174
|
-
subscribe("agent/disposed", (payload) => {
|
|
175
|
-
if (payload?.agent === agent) finish();
|
|
176
|
-
});
|
|
177
|
-
const sessionId = agent.session?.id;
|
|
178
|
-
subscribe("session/disposed", (session) => {
|
|
179
|
-
const id = session?.id;
|
|
180
|
-
if (typeof id === "string" && id === sessionId) finish();
|
|
181
|
-
});
|
|
182
|
-
if (agent.status === "idle") finish();
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
//#endregion
|
|
186
145
|
//#region src/skills/discovery.ts
|
|
187
146
|
/**
|
|
188
147
|
* skills/discovery.ts — CCC 入口 skill 自动发现(纯逻辑,零 DSH 依赖)
|
|
@@ -2528,136 +2487,52 @@ async function createOrResumeAgent(ctx, id, root, model, setup, fixedId) {
|
|
|
2528
2487
|
};
|
|
2529
2488
|
}
|
|
2530
2489
|
}
|
|
2531
|
-
/** 会话事件读取(兼容 `snapshotEvents()` 与测试替身的 `.events`) */
|
|
2532
|
-
function sessionEvents(session) {
|
|
2533
|
-
const s = session;
|
|
2534
|
-
if (typeof s.snapshotEvents === "function") return s.snapshotEvents();
|
|
2535
|
-
return s.events ?? [];
|
|
2536
|
-
}
|
|
2537
|
-
/** 读会话最后一个 assistant/message 文本 */
|
|
2538
|
-
function lastAssistantText(agent) {
|
|
2539
|
-
const events = sessionEvents(agent.session);
|
|
2540
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
2541
|
-
const e = events[i];
|
|
2542
|
-
if (e && e.type === "assistant/message") {
|
|
2543
|
-
const text = (e.data?.message?.content ?? e.data?.content ?? []).filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n");
|
|
2544
|
-
if (text) return text;
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
return "";
|
|
2548
|
-
}
|
|
2549
|
-
/** 本轮等待上限(毫秒):超时即判「本轮无产出」——**绝不**把上一轮的旧答案当本轮答案 */
|
|
2550
|
-
const ASK_TURN_TIMEOUT_MS = 18e4;
|
|
2551
|
-
/** 会话里 `assistant/message` 的条数(判「本轮是否真的产出了回答」) */
|
|
2552
|
-
function countAssistantMessages(session) {
|
|
2553
|
-
let n = 0;
|
|
2554
|
-
for (const e of sessionEvents(session)) if (e?.type === "assistant/message") n += 1;
|
|
2555
|
-
return n;
|
|
2556
|
-
}
|
|
2557
|
-
/**
|
|
2558
|
-
* 会话当前轮号(最后一个 `turn/start` 的 `turn`;取不到 ⇒ `null`)。
|
|
2559
|
-
*
|
|
2560
|
-
* 🔴 为什么需要它(2026-10-21,"同一条消息被处理两轮"的第二处缺陷):
|
|
2561
|
-
* 判"本轮是否产出"用的是 **`assistant/message` 条数增长**。而在**并发入站**下,
|
|
2562
|
-
* 后一条的快照里,**前一条那一轮**还在继续产出 assistant 消息 ⇒ 计数照样增长
|
|
2563
|
-
* ⇒ 后一条会把**别人那一轮的文本**当成自己的答案(真机实测:兜底把内部过程话发给了家人)。
|
|
2564
|
-
* ⇒ 增长必须限定在「**这一轮开始之后**新出现的事件」里。
|
|
2565
|
-
*/
|
|
2566
|
-
function currentTurnOfSession(session) {
|
|
2567
|
-
const events = sessionEvents(session);
|
|
2568
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
2569
|
-
const e = events[i];
|
|
2570
|
-
if (e?.type === "turn/start") {
|
|
2571
|
-
const t = e.data?.turn;
|
|
2572
|
-
return typeof t === "number" && Number.isFinite(t) ? t : null;
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
return null;
|
|
2576
|
-
}
|
|
2577
|
-
function snapshotSession(session) {
|
|
2578
|
-
return {
|
|
2579
|
-
turn: currentTurnOfSession(session),
|
|
2580
|
-
assistantCount: countAssistantMessages(session),
|
|
2581
|
-
eventCount: sessionEvents(session).length
|
|
2582
|
-
};
|
|
2583
|
-
}
|
|
2584
2490
|
/**
|
|
2585
|
-
*
|
|
2491
|
+
* 把一条用户消息**投进**载体会话 —— 🔴 **2026-10-24 owner 令:正在跑就直接进,不排队**。
|
|
2586
2492
|
*
|
|
2587
|
-
*
|
|
2588
|
-
*
|
|
2589
|
-
*
|
|
2590
|
-
* ③ 条数确实增长 ⇒ 成功。
|
|
2591
|
-
*/
|
|
2592
|
-
function grewWithFreshAssistant(session, snap) {
|
|
2593
|
-
const events = sessionEvents(session);
|
|
2594
|
-
if (events.length <= snap.eventCount) return false;
|
|
2595
|
-
const now = currentTurnOfSession(session);
|
|
2596
|
-
if (snap.turn !== null && now !== null && now !== snap.turn) return false;
|
|
2597
|
-
let fresh = 0;
|
|
2598
|
-
for (let i = snap.eventCount; i < events.length; i++) if (events[i]?.type === "assistant/message") fresh += 1;
|
|
2599
|
-
if (fresh > 0) return true;
|
|
2600
|
-
return countAssistantMessages(session) > snap.assistantCount;
|
|
2601
|
-
}
|
|
2602
|
-
/**
|
|
2603
|
-
* 等**本轮**产出一条 assistant 消息。
|
|
2493
|
+
* ## owner 原话
|
|
2494
|
+
* 「**当用户消息进来,且绑定轨迹正在活动时,消息是排队而不是直接进入,应当改为直接进入,
|
|
2495
|
+
* 这样确保来回通信的流畅性**」。
|
|
2604
2496
|
*
|
|
2605
|
-
* ##
|
|
2497
|
+
* ## 修前为什么是"排队"
|
|
2498
|
+
* 入站走 {@link askTrajectory}:它 `followup()`(**排队语义**,等本轮结束才开新一轮)
|
|
2499
|
+
* 然后**等整轮跑完**读答案。而 D238(2026-10-24,出站只剩 `im-bridge`)之后
|
|
2500
|
+
* 那个答案**已经没有消费方**(调用点是 `void result`)⇒
|
|
2501
|
+
* **整轮等待只剩副作用:把后一条消息堵在前一条那一轮后面**。
|
|
2606
2502
|
*
|
|
2607
|
-
*
|
|
2608
|
-
*
|
|
2609
|
-
*
|
|
2610
|
-
*
|
|
2503
|
+
* ## 判据(与 `trajectory/interrupted.ts` 的 `createResumeOne` 同源)
|
|
2504
|
+
* - **`agent.status === 'running'` 且 `steer` 可用** ⇒ `steer`:**当场注入当前轮**
|
|
2505
|
+
* (模型在同一次生成里就看到这句话 ⇒ 来回通信连续)。
|
|
2506
|
+
* - 否则 `followup`:空闲 ⇒ 立刻起一轮;`steer` 缺失 ⇒ 排队也比丢掉好。
|
|
2611
2507
|
*
|
|
2612
|
-
* ##
|
|
2508
|
+
* ## 为什么本函数**不等**回答
|
|
2509
|
+
* ① 答案没人读(D238 之后出站只能由 agent 自己调 `im-bridge`);
|
|
2510
|
+
* ② **等待正是"排队"的机制本体** —— 不等,锁就只覆盖投递那一下,
|
|
2511
|
+
* 下一条消息才能也当场进去。
|
|
2512
|
+
* ⇒ 语义从"**问一句、等回答**"变成"**把话递进去**"。这也更贴近 IM 的真实形态:
|
|
2513
|
+
* 用户是连续说话的,不是一问一答的请求-响应。
|
|
2613
2514
|
*
|
|
2614
|
-
*
|
|
2615
|
-
*
|
|
2616
|
-
* ③ 超时或有界轮询耗尽 ⇒ `false`(调用方据此给空答案,兜底不触发)。
|
|
2515
|
+
* ⚠️ **与 {@link askTrajectory} 的分工**:后者保留"问 + 等本轮答案"的完整语义
|
|
2516
|
+
* (仍有消费方/测试在用);本函数是**入站投递**专用。
|
|
2617
2517
|
*/
|
|
2618
|
-
|
|
2619
|
-
const
|
|
2620
|
-
|
|
2621
|
-
* 快照兼容两种入参:数字(旧调用方的"条数")与 `SessionSnapshot`(新,带轮号与事件位)。
|
|
2622
|
-
* 🔴 数字形态**仍然可用**,因为既有测试与调用点按数字传,而它们的语义("条数增长")
|
|
2623
|
-
* 在本轮修法之后依然成立 —— 只是**并发**时不够。给 `SessionSnapshot` 才拿到全部判别力。
|
|
2624
|
-
*/
|
|
2625
|
-
const snap = typeof before === "number" ? {
|
|
2626
|
-
turn: null,
|
|
2627
|
-
assistantCount: before,
|
|
2628
|
-
eventCount: 0
|
|
2629
|
-
} : before;
|
|
2630
|
-
const grew = () => typeof before === "number" ? countAssistantMessages(agent.session) > before : grewWithFreshAssistant(agent.session, snap);
|
|
2631
|
-
if (await Promise.race([waitAgentIdle(ctx, agent).then(() => true), new Promise((resolve) => {
|
|
2632
|
-
setTimeout(() => resolve(false), timeoutMs);
|
|
2633
|
-
})])) return grew();
|
|
2634
|
-
return grew();
|
|
2635
|
-
}
|
|
2636
|
-
/**
|
|
2637
|
-
* 提问一轮:followup → 等**本轮**回答 → 读答案。
|
|
2638
|
-
*
|
|
2639
|
-
* ## 🔴 2026-10-21:快照从"条数"升级为"(轮号, 条数, 事件位)"
|
|
2640
|
-
*
|
|
2641
|
-
* 并发入站时,后一条的快照里**前一条那一轮还在产出** ⇒ 旧判据(条数增长)会被满足
|
|
2642
|
-
* ⇒ 后一条把**别人那一轮的文本**当自己的答案。新快照让"增长"只在**本轮的窗口内**才成立。
|
|
2643
|
-
* 配套:调用侧(`im/inbound-event.ts`)给**同一会话**加了串行锁 —— 两层都要,缺一不可:
|
|
2644
|
-
* 锁防"同时开两轮",快照防"锁被绕过时(如别的路径直接调本函数)把错轮的文本当答案"。
|
|
2645
|
-
*
|
|
2646
|
-
* @param options.timeoutMs 本轮等待上限(测试注入;生产用缺省)
|
|
2647
|
-
*/
|
|
2648
|
-
async function askTrajectory(ctx, agent, question, options) {
|
|
2649
|
-
const beforeSnapshot = snapshotSession(agent.session);
|
|
2650
|
-
agent.followup(createUserMessage({
|
|
2518
|
+
function deliverToTrajectory(agent, text) {
|
|
2519
|
+
const a = agent;
|
|
2520
|
+
const msg = createUserMessage({
|
|
2651
2521
|
content: [{
|
|
2652
2522
|
type: "text",
|
|
2653
|
-
text
|
|
2523
|
+
text
|
|
2654
2524
|
}],
|
|
2655
2525
|
source: PLUGIN_SOURCE
|
|
2656
|
-
})
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
}
|
|
2526
|
+
});
|
|
2527
|
+
if (a.status === "running" && typeof a.steer === "function") {
|
|
2528
|
+
a.steer(msg);
|
|
2529
|
+
return { how: "steer" };
|
|
2530
|
+
}
|
|
2531
|
+
if (typeof a.followup === "function") {
|
|
2532
|
+
a.followup(msg);
|
|
2533
|
+
return { how: "followup" };
|
|
2534
|
+
}
|
|
2535
|
+
return { how: "none" };
|
|
2661
2536
|
}
|
|
2662
2537
|
/**
|
|
2663
2538
|
* 轨迹约束会话的轨迹纪律参与判定:**不是 轨迹约束恒 true**(正常参与);
|
|
@@ -2692,4 +2567,4 @@ function trajectoryConstraintMsmGate(root, sessionId, action, name) {
|
|
|
2692
2567
|
return {};
|
|
2693
2568
|
}
|
|
2694
2569
|
//#endregion
|
|
2695
|
-
export { buildResourceIndex as A, verifyPassword as C, recordToolUsage as D, verifyTotpCode as E, hasTrajectoryPrompt as F, isSafeSkillName as M, truncateContent as N, REFERENCE_DIR as O, TRAJECTORY_PROMPT_FILENAME as P, toWire as S, MECHANISM_PORTS as T, registerEntrySkillSectionGlobal as _, trajectoryConstraintFor as a, projectKnownWorkspaces as b, trajectoryWorkspaceMdPath as c, CHEAP_WORKER_GUIDE as d, providerAvailabilityError as f, registerEntrySkillSection as g, registerCognitiveTrajectorySkillSection as h, getTrajectoryAgent as i, buildResourceNameSuffix as j, SKILL_ONTOLOGY_FILE as k, unregisterTrajectorySession as l, splitModel as m,
|
|
2570
|
+
export { buildResourceIndex as A, verifyPassword as C, recordToolUsage as D, verifyTotpCode as E, hasTrajectoryPrompt as F, isSafeSkillName as M, truncateContent as N, REFERENCE_DIR as O, TRAJECTORY_PROMPT_FILENAME as P, toWire as S, MECHANISM_PORTS as T, registerEntrySkillSectionGlobal as _, trajectoryConstraintFor as a, projectKnownWorkspaces as b, trajectoryWorkspaceMdPath as c, CHEAP_WORKER_GUIDE as d, providerAvailabilityError as f, registerEntrySkillSection as g, registerCognitiveTrajectorySkillSection as h, getTrajectoryAgent as i, buildResourceNameSuffix as j, SKILL_ONTOLOGY_FILE as k, unregisterTrajectorySession as l, splitModel as m, deliverToTrajectory as n, trajectoryConstraintMsmGate as o, requireWhitelistedModel as p, ensureWorkspacePromptSection as r, trajectoryConstraintParticipates as s, createTrajectoryAgent as t, workspaceCognitiveTrajectoryLine as u, applyWirePatch as v, FACE_PORTS as w, readAdvancedSettings as x, migrateLegacyLocalstore as y };
|
|
@@ -137,9 +137,45 @@ export declare function waitForTurnAssistant(ctx: unknown, agent: Agent, before:
|
|
|
137
137
|
timeoutMs?: number;
|
|
138
138
|
pollMs?: number;
|
|
139
139
|
}): Promise<boolean>;
|
|
140
|
+
/**
|
|
141
|
+
* 把一条用户消息**投进**载体会话 —— 🔴 **2026-10-24 owner 令:正在跑就直接进,不排队**。
|
|
142
|
+
*
|
|
143
|
+
* ## owner 原话
|
|
144
|
+
* 「**当用户消息进来,且绑定轨迹正在活动时,消息是排队而不是直接进入,应当改为直接进入,
|
|
145
|
+
* 这样确保来回通信的流畅性**」。
|
|
146
|
+
*
|
|
147
|
+
* ## 修前为什么是"排队"
|
|
148
|
+
* 入站走 {@link askTrajectory}:它 `followup()`(**排队语义**,等本轮结束才开新一轮)
|
|
149
|
+
* 然后**等整轮跑完**读答案。而 D238(2026-10-24,出站只剩 `im-bridge`)之后
|
|
150
|
+
* 那个答案**已经没有消费方**(调用点是 `void result`)⇒
|
|
151
|
+
* **整轮等待只剩副作用:把后一条消息堵在前一条那一轮后面**。
|
|
152
|
+
*
|
|
153
|
+
* ## 判据(与 `trajectory/interrupted.ts` 的 `createResumeOne` 同源)
|
|
154
|
+
* - **`agent.status === 'running'` 且 `steer` 可用** ⇒ `steer`:**当场注入当前轮**
|
|
155
|
+
* (模型在同一次生成里就看到这句话 ⇒ 来回通信连续)。
|
|
156
|
+
* - 否则 `followup`:空闲 ⇒ 立刻起一轮;`steer` 缺失 ⇒ 排队也比丢掉好。
|
|
157
|
+
*
|
|
158
|
+
* ## 为什么本函数**不等**回答
|
|
159
|
+
* ① 答案没人读(D238 之后出站只能由 agent 自己调 `im-bridge`);
|
|
160
|
+
* ② **等待正是"排队"的机制本体** —— 不等,锁就只覆盖投递那一下,
|
|
161
|
+
* 下一条消息才能也当场进去。
|
|
162
|
+
* ⇒ 语义从"**问一句、等回答**"变成"**把话递进去**"。这也更贴近 IM 的真实形态:
|
|
163
|
+
* 用户是连续说话的,不是一问一答的请求-响应。
|
|
164
|
+
*
|
|
165
|
+
* ⚠️ **与 {@link askTrajectory} 的分工**:后者保留"问 + 等本轮答案"的完整语义
|
|
166
|
+
* (仍有消费方/测试在用);本函数是**入站投递**专用。
|
|
167
|
+
*/
|
|
168
|
+
export declare function deliverToTrajectory(agent: Agent, text: string): {
|
|
169
|
+
how: string;
|
|
170
|
+
};
|
|
140
171
|
/**
|
|
141
172
|
* 提问一轮:followup → 等**本轮**回答 → 读答案。
|
|
142
173
|
*
|
|
174
|
+
* ⚠️ **2026-10-24:本函数已不再是 IM 入站的投递路径**(改用 {@link deliverToTrajectory},
|
|
175
|
+
* 因为 owner 要求"正在跑就直接进、不排队",且 D238 之后答案已无消费方)。
|
|
176
|
+
* 保留它是因为"问一句并等这一轮的答案"这个语义本身仍然有效(测试与将来的同步调用方)。
|
|
177
|
+
* ⛔ **不要把它改回入站路径** —— 那会把排队一起带回来。
|
|
178
|
+
*
|
|
143
179
|
* ## 🔴 2026-10-21:快照从"条数"升级为"(轮号, 条数, 事件位)"
|
|
144
180
|
*
|
|
145
181
|
* 并发入站时,后一条的快照里**前一条那一轮还在产出** ⇒ 旧判据(条数增长)会被满足
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.94.0",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|