@shgroup/dsh-serenity-hooks 1.87.2 → 1.87.4
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/{ccc-roots-BI_4UVcj.js → ccc-roots-CR1IWLDJ.js} +1 -1
- package/lib/{cleanup-C-92qnDs.js → cleanup-DxX78__Z.js} +1 -1
- package/lib/{ilink-BMk2w2KY.js → ilink-CsZ-f64q.js} +52 -2
- package/lib/im/ilink.d.ts +26 -0
- package/lib/index.js +426 -102
- package/lib/{message-source-DRkQwfgh.js → message-source-HLXLQuvR.js} +2 -2
- package/lib/{profile-vHheCJiU.js → profile-CQWuuopQ.js} +2 -1
- package/lib/{session-DZXSLLEx.js → session-CksIqzii.js} +3 -3
- package/lib/{settings-H7TVDayZ.js → settings-D14eA2MR.js} +1 -1
- package/lib/trajectory/core.d.ts +8 -1
- package/lib/trajectory/interrupted.d.ts +122 -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.87.
|
|
3
|
+
"version": "1.87.4",
|
|
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": {
|
|
@@ -227,7 +227,7 @@ async function listCccs(ctx, opts = {}) {
|
|
|
227
227
|
if (typeof defaultRoot === "string" && defaultRoot !== "" && !c.roots.includes(defaultRoot)) c.roots.unshift(defaultRoot);
|
|
228
228
|
let rolesOf = null;
|
|
229
229
|
if (opts.withRoles === true) {
|
|
230
|
-
const { listTrajectoryConstraints } = await import("./profile-
|
|
230
|
+
const { listTrajectoryConstraints } = await import("./profile-CQWuuopQ.js").then((n) => n.a);
|
|
231
231
|
rolesOf = (root) => {
|
|
232
232
|
try {
|
|
233
233
|
return listTrajectoryConstraints(root).map((p) => p.code);
|
|
@@ -210,4 +210,4 @@ function cutoffDaysAgo(days, nowMs = Date.now()) {
|
|
|
210
210
|
return nowMs - days * DAY_MS;
|
|
211
211
|
}
|
|
212
212
|
//#endregion
|
|
213
|
-
export {
|
|
213
|
+
export { sessionsRootDir as i, findSessionLog as n, hasSessionLogById as r, cleanup_exports as t };
|
|
@@ -20,6 +20,7 @@ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:
|
|
|
20
20
|
var ilink_exports = /* @__PURE__ */ __exportAll({
|
|
21
21
|
TypingStatus: () => TypingStatus,
|
|
22
22
|
aes128EcbDecrypt: () => aes128EcbDecrypt,
|
|
23
|
+
buildBaseInfo: () => buildBaseInfo,
|
|
23
24
|
buildClientVersion: () => buildClientVersion,
|
|
24
25
|
buildMediaDownloadUrl: () => buildMediaDownloadUrl,
|
|
25
26
|
downloadMedia: () => downloadMedia,
|
|
@@ -65,6 +66,30 @@ function buildCommonHeaders() {
|
|
|
65
66
|
"iLink-App-ClientVersion": String(CLIENT_VERSION)
|
|
66
67
|
};
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* `base_info` —— **每一个业务 POST 都必须带**(2026-10-22 补齐,完全照官方实现)。
|
|
71
|
+
*
|
|
72
|
+
* ## 🔴 我们漏了这个字段,漏了很久
|
|
73
|
+
*
|
|
74
|
+
* 腾讯官方 `@tencent-weixin/openclaw-weixin` 的 `src/api/api.ts` 里,**九处**业务 POST
|
|
75
|
+
* 全部无条件附加它(`body: JSON.stringify({ ...body, base_info: buildBaseInfo() })`),
|
|
76
|
+
* 而 `buildBaseInfo()` 恒为 `{ channel_version: pkg.version }`。
|
|
77
|
+
*
|
|
78
|
+
* 我们这一侧长期只发 `{ msg: {...} }` —— **`base_info` 完全没有**。
|
|
79
|
+
*
|
|
80
|
+
* ## 症状为什么是「平时能发,隔久了发不出」
|
|
81
|
+
*
|
|
82
|
+
* 缺字段本身不会每次崩:入站带来的**新** `context_token` 给服务端提供了足够上下文,
|
|
83
|
+
* 请求照样成立。等票变旧、服务端进入 `prepare`(准备会话上下文)阶段时,
|
|
84
|
+
* **没有 `channel_version` 可依据** ⇒ 只能回 `ret=-2: prepare failed`。
|
|
85
|
+
* ⇒ 那个报错读作「准备阶段缺参数」,**不是**「会话过期」(后者是 `-14`)。
|
|
86
|
+
*
|
|
87
|
+
* ⚠️ 判据边界:本字段**不修复**「票真的过期」那种失败(那是 `-14`),
|
|
88
|
+
* 它修复的是**票在半旧窗口内、本该能发却被拒**的那一档。
|
|
89
|
+
*/
|
|
90
|
+
function buildBaseInfo() {
|
|
91
|
+
return { channel_version: CHANNEL_VERSION };
|
|
92
|
+
}
|
|
68
93
|
function buildHeaders(token) {
|
|
69
94
|
return {
|
|
70
95
|
"Content-Type": "application/json",
|
|
@@ -95,6 +120,30 @@ async function apiGet(params) {
|
|
|
95
120
|
throw err;
|
|
96
121
|
}
|
|
97
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* 给 POST body 统一附加 `base_info`(**对齐官方做法**)。
|
|
125
|
+
*
|
|
126
|
+
* 官方的九处业务 POST 各自写 `{ ...body, base_info: buildBaseInfo() }`;
|
|
127
|
+
* 我们把它收进 `apiPost` 一处 —— 语义完全相同(每个 POST 都带上),
|
|
128
|
+
* 但**新增调用点不可能漏**(这正是我们当初漏掉它的形态:靠每处手写就会漏)。
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ 只在 body 是 JSON 对象时附加;解析失败就原样发(不为了加字段把请求搞坏)。
|
|
131
|
+
* ⚠️ 已经带 `base_info` 的 body **不覆盖**(以调用方给的为准)。
|
|
132
|
+
*/
|
|
133
|
+
function withBaseInfo(body) {
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(body);
|
|
136
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return body;
|
|
137
|
+
const obj = parsed;
|
|
138
|
+
if (obj.base_info !== void 0) return body;
|
|
139
|
+
return JSON.stringify({
|
|
140
|
+
...obj,
|
|
141
|
+
base_info: buildBaseInfo()
|
|
142
|
+
});
|
|
143
|
+
} catch {
|
|
144
|
+
return body;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
98
147
|
async function apiPost(params) {
|
|
99
148
|
const base = ensureTrailingSlash(params.baseUrl);
|
|
100
149
|
const url = new URL(params.endpoint, base);
|
|
@@ -104,7 +153,7 @@ async function apiPost(params) {
|
|
|
104
153
|
const res = await currentFetch(url.toString(), {
|
|
105
154
|
method: "POST",
|
|
106
155
|
headers: buildHeaders(params.token),
|
|
107
|
-
body: params.body,
|
|
156
|
+
body: withBaseInfo(params.body),
|
|
108
157
|
signal: controller.signal
|
|
109
158
|
});
|
|
110
159
|
clearTimeout(timer);
|
|
@@ -214,7 +263,8 @@ async function sendTextMessage(params) {
|
|
|
214
263
|
type: MessageItemType.TEXT,
|
|
215
264
|
text_item: { text: markdownToPlainText(params.text) }
|
|
216
265
|
}],
|
|
217
|
-
...params.contextToken ? { context_token: params.contextToken } : {}
|
|
266
|
+
...params.contextToken ? { context_token: params.contextToken } : {},
|
|
267
|
+
...params.runId ? { run_id: params.runId } : {}
|
|
218
268
|
} }),
|
|
219
269
|
token: params.token,
|
|
220
270
|
timeoutMs: params.timeoutMs ?? 15e3
|
package/lib/im/ilink.d.ts
CHANGED
|
@@ -16,6 +16,30 @@
|
|
|
16
16
|
*/
|
|
17
17
|
/** 版本号 → ClientVersion 整数:(major<<16)|(minor<<8)|patch */
|
|
18
18
|
export declare function buildClientVersion(version: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* `base_info` —— **每一个业务 POST 都必须带**(2026-10-22 补齐,完全照官方实现)。
|
|
21
|
+
*
|
|
22
|
+
* ## 🔴 我们漏了这个字段,漏了很久
|
|
23
|
+
*
|
|
24
|
+
* 腾讯官方 `@tencent-weixin/openclaw-weixin` 的 `src/api/api.ts` 里,**九处**业务 POST
|
|
25
|
+
* 全部无条件附加它(`body: JSON.stringify({ ...body, base_info: buildBaseInfo() })`),
|
|
26
|
+
* 而 `buildBaseInfo()` 恒为 `{ channel_version: pkg.version }`。
|
|
27
|
+
*
|
|
28
|
+
* 我们这一侧长期只发 `{ msg: {...} }` —— **`base_info` 完全没有**。
|
|
29
|
+
*
|
|
30
|
+
* ## 症状为什么是「平时能发,隔久了发不出」
|
|
31
|
+
*
|
|
32
|
+
* 缺字段本身不会每次崩:入站带来的**新** `context_token` 给服务端提供了足够上下文,
|
|
33
|
+
* 请求照样成立。等票变旧、服务端进入 `prepare`(准备会话上下文)阶段时,
|
|
34
|
+
* **没有 `channel_version` 可依据** ⇒ 只能回 `ret=-2: prepare failed`。
|
|
35
|
+
* ⇒ 那个报错读作「准备阶段缺参数」,**不是**「会话过期」(后者是 `-14`)。
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ 判据边界:本字段**不修复**「票真的过期」那种失败(那是 `-14`),
|
|
38
|
+
* 它修复的是**票在半旧窗口内、本该能发却被拒**的那一档。
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildBaseInfo(): {
|
|
41
|
+
channel_version: string;
|
|
42
|
+
};
|
|
19
43
|
/** fetch 注入点(测试替换;生产 = 全局 fetch) */
|
|
20
44
|
type FetchLike = typeof fetch;
|
|
21
45
|
/** 测试注入:替换全局 fetch 实现(生产零调用) */
|
|
@@ -126,6 +150,8 @@ export declare function sendTextMessage(params: {
|
|
|
126
150
|
text: string;
|
|
127
151
|
contextToken?: string;
|
|
128
152
|
clientId?: string;
|
|
153
|
+
/** 本轮次的运行 id(官方每条出站都带;同一次回复的多条消息共享它) */
|
|
154
|
+
runId?: string;
|
|
129
155
|
timeoutMs?: number;
|
|
130
156
|
}): Promise<void>;
|
|
131
157
|
/**
|
package/lib/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
|
|
2
|
-
import { c as hostInjected, d as hostSessions, f as hostSettings, h as hostWebServer, i as cccRootForExec, l as hostLlm, m as hostWeb, n as agentCwdFor, o as listCccs, p as hostSubagents, r as cccRootForCwd, s as hostAgents, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-
|
|
2
|
+
import { c as hostInjected, d as hostSessions, f as hostSettings, h as hostWebServer, i as cccRootForExec, l as hostLlm, m as hostWeb, n as agentCwdFor, o as listCccs, p as hostSubagents, r as cccRootForCwd, s as hostAgents, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-CR1IWLDJ.js";
|
|
3
3
|
import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as readCccName$1, f as readCheapWorkerConfig, h as readSafeModeHiddenTools, i as findGitRoot, l as pathInside, m as readExclusiveTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-CyYJL4F2.js";
|
|
4
|
-
import { A as resolveSessionByTitle, B as readTrajectoryFile, C as listActiveTrajectories, D as parseTrajectoryCode, E as parseSessionContextFromEvents, F as summarize, H as localFileStamp, I as trajectoryDescOf, L as useSession, M as sessionsRoot, N as setActiveSessionInfo, P as showSession, R as TRAJECTORY_FILENAME, T as namingTitleFor, U as localHuman, V as isoLocal, W as localIdStamp, _ as clearActiveSessionInfo, b as findSession, c as replacesDefaultIdentity, g as archiveSession, h as UID_FILENAME, i as listTrajectoryConstraints, j as sessionEvents, k as readTrajectoryUid, l as trajectoryConstraintForSession, n as findDirNameByCode, o as readTrajectoryConstraintAt, p as COGNITIVE_TRAJECTORY_ACTIONS, r as isTrajectoryConstrained, s as readTrajectoryConstraintByCode, t as TRAJECTORY_CONSTRAINT_FILENAME, v as createSession, w as listSessions, x as getActiveSessionInfo, y as extractSessionMdPathFromText, z as TRAJECTORY_LEGACY_FILENAMES } from "./profile-
|
|
4
|
+
import { A as resolveSessionByTitle, B as readTrajectoryFile, C as listActiveTrajectories, D as parseTrajectoryCode, E as parseSessionContextFromEvents, F as summarize, H as localFileStamp, I as trajectoryDescOf, L as useSession, M as sessionsRoot, N as setActiveSessionInfo, P as showSession, R as TRAJECTORY_FILENAME, T as namingTitleFor, U as localHuman, V as isoLocal, W as localIdStamp, _ as clearActiveSessionInfo, b as findSession, c as replacesDefaultIdentity, g as archiveSession, h as UID_FILENAME, i as listTrajectoryConstraints, j as sessionEvents, k as readTrajectoryUid, l as trajectoryConstraintForSession, n as findDirNameByCode, o as readTrajectoryConstraintAt, p as COGNITIVE_TRAJECTORY_ACTIONS, r as isTrajectoryConstrained, s as readTrajectoryConstraintByCode, t as TRAJECTORY_CONSTRAINT_FILENAME, v as createSession, w as listSessions, x as getActiveSessionInfo, y as extractSessionMdPathFromText, z as TRAJECTORY_LEGACY_FILENAMES } from "./profile-CQWuuopQ.js";
|
|
5
5
|
import { n as checkLocalstoreGitCompliance, o as readStore, s as runLocalStore, t as LOCALSTORE_SCOPES } from "./service-DDmHSglV.js";
|
|
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-
|
|
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 createTrajectoryAgent, o as trajectoryConstraintMsmGate, p as requireWhitelistedModel, r as ensureWorkspacePromptSection, s as trajectoryConstraintParticipates, t as askTrajectory, u as workspaceCognitiveTrajectoryLine, v as applyWirePatch, w as FACE_PORTS, x as readAdvancedSettings, y as migrateLegacyLocalstore } from "./session-
|
|
8
|
-
import { n as
|
|
9
|
-
import { n as readSimpleSettings, r as registerSettingsSection } from "./settings-
|
|
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-HLXLQuvR.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 createTrajectoryAgent, o as trajectoryConstraintMsmGate, p as requireWhitelistedModel, r as ensureWorkspacePromptSection, s as trajectoryConstraintParticipates, t as askTrajectory, u as workspaceCognitiveTrajectoryLine, v as applyWirePatch, w as FACE_PORTS, x as readAdvancedSettings, y as migrateLegacyLocalstore } from "./session-CksIqzii.js";
|
|
8
|
+
import { i as sessionsRootDir, n as findSessionLog, r as hasSessionLogById } from "./cleanup-DxX78__Z.js";
|
|
9
|
+
import { n as readSimpleSettings, r as registerSettingsSection } from "./settings-D14eA2MR.js";
|
|
10
10
|
import { a as readFeishuCredential, c as readWeixinSettings, d as weixinInboundDir, f as weixinSessionIdForTrajectory, i as matchWeixinRoute, n as extractWeixinText, o as readImSettings, r as hasVoiceItem, s as readWeixinCredential, t as extractWeixinMedia, u as sanitizeFileName$1 } from "./route-3OzOAjec.js";
|
|
11
11
|
import { REGISTERED_TOOLS } from "./invariant.js";
|
|
12
|
-
import { c as sendTextMessage, i as getUpdates, l as sendTyping, n as downloadMedia, o as markdownToPlainText, r as getConfig, s as sendFileMessage, t as TypingStatus, u as sniffImageExt } from "./ilink-
|
|
12
|
+
import { c as sendTextMessage, i as getUpdates, l as sendTyping, n as downloadMedia, o as markdownToPlainText, r as getConfig, s as sendFileMessage, t as TypingStatus, u as sniffImageExt } from "./ilink-CsZ-f64q.js";
|
|
13
13
|
import { a as registerImAccountImpl, i as normalizeAccountChannel, n as getImAccountImpl, r as imAccountChannels } from "./accounts-BHQbakUh.js";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import z from "@deepseek-ai/schemastery";
|
|
16
16
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
17
17
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
18
|
-
import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { appendFileSync, chmodSync, closeSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
20
20
|
import { homedir, hostname, platform } from "node:os";
|
|
21
21
|
import { promisify } from "node:util";
|
|
@@ -24,8 +24,8 @@ import { fileURLToPath } from "node:url";
|
|
|
24
24
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
25
25
|
import { createHash, randomBytes } from "node:crypto";
|
|
26
26
|
import { Resvg, initWasm } from "@resvg/resvg-wasm";
|
|
27
|
+
import * as nodeZlib from "node:zlib";
|
|
27
28
|
import { createServer, request } from "node:http";
|
|
28
|
-
import * as zlib from "node:zlib";
|
|
29
29
|
import { deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
30
30
|
import { lookup } from "node:dns/promises";
|
|
31
31
|
import { isIP } from "node:net";
|
|
@@ -10190,6 +10190,322 @@ function listResident(root) {
|
|
|
10190
10190
|
return lines.join("\n");
|
|
10191
10191
|
}
|
|
10192
10192
|
//#endregion
|
|
10193
|
+
//#region src/trajectory/live.ts
|
|
10194
|
+
/**
|
|
10195
|
+
* trajectory/live.ts — 进程内 live 会话取数 + 精简诊断
|
|
10196
|
+
*
|
|
10197
|
+
* ── 为什么单独成模块(R↓,2026-09-15 S142「ACC 侧 autopilot 退场」S1 搬迁)──
|
|
10198
|
+
* 这两个函数原先住在 `autopilot-trajectory.ts`,但它们的**主语不是 autopilot**:
|
|
10199
|
+
* `listLiveSessions` 服务 `acc-diag` ① 段的"**哪个会话属于哪个 CCC**"(会话维度,
|
|
10200
|
+
* 不是 CCC 维度),而"**独立进程看不到运行时**"正是 `acc-diag` 存在的理由。
|
|
10201
|
+
* autopilot 退场后它们必须活下去 ⇒ 独立成模块,不再挂着已死机制的名字。
|
|
10202
|
+
*
|
|
10203
|
+
* ── 迁移边界(不搬什么,以及为什么)──────────────────────────────────────
|
|
10204
|
+
* 原 `diagLive` 还含两段,**均不搬**(其主语是 autopilot,随机制退场):
|
|
10205
|
+
* · `autopilotCccs`(逐 CCC 目标命中 / agent 定位诊断)—— 依赖 `readAutopilotSettings` /
|
|
10206
|
+
* `getAutopilotStatus` / `resolveTargetAgent`
|
|
10207
|
+
* · `panelResolved`(无参面板请求落到哪个 CCC)—— 面板区块随机制退场
|
|
10208
|
+
* 留下的 = **进程 cwd/CCC + live 会话清单**(`acc-diag` ① 段仍需要)。
|
|
10209
|
+
*
|
|
10210
|
+
* 判据纪律:本模块**不得**反向依赖 autopilot 判据层(那会让退场半途而废)。
|
|
10211
|
+
*
|
|
10212
|
+
* ── 🔴 2026-09-25 I2 收敛(约束文档 §2.7 违反清单,L1 违反之一)────────────────
|
|
10213
|
+
* **本模块的 `ctx` 参数类型从 `Context` 改为 `unknown`** —— 消除本文件唯一的 `W2` 接触
|
|
10214
|
+
* (直接 import 宿主 `cordis` 的类型)。
|
|
10215
|
+
*
|
|
10216
|
+
* **为什么这是"真解耦"而不是"把类型藏起来"(判据):** `ctx` 在本模块**从不被解引用** ——
|
|
10217
|
+
* 它只被**原样透传**给 `hostSessions(ctx)`,而后者(`host/access.ts`,**L0**)的参数
|
|
10218
|
+
* 本就声明为 `ctx: unknown` ⇒ `Context` 这个 import **在语义上是多余的**,
|
|
10219
|
+
* 它没有参与任何类型检查,只贡献了一处 L1→宿主 的接触点。
|
|
10220
|
+
* 🔵 **证据**:grep 本文件 `ctx` 全部 5 处命中 —— 3 处在参数/调用位、2 处在注释,
|
|
10221
|
+
* **零处**属性访问(`ctx.xxx`)。
|
|
10222
|
+
*/
|
|
10223
|
+
/**
|
|
10224
|
+
* 从 dsh 会话 log 读取标题(latest-wins `session/title` 事件)——
|
|
10225
|
+
* **标题不在 `sessions.list()` 条目上**(wire/对象均无 title 字段;F3 命名经
|
|
10226
|
+
* `sessionTitle.rename` 写进 session log),必须从 events 提取(rebuild 同款读取模式)。
|
|
10227
|
+
*/
|
|
10228
|
+
function readSessionTitle(session) {
|
|
10229
|
+
try {
|
|
10230
|
+
const events = sessionEvents(session);
|
|
10231
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
10232
|
+
const e = events[i];
|
|
10233
|
+
if (e?.type === "session/title" && typeof e.data?.title === "string" && e.data.title.trim() !== "") return e.data.title.trim();
|
|
10234
|
+
}
|
|
10235
|
+
} catch {}
|
|
10236
|
+
return null;
|
|
10237
|
+
}
|
|
10238
|
+
/**
|
|
10239
|
+
* 逐条 live 会话(`cccRoot` = 该会话 cwd 的 CCC 归属,无 → null)。
|
|
10240
|
+
*
|
|
10241
|
+
* CCC 枚举("本机有哪些 CCC")**不在这里**,归 `ccc-roots.listCccs`(并集语义);
|
|
10242
|
+
* 本函数保留的是它**独有**的那部分:会话维度 + cwd 归属 + 标题 + id。
|
|
10243
|
+
*
|
|
10244
|
+
* @param ctx 插件上下文(**只透传**给 L0 取数口,本模块不解引用 ⇒ 类型为 `unknown`)
|
|
10245
|
+
* @returns 逐条 live 会话
|
|
10246
|
+
*/
|
|
10247
|
+
function listLiveSessions(ctx) {
|
|
10248
|
+
const out = [];
|
|
10249
|
+
try {
|
|
10250
|
+
const sessions = hostSessions(ctx);
|
|
10251
|
+
for (const s of sessions?.list?.() ?? []) {
|
|
10252
|
+
const cwd = s?.header?.cwd ?? null;
|
|
10253
|
+
out.push({
|
|
10254
|
+
id: s?.id ?? "",
|
|
10255
|
+
cwd,
|
|
10256
|
+
cccRoot: cwd ? cccRootForCwd(cwd) : null,
|
|
10257
|
+
title: readSessionTitle(s)
|
|
10258
|
+
});
|
|
10259
|
+
}
|
|
10260
|
+
} catch {}
|
|
10261
|
+
return out;
|
|
10262
|
+
}
|
|
10263
|
+
function diagLive(ctx) {
|
|
10264
|
+
const processCwd = process.cwd();
|
|
10265
|
+
return {
|
|
10266
|
+
processCwd,
|
|
10267
|
+
processCcc: cccRootForCwd(processCwd),
|
|
10268
|
+
liveSessions: listLiveSessions(ctx)
|
|
10269
|
+
};
|
|
10270
|
+
}
|
|
10271
|
+
//#endregion
|
|
10272
|
+
//#region src/trajectory/interrupted.ts
|
|
10273
|
+
/**
|
|
10274
|
+
* 中断轮次发现(`interrupted` 会话扫描)
|
|
10275
|
+
*
|
|
10276
|
+
* ## 这个模块解决什么
|
|
10277
|
+
*
|
|
10278
|
+
* DSH 的会话日志里,**进程在某一轮完成前退出**(被 kill/崩溃/关机超时)时,
|
|
10279
|
+
* 宿主重放时会为该轮补上收尾事件:
|
|
10280
|
+
*
|
|
10281
|
+
* ```jsonl
|
|
10282
|
+
* turn/end {"turn":66,"reason":{"kind":"interrupted"}}
|
|
10283
|
+
* step/end {"turn":66,"step":85}
|
|
10284
|
+
* session/end-seed {}
|
|
10285
|
+
* ```
|
|
10286
|
+
*
|
|
10287
|
+
* 宿主**只补收尾,不续跑** —— 那一轮的工作就此停在那里。本模块负责把这些会话找出来,
|
|
10288
|
+
* 交给上层(认知轨迹)决定要不要续。
|
|
10289
|
+
*
|
|
10290
|
+
* ## 判据(三条同时成立,缺一不可)
|
|
10291
|
+
*
|
|
10292
|
+
* 1. 日志**最后一条事件是 `session/end-seed`** —— 这是"上一进程已结束、本进程重放"的标记;
|
|
10293
|
+
* 2. 该标记**之前最近的 `turn/end`** 的 `reason.kind === 'interrupted'`;
|
|
10294
|
+
* 3. `session/end-seed` 之后**没有新事件**(由判据 1 保证)。
|
|
10295
|
+
*
|
|
10296
|
+
* ⚠️ **为什么不能只看"存在过 interrupted"**:一个长会话历史上可能中断几十次
|
|
10297
|
+
* (本容器实测单会话 33 次)。只看"曾经中断"会把早已恢复的会话全部当成待续 ⇒ 灾难。
|
|
10298
|
+
*
|
|
10299
|
+
* ## 为什么不整文件解压
|
|
10300
|
+
*
|
|
10301
|
+
* DSH 日志是 **zstd 多帧追加**(实测单会话 24705 帧 / 47 MB)。原生
|
|
10302
|
+
* `zlib.zstdDecompressSync` 只解**第一帧**,对多帧直接报 `Unknown frame descriptor`。
|
|
10303
|
+
* ⇒ 采用**尾部窗口 + 倒序逐帧解压**:只读文件末尾 N 字节,从后往前找帧魔数,
|
|
10304
|
+
* 解到凑够所需事件数即停。实测 8h 窗口 6 个会话 **20 ms**。
|
|
10305
|
+
*
|
|
10306
|
+
* ## 世代识别复用 `cleanup.ts`
|
|
10307
|
+
*
|
|
10308
|
+
* 日志文件名有多个世代(`session.jsonl` / `session.v3.jsonl.zstd` / `session.v4.jsonl.zstd`…),
|
|
10309
|
+
* 宿主按世代写且**迁移保留源文件**。⇒ 本模块**不硬编码文件名**,走
|
|
10310
|
+
* {@link findSessionLog}(与清理/体积统计同一判据),避免"新世代对本模块不可见"。
|
|
10311
|
+
*
|
|
10312
|
+
* ## 边界
|
|
10313
|
+
*
|
|
10314
|
+
* - **只读**:不改任何会话日志。
|
|
10315
|
+
* - **不判周期**:何时扫、扫完做什么归调用方(CCC 的认知轨迹)。本模块只回答
|
|
10316
|
+
* 「此刻哪些会话以 interrupted 结尾」。
|
|
10317
|
+
*/
|
|
10318
|
+
/**
|
|
10319
|
+
* zstd 解压(**运行时有、类型声明没有**)。
|
|
10320
|
+
*
|
|
10321
|
+
* 🔴 为什么不能用 `import { zstdDecompressSync } from 'node:zlib'`:
|
|
10322
|
+
* 本仓 `@types/node@^20` 的声明里**没有** zstd 系列(Node 22.15+ 才进声明)。
|
|
10323
|
+
* 而运行时本机 v22.22.1 **确实有**(实测 `typeof require('zlib').zstdDecompressSync === 'function'`)。
|
|
10324
|
+
* ⇒ 用命名空间导入 + 局部断言,并**在运行时校验**;缺失时走明确降级(返回空,不静默瞎猜)。
|
|
10325
|
+
*/
|
|
10326
|
+
const zstdDecompressSync = nodeZlib.zstdDecompressSync;
|
|
10327
|
+
/** zstd 帧魔数(小端 `28 B5 2F FD`) */
|
|
10328
|
+
const ZSTD_MAGIC = Buffer.from([
|
|
10329
|
+
40,
|
|
10330
|
+
181,
|
|
10331
|
+
47,
|
|
10332
|
+
253
|
|
10333
|
+
]);
|
|
10334
|
+
/** 默认回看窗口:8 小时(owner 2026-10-22 定) */
|
|
10335
|
+
const DEFAULT_LOOKBACK_MS = 288e5;
|
|
10336
|
+
/** 每次从文件尾部读取的字节数(够覆盖数百个末尾事件) */
|
|
10337
|
+
const TAIL_WINDOW_BYTES = 1 << 20;
|
|
10338
|
+
/** 需要凑够的末尾事件数(够跨过 turn/end + step/end + end-seed 即可,留余量) */
|
|
10339
|
+
const TAIL_EVENT_TARGET = 60;
|
|
10340
|
+
/**
|
|
10341
|
+
* 从文件尾部倒序解压,凑够 `want` 个事件。
|
|
10342
|
+
*
|
|
10343
|
+
* 多帧 zstd 必须**按帧边界**切:从后往前找魔数,每帧用下一帧起始位置当结束边界。
|
|
10344
|
+
* 任何一帧解压失败就停下(返回已拿到的)—— 再往前更可能不完整。
|
|
10345
|
+
*/
|
|
10346
|
+
function readTailEvents(logPath, want) {
|
|
10347
|
+
if (zstdDecompressSync === void 0) return [];
|
|
10348
|
+
const st = statSync(logPath);
|
|
10349
|
+
const windowBytes = Math.min(TAIL_WINDOW_BYTES, st.size);
|
|
10350
|
+
if (windowBytes < 8) return [];
|
|
10351
|
+
const buf = Buffer.alloc(windowBytes);
|
|
10352
|
+
const fd = openSync(logPath, "r");
|
|
10353
|
+
try {
|
|
10354
|
+
readSync(fd, buf, 0, windowBytes, st.size - windowBytes);
|
|
10355
|
+
} finally {
|
|
10356
|
+
closeSync(fd);
|
|
10357
|
+
}
|
|
10358
|
+
const offsets = [];
|
|
10359
|
+
for (let i = 0; i <= windowBytes - ZSTD_MAGIC.length; i++) if (buf[i] === ZSTD_MAGIC[0] && buf[i + 1] === ZSTD_MAGIC[1] && buf[i + 2] === ZSTD_MAGIC[2] && buf[i + 3] === ZSTD_MAGIC[3]) offsets.push(i);
|
|
10360
|
+
if (offsets.length === 0) return [];
|
|
10361
|
+
const events = [];
|
|
10362
|
+
for (let k = offsets.length - 1; k >= 0 && events.length < want; k--) {
|
|
10363
|
+
const start = offsets[k];
|
|
10364
|
+
const end = k + 1 < offsets.length ? offsets[k + 1] : windowBytes;
|
|
10365
|
+
let text;
|
|
10366
|
+
try {
|
|
10367
|
+
text = zstdDecompressSync(buf.subarray(start, end)).toString("utf8");
|
|
10368
|
+
} catch {
|
|
10369
|
+
break;
|
|
10370
|
+
}
|
|
10371
|
+
const batch = [];
|
|
10372
|
+
for (const line of text.split("\n")) {
|
|
10373
|
+
if (!line.trim()) continue;
|
|
10374
|
+
try {
|
|
10375
|
+
batch.push(JSON.parse(line));
|
|
10376
|
+
} catch {}
|
|
10377
|
+
}
|
|
10378
|
+
events.unshift(...batch);
|
|
10379
|
+
}
|
|
10380
|
+
return events.slice(-want);
|
|
10381
|
+
}
|
|
10382
|
+
/**
|
|
10383
|
+
* 判定一组尾部事件是否「以 interrupted 结尾」。
|
|
10384
|
+
* 不命中 → null。
|
|
10385
|
+
*/
|
|
10386
|
+
function detectInterruptedEnd(events) {
|
|
10387
|
+
if (events.length === 0) return null;
|
|
10388
|
+
if (events[events.length - 1]?.type !== "session/end-seed") return null;
|
|
10389
|
+
let turnEnd;
|
|
10390
|
+
for (let i = events.length - 2; i >= 0; i--) if (events[i]?.type === "turn/end") {
|
|
10391
|
+
turnEnd = events[i];
|
|
10392
|
+
break;
|
|
10393
|
+
}
|
|
10394
|
+
if (!turnEnd || turnEnd.data?.reason?.kind !== "interrupted") return null;
|
|
10395
|
+
const turn = typeof turnEnd.data?.turn === "number" ? turnEnd.data.turn : null;
|
|
10396
|
+
return {
|
|
10397
|
+
turn,
|
|
10398
|
+
pendingTools: findPendingTools(events, turn)
|
|
10399
|
+
};
|
|
10400
|
+
}
|
|
10401
|
+
/**
|
|
10402
|
+
* 找出末轮里发起了但没有结果的工具调用名。
|
|
10403
|
+
*
|
|
10404
|
+
* ⚠️ **尽力而为**,非权威:尾部窗口可能截断更早的调用。上层应把它当
|
|
10405
|
+
* "**可能**未完成的副作用"清单,而不是"确定未完成"。
|
|
10406
|
+
*/
|
|
10407
|
+
function findPendingTools(events, turn) {
|
|
10408
|
+
if (turn === null) return [];
|
|
10409
|
+
const called = /* @__PURE__ */ new Map();
|
|
10410
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
10411
|
+
for (const e of events) {
|
|
10412
|
+
const data = e.data;
|
|
10413
|
+
if (!data || data.turn !== turn) continue;
|
|
10414
|
+
if (e.type === "tool/call" && typeof data.callId === "string" && typeof data.name === "string") called.set(data.callId, data.name);
|
|
10415
|
+
else if (e.type === "tool/result" && typeof data.callId === "string") resolved.add(data.callId);
|
|
10416
|
+
}
|
|
10417
|
+
return [...called].filter(([callId]) => !resolved.has(callId)).map(([, name]) => name);
|
|
10418
|
+
}
|
|
10419
|
+
/**
|
|
10420
|
+
* 扫描一个 **project 目录**(形如 `<sessions-root>/--home-yh-home-home-serenity--/`)。
|
|
10421
|
+
*
|
|
10422
|
+
* @param projectDir DSH 项目目录(其下每个子目录是一个会话)
|
|
10423
|
+
* @param options.lookbackMs 回看窗口(缺省 8h)
|
|
10424
|
+
* @param options.now 注入"当前时刻"(测试用)
|
|
10425
|
+
* @param options.liveIds 需要跳过的会话 id(正在跑的会话不该被判为待续)
|
|
10426
|
+
*/
|
|
10427
|
+
function scanInterruptedSessions(projectDir, options = {}) {
|
|
10428
|
+
const t0 = Date.now();
|
|
10429
|
+
const lookbackMs = options.lookbackMs ?? 288e5;
|
|
10430
|
+
const cutoff = (options.now ?? Date.now()) - lookbackMs;
|
|
10431
|
+
const liveIds = options.liveIds ?? /* @__PURE__ */ new Set();
|
|
10432
|
+
const result = {
|
|
10433
|
+
sessions: [],
|
|
10434
|
+
scanned: 0,
|
|
10435
|
+
skipped: 0,
|
|
10436
|
+
errors: [],
|
|
10437
|
+
elapsedMs: 0
|
|
10438
|
+
};
|
|
10439
|
+
if (!existsSync(projectDir)) {
|
|
10440
|
+
result.elapsedMs = Date.now() - t0;
|
|
10441
|
+
return result;
|
|
10442
|
+
}
|
|
10443
|
+
let entries;
|
|
10444
|
+
try {
|
|
10445
|
+
entries = readdirSync(projectDir);
|
|
10446
|
+
} catch (err) {
|
|
10447
|
+
result.errors.push({
|
|
10448
|
+
sessionId: "*",
|
|
10449
|
+
message: err instanceof Error ? err.message : String(err)
|
|
10450
|
+
});
|
|
10451
|
+
result.elapsedMs = Date.now() - t0;
|
|
10452
|
+
return result;
|
|
10453
|
+
}
|
|
10454
|
+
for (const sessionId of entries) {
|
|
10455
|
+
if (liveIds.has(sessionId)) continue;
|
|
10456
|
+
const dirPath = join(projectDir, sessionId);
|
|
10457
|
+
const logPath = findSessionLog(dirPath);
|
|
10458
|
+
if (logPath === null) continue;
|
|
10459
|
+
let mtimeMs;
|
|
10460
|
+
try {
|
|
10461
|
+
mtimeMs = statSync(logPath).mtimeMs;
|
|
10462
|
+
} catch (err) {
|
|
10463
|
+
result.errors.push({
|
|
10464
|
+
sessionId,
|
|
10465
|
+
message: err instanceof Error ? err.message : String(err)
|
|
10466
|
+
});
|
|
10467
|
+
continue;
|
|
10468
|
+
}
|
|
10469
|
+
if (mtimeMs < cutoff) continue;
|
|
10470
|
+
result.scanned++;
|
|
10471
|
+
try {
|
|
10472
|
+
const events = readTailEvents(logPath, TAIL_EVENT_TARGET);
|
|
10473
|
+
const hit = detectInterruptedEnd(events);
|
|
10474
|
+
if (!hit) {
|
|
10475
|
+
result.skipped++;
|
|
10476
|
+
continue;
|
|
10477
|
+
}
|
|
10478
|
+
const tailTime = events[events.length - 1]?.time;
|
|
10479
|
+
result.sessions.push({
|
|
10480
|
+
sessionId,
|
|
10481
|
+
logPath,
|
|
10482
|
+
turn: hit.turn,
|
|
10483
|
+
lastEventAt: typeof tailTime === "number" ? tailTime : null,
|
|
10484
|
+
mtimeMs,
|
|
10485
|
+
pendingTools: hit.pendingTools
|
|
10486
|
+
});
|
|
10487
|
+
} catch (err) {
|
|
10488
|
+
result.errors.push({
|
|
10489
|
+
sessionId,
|
|
10490
|
+
message: err instanceof Error ? err.message : String(err)
|
|
10491
|
+
});
|
|
10492
|
+
}
|
|
10493
|
+
}
|
|
10494
|
+
result.sessions.sort((a, b) => (b.lastEventAt ?? b.mtimeMs) - (a.lastEventAt ?? a.mtimeMs));
|
|
10495
|
+
result.elapsedMs = Date.now() - t0;
|
|
10496
|
+
return result;
|
|
10497
|
+
}
|
|
10498
|
+
/**
|
|
10499
|
+
* 由 CCC 根推导它的 DSH 项目目录名(`--home-yh-home-home-serenity--`)。
|
|
10500
|
+
*
|
|
10501
|
+
* 规则取自宿主:把绝对路径的 `/` 换成 `-`,并在前后各加 `--`。
|
|
10502
|
+
* ⚠️ 这是**推导**,不是真相源 —— 实际目录名以磁盘为准;调用方应在不存在时
|
|
10503
|
+
* 回退到"扫整个 sessions root"或直接报未找到。
|
|
10504
|
+
*/
|
|
10505
|
+
function projectDirNameForCwd(cwd) {
|
|
10506
|
+
return `--${cwd.replace(/\/+$/, "").split("/").filter(Boolean).join("-")}--`;
|
|
10507
|
+
}
|
|
10508
|
+
//#endregion
|
|
10193
10509
|
//#region src/tools/cognitive-trajectory.ts
|
|
10194
10510
|
/**
|
|
10195
10511
|
* trajectory.ts — `cognitive_trajectory` 真实 DSH 工具定义(defineTool)
|
|
@@ -10230,6 +10546,57 @@ function listResident(root) {
|
|
|
10230
10546
|
* 保留 osp 对齐语义的部分:create 的 `--desc`/`--issue` 二选一、use 激活即重命名 dsh 会话标题(F3)。
|
|
10231
10547
|
* 活跃会话跟踪:内存 Map(按 dsh 会话 id 隔离)+ 从 events 恢复(S134)。
|
|
10232
10548
|
*/
|
|
10549
|
+
/**
|
|
10550
|
+
* 当前宿主里**正在跑**的 dsh 会话 id(用于把 live 会话排除出"待续"名单)。
|
|
10551
|
+
*
|
|
10552
|
+
* 🔴 **为什么必须排除**:一个正在跑的会话,其日志末条**不是** `session/end-seed`
|
|
10553
|
+
* (它还在写),所以按判据本来也不会命中;但排除是**双保险** ——
|
|
10554
|
+
* 万一某轮刚被中断、宿主还没来得及继续,把 live 会话当"待续"会导致**重复投递**。
|
|
10555
|
+
* 拿不到 live 列表时退化为空集(不阻断扫描,只少一层保护)。
|
|
10556
|
+
*/
|
|
10557
|
+
function collectLiveSessionIds() {
|
|
10558
|
+
const out = /* @__PURE__ */ new Set();
|
|
10559
|
+
try {
|
|
10560
|
+
for (const s of listLiveSessions(void 0)) if (typeof s.id === "string" && s.id) out.add(s.id);
|
|
10561
|
+
} catch {}
|
|
10562
|
+
return out;
|
|
10563
|
+
}
|
|
10564
|
+
/**
|
|
10565
|
+
* 渲染 `interrupted` 扫描结果(人读)。
|
|
10566
|
+
*
|
|
10567
|
+
* 显示口径:**先给"有没有",再给每一条的定位信息,最后给边界说明**。
|
|
10568
|
+
* ⚠️ 明确写出"这些会话是否续跑由调用方决定" —— 否则读者会以为本动作已经处理了。
|
|
10569
|
+
*/
|
|
10570
|
+
function renderInterruptedScan(input) {
|
|
10571
|
+
const { projectDir, lookbackHours, scan } = input;
|
|
10572
|
+
const lines = [];
|
|
10573
|
+
lines.push(`═══ 以 interrupted 结尾的 dsh 会话(回看 ${lookbackHours}h)═══`);
|
|
10574
|
+
lines.push(` 扫描域: ${projectDir}`);
|
|
10575
|
+
lines.push(` 检查 ${scan.scanned} 个 · 命中 ${scan.sessions.length} · 跳过 ${scan.skipped} · 耗时 ${scan.elapsedMs}ms${scan.errors.length > 0 ? ` · 读取失败 ${scan.errors.length}` : ""}`);
|
|
10576
|
+
if (scan.sessions.length === 0) {
|
|
10577
|
+
lines.push("");
|
|
10578
|
+
lines.push("✓ 没有会话以 interrupted 结尾 —— 无需处理。");
|
|
10579
|
+
return lines.join("\n");
|
|
10580
|
+
}
|
|
10581
|
+
lines.push("");
|
|
10582
|
+
for (const s of scan.sessions) {
|
|
10583
|
+
const at = s.lastEventAt !== null ? new Date(s.lastEventAt).toISOString() : "(时间未知)";
|
|
10584
|
+
lines.push(` · ${s.sessionId}`);
|
|
10585
|
+
lines.push(` 轮次 turn=${s.turn ?? "?"} · 末事件 ${at}`);
|
|
10586
|
+
if (s.pendingTools.length > 0) lines.push(` ⚠️ 有调用无结果的工具(副作用未知): ${s.pendingTools.join(", ")}`);
|
|
10587
|
+
}
|
|
10588
|
+
lines.push("");
|
|
10589
|
+
lines.push("判据: 日志末条 = session/end-seed,且其前最近的 turn/end 的 reason.kind = interrupted。");
|
|
10590
|
+
lines.push("🔴 本动作**只读** —— 是否续跑、续哪条、几点续,由调用方(你的认知轨迹)决定。");
|
|
10591
|
+
lines.push("⚠️ 未完成的工具调用「副作用未知」≠「没执行」:它们可能已写文件 / 已发消息。");
|
|
10592
|
+
lines.push(" 续跑前应先核对副作用,而不是把那一轮整段重放。");
|
|
10593
|
+
if (scan.errors.length > 0) {
|
|
10594
|
+
lines.push("");
|
|
10595
|
+
lines.push("读取失败(诊断):");
|
|
10596
|
+
for (const e of scan.errors.slice(0, 10)) lines.push(` · ${e.sessionId}: ${e.message}`);
|
|
10597
|
+
}
|
|
10598
|
+
return lines.join("\n");
|
|
10599
|
+
}
|
|
10233
10600
|
/** 当前 dsh 会话 id(use/close 按会话隔离的 scope) */
|
|
10234
10601
|
function agentScope$1(exec) {
|
|
10235
10602
|
return exec.agent?.session?.id ?? "default";
|
|
@@ -10349,6 +10716,7 @@ function createCognitiveTrajectoryTool(ctx) {
|
|
|
10349
10716
|
"list (inventory + stats) / show (read one SESSION.md) / create (make a NEW trajectory) / use (bind THIS conversation) / rebuild (clear-and-rebuild THIS conversation in place) / archive (move a trajectory out of the active list; one-way, never overwrites).",
|
|
10350
10717
|
"(B) DELIVERY — sends ONE message to ANOTHER trajectory; your own binding is unchanged: send-now (right now) / send-later (one future instant).",
|
|
10351
10718
|
"(C) REFERENCE — cro-guide: how to give a trajectory its own program that decides when it should be woken.",
|
|
10719
|
+
"(D) INSPECTION — interrupted: list dsh sessions whose LAST turn ended with reason \"interrupted\" (the process died before that turn completed). PURE READ: no periodic scheduling, no auto-continue, no log mutation — deciding IF/WHEN/WHICH to resume belongs to the calling trajectory (this is the \"entry point only\" contract). The criterion is EXACTLY \"ends with interrupted\", NOT \"was ever interrupted\": one long session may have been interrupted dozens of times historically, and treating those as pending would resurrect sessions that already recovered. Each hit reports the turn number and the tool names that had a call but no result (best-effort — those side effects are UNKNOWN, so re-running them blindly is dangerous).",
|
|
10352
10720
|
"HOW TO CHOOSE (a rule, not a guess):",
|
|
10353
10721
|
"not yet bound + starting new work ⇒ create (with no prior binding it activates immediately, so the new trajectory has a carrier at once).",
|
|
10354
10722
|
"already bound + recording a SEPARATE future line ⇒ create (it does NOT steal your binding; activate the new one later by send-now).",
|
|
@@ -10388,7 +10756,8 @@ function createCognitiveTrajectoryTool(ctx) {
|
|
|
10388
10756
|
"send-now (deliver RIGHT NOW; the target needs no prior setup — if it has no live session, one is created and bound automatically) /",
|
|
10389
10757
|
"send-later (one future instant + one message; fire-and-forget, and it does NOT auto-create a target).",
|
|
10390
10758
|
"REFERENCE: cro-guide (no parameters: prints the CRO programming guide, including a ready-to-use sample snapshot) /",
|
|
10391
|
-
"resident (READ-ONLY: the roster of trajectories that declared themselves RESIDENT — \"I am here, this is what I am good at, come bother me\". It lists each code with its own `serves` sentence, plus any code that wrote `resident` without `serves` (half a declaration is worse than none: people ask, then find they asked the wrong one). ⚠️ Residency is NOT a resource state — it does not mean \"never sleeps\" or \"never destroyed\"; a trajectory permanently exists either way. It governs BEING FOUND, not being kept alive, and it never touches the scheduler)."
|
|
10759
|
+
"resident (READ-ONLY: the roster of trajectories that declared themselves RESIDENT — \"I am here, this is what I am good at, come bother me\". It lists each code with its own `serves` sentence, plus any code that wrote `resident` without `serves` (half a declaration is worse than none: people ask, then find they asked the wrong one). ⚠️ Residency is NOT a resource state — it does not mean \"never sleeps\" or \"never destroyed\"; a trajectory permanently exists either way. It governs BEING FOUND, not being kept alive, and it never touches the scheduler).",
|
|
10760
|
+
"INSPECTION: interrupted (READ-ONLY: dsh sessions whose LAST turn ended with reason \"interrupted\" — the process died mid-turn. Accepts an optional lookbackHours (default 8). Deciding whether/when to resume is YOUR job — this action only answers \"which sessions are in that state right now\")."
|
|
10392
10761
|
].join(" ")
|
|
10393
10762
|
},
|
|
10394
10763
|
name: {
|
|
@@ -10442,6 +10811,10 @@ function createCognitiveTrajectoryTool(ctx) {
|
|
|
10442
10811
|
message: {
|
|
10443
10812
|
type: "string",
|
|
10444
10813
|
description: "send-later/send-now: the single message delivered to the target cognitive trajectory. send-now may create and bind a carrier ONLY when the target has never had one — acceptable only for a delivery the user actually wants made (user-intent-driven), never for speculatively starting work."
|
|
10814
|
+
},
|
|
10815
|
+
lookbackHours: {
|
|
10816
|
+
type: "number",
|
|
10817
|
+
description: "interrupted: how far back to look, in hours (default 8). Sessions whose current-generation log was last written earlier than this are not examined at all."
|
|
10445
10818
|
}
|
|
10446
10819
|
},
|
|
10447
10820
|
output: {
|
|
@@ -10636,6 +11009,34 @@ function createCognitiveTrajectoryTool(ctx) {
|
|
|
10636
11009
|
* 🔴 **不进系统提示词**(owner 裁决 Q2:说明类信息按需取,不占每轮 token)。
|
|
10637
11010
|
*/
|
|
10638
11011
|
return listResident(root);
|
|
11012
|
+
case "interrupted": {
|
|
11013
|
+
/**
|
|
11014
|
+
* 以 `interrupted` 结尾的 dsh 会话(owner 2026-10-22 令:**只出入口**)。
|
|
11015
|
+
*
|
|
11016
|
+
* 🔴 **边界(owner 原话:「我们 ACC 不关心周期」)**:本动作**只回答**"此刻哪些会话
|
|
11017
|
+
* 的最后一轮以 interrupted 结束",**不判周期、不自动续跑、不改日志**。
|
|
11018
|
+
* 何时扫、续谁、怎么续 = 调用方(CCC 的认知轨迹)的事。
|
|
11019
|
+
*
|
|
11020
|
+
* 🔴 **为什么不是"曾经中断过"**:长会话历史上可中断几十次(实测单会话 33 次);
|
|
11021
|
+
* 按"曾经"判会把早已恢复的会话全部当成待续 ⇒ 灾难。判据在
|
|
11022
|
+
* `trajectory/interrupted.ts` 的 `detectInterruptedEnd`:**末条 = end-seed
|
|
11023
|
+
* 且其前最近的 turn/end 是 interrupted**。
|
|
11024
|
+
*
|
|
11025
|
+
* 🔴 **正在跑的会话要排除**:live 会话不该被判成"待续"(它正在跑,不是中断)。
|
|
11026
|
+
*/
|
|
11027
|
+
const lookbackHours = typeof args.lookbackHours === "number" && args.lookbackHours > 0 ? args.lookbackHours : DEFAULT_LOOKBACK_MS / 36e5;
|
|
11028
|
+
const sessionsRootPath = sessionsRootDir();
|
|
11029
|
+
const cwd = exec.agent?.session?.header?.cwd ?? root;
|
|
11030
|
+
const projectDir = join(sessionsRootPath, projectDirNameForCwd(cwd));
|
|
11031
|
+
return renderInterruptedScan({
|
|
11032
|
+
projectDir,
|
|
11033
|
+
lookbackHours,
|
|
11034
|
+
scan: scanInterruptedSessions(projectDir, {
|
|
11035
|
+
lookbackMs: lookbackHours * 36e5,
|
|
11036
|
+
liveIds: collectLiveSessionIds()
|
|
11037
|
+
})
|
|
11038
|
+
});
|
|
11039
|
+
}
|
|
10639
11040
|
default: throw new Error(`Unknown action: ${args.action}`);
|
|
10640
11041
|
}
|
|
10641
11042
|
}
|
|
@@ -14344,7 +14745,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
14344
14745
|
}
|
|
14345
14746
|
const root = chk.root;
|
|
14346
14747
|
if (body.action === "login-start") {
|
|
14347
|
-
const { fetchQRCode } = await import("./ilink-
|
|
14748
|
+
const { fetchQRCode } = await import("./ilink-CsZ-f64q.js").then((n) => n.a);
|
|
14348
14749
|
const { randomUUID } = await import("node:crypto");
|
|
14349
14750
|
for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
|
|
14350
14751
|
const { readWeixinSettings } = await import("./route-3OzOAjec.js").then((n) => n.l);
|
|
@@ -14395,7 +14796,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
14395
14796
|
});
|
|
14396
14797
|
}
|
|
14397
14798
|
const { saveWeixinRoutes } = await import("./route-3OzOAjec.js").then((n) => n.l);
|
|
14398
|
-
const { findDirNameByCode } = await import("./profile-
|
|
14799
|
+
const { findDirNameByCode } = await import("./profile-CQWuuopQ.js").then((n) => n.a);
|
|
14399
14800
|
for (const r of routes) if (findDirNameByCode(root, r.trajectory) === null) {
|
|
14400
14801
|
sendJson$2(res, 400, { error: `unknown trajectory: ${r.trajectory} (no such trajectory code in ${root})` });
|
|
14401
14802
|
return;
|
|
@@ -14478,7 +14879,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
14478
14879
|
sendJson$2(res, 200, { status: "expired" });
|
|
14479
14880
|
return;
|
|
14480
14881
|
}
|
|
14481
|
-
const { pollQRStatus } = await import("./ilink-
|
|
14882
|
+
const { pollQRStatus } = await import("./ilink-CsZ-f64q.js").then((n) => n.a);
|
|
14482
14883
|
const { readWeixinSettings } = await import("./route-3OzOAjec.js").then((n) => n.l);
|
|
14483
14884
|
const settings = readWeixinSettings(login.root);
|
|
14484
14885
|
const status = await pollQRStatus({
|
|
@@ -14543,7 +14944,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
14543
14944
|
const id = s.id ?? s.header?.id;
|
|
14544
14945
|
if (id) liveIds.add(id);
|
|
14545
14946
|
}
|
|
14546
|
-
const { sessionsRootDir, collectEligibleSessions, performCleanup, cutoffDaysAgo } = await import("./cleanup-
|
|
14947
|
+
const { sessionsRootDir, collectEligibleSessions, performCleanup, cutoffDaysAgo } = await import("./cleanup-DxX78__Z.js").then((n) => n.t);
|
|
14547
14948
|
const root = sessionsRootDir();
|
|
14548
14949
|
if (req.method === "GET") {
|
|
14549
14950
|
const { candidates } = performCleanup(root, cutoffDaysAgo(days), liveIds, { dryRun: true });
|
|
@@ -15908,9 +16309,9 @@ function transformHtmlForProxy(raw, upstreamHeaders) {
|
|
|
15908
16309
|
const enc = String(upstreamHeaders["content-encoding"] ?? "").toLowerCase().trim();
|
|
15909
16310
|
const outHeaders = { ...upstreamHeaders };
|
|
15910
16311
|
if (enc === "gzip" || enc === "x-gzip" || enc === "deflate" || enc === "br") try {
|
|
15911
|
-
if (enc === "br") body =
|
|
15912
|
-
else if (enc === "deflate") body =
|
|
15913
|
-
else body =
|
|
16312
|
+
if (enc === "br") body = nodeZlib.brotliDecompressSync(body);
|
|
16313
|
+
else if (enc === "deflate") body = nodeZlib.inflateSync(body);
|
|
16314
|
+
else body = nodeZlib.gunzipSync(body);
|
|
15914
16315
|
delete outHeaders["content-encoding"];
|
|
15915
16316
|
} catch {
|
|
15916
16317
|
return null;
|
|
@@ -19438,7 +19839,7 @@ const MEDIA_MAX_BYTES = 20971520;
|
|
|
19438
19839
|
*/
|
|
19439
19840
|
async function resolveCccRootByAlias(ctx, alias) {
|
|
19440
19841
|
try {
|
|
19441
|
-
const { listCccs } = await import("./ccc-roots-
|
|
19842
|
+
const { listCccs } = await import("./ccc-roots-CR1IWLDJ.js").then((n) => n.a);
|
|
19442
19843
|
const entries = await listCccs(ctx);
|
|
19443
19844
|
const byName = entries.find((e) => e.name === alias);
|
|
19444
19845
|
if (byName) return byName.root;
|
|
@@ -19506,7 +19907,7 @@ async function resolveInboundTarget(ctx, input) {
|
|
|
19506
19907
|
});
|
|
19507
19908
|
return null;
|
|
19508
19909
|
}
|
|
19509
|
-
const { findDirNameByCode } = await import("./profile-
|
|
19910
|
+
const { findDirNameByCode } = await import("./profile-CQWuuopQ.js").then((n) => n.a);
|
|
19510
19911
|
if (findDirNameByCode(root, target.role) === null) {
|
|
19511
19912
|
const detail = `目标 CCC "${target.ccc}" 内没有编码为 "${target.role}" 的认知轨迹`;
|
|
19512
19913
|
console.error(`[serenity-hooks] ✗ human-channel: ${detail}(account=${input.account} user=${input.user})—— 该消息**未投递**,请改订阅表里的目标编码`);
|
|
@@ -20190,7 +20591,8 @@ async function handleIncoming(ctx, root, accountId, cred, msg, preset) {
|
|
|
20190
20591
|
token: cred.token,
|
|
20191
20592
|
toUserId,
|
|
20192
20593
|
text,
|
|
20193
|
-
contextToken: replyRef
|
|
20594
|
+
contextToken: replyRef,
|
|
20595
|
+
runId: `dsp-run-${randomBytes(6).toString("hex")}`
|
|
20194
20596
|
});
|
|
20195
20597
|
},
|
|
20196
20598
|
typingStart: ({ toUserId, replyRef }) => sendTypingStart(cred, accountId, toUserId, replyRef),
|
|
@@ -20459,7 +20861,7 @@ function syncHumanChannel(ctx) {
|
|
|
20459
20861
|
(async () => {
|
|
20460
20862
|
try {
|
|
20461
20863
|
stopMachineLoops();
|
|
20462
|
-
const { listCccs } = await import("./ccc-roots-
|
|
20864
|
+
const { listCccs } = await import("./ccc-roots-CR1IWLDJ.js").then((n) => n.a);
|
|
20463
20865
|
for (const entry of await listCccs(ctx)) syncCccBridge(ctx, entry.root);
|
|
20464
20866
|
} catch {}
|
|
20465
20867
|
})();
|
|
@@ -20533,7 +20935,8 @@ async function sendProactiveText(input) {
|
|
|
20533
20935
|
token: cred.token,
|
|
20534
20936
|
toUserId,
|
|
20535
20937
|
text: input.text,
|
|
20536
|
-
...contextToken === void 0 ? {} : { contextToken }
|
|
20938
|
+
...contextToken === void 0 ? {} : { contextToken },
|
|
20939
|
+
runId: `dsp-run-${randomBytes(6).toString("hex")}`
|
|
20537
20940
|
});
|
|
20538
20941
|
} catch (err) {
|
|
20539
20942
|
/**
|
|
@@ -20751,7 +21154,7 @@ function matchCcc(input, candidates) {
|
|
|
20751
21154
|
}
|
|
20752
21155
|
/** 组装候选列表(`ccc-roots.listCccs` 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
|
|
20753
21156
|
async function collectCandidates(ctx) {
|
|
20754
|
-
const { listCccs } = await import("./ccc-roots-
|
|
21157
|
+
const { listCccs } = await import("./ccc-roots-CR1IWLDJ.js").then((n) => n.a);
|
|
20755
21158
|
const entries = await listCccs(ctx, { defaultRoot: process.cwd() });
|
|
20756
21159
|
const seen = /* @__PURE__ */ new Set();
|
|
20757
21160
|
const out = [];
|
|
@@ -22429,85 +22832,6 @@ function createImBridgeTool() {
|
|
|
22429
22832
|
});
|
|
22430
22833
|
}
|
|
22431
22834
|
//#endregion
|
|
22432
|
-
//#region src/trajectory/live.ts
|
|
22433
|
-
/**
|
|
22434
|
-
* trajectory/live.ts — 进程内 live 会话取数 + 精简诊断
|
|
22435
|
-
*
|
|
22436
|
-
* ── 为什么单独成模块(R↓,2026-09-15 S142「ACC 侧 autopilot 退场」S1 搬迁)──
|
|
22437
|
-
* 这两个函数原先住在 `autopilot-trajectory.ts`,但它们的**主语不是 autopilot**:
|
|
22438
|
-
* `listLiveSessions` 服务 `acc-diag` ① 段的"**哪个会话属于哪个 CCC**"(会话维度,
|
|
22439
|
-
* 不是 CCC 维度),而"**独立进程看不到运行时**"正是 `acc-diag` 存在的理由。
|
|
22440
|
-
* autopilot 退场后它们必须活下去 ⇒ 独立成模块,不再挂着已死机制的名字。
|
|
22441
|
-
*
|
|
22442
|
-
* ── 迁移边界(不搬什么,以及为什么)──────────────────────────────────────
|
|
22443
|
-
* 原 `diagLive` 还含两段,**均不搬**(其主语是 autopilot,随机制退场):
|
|
22444
|
-
* · `autopilotCccs`(逐 CCC 目标命中 / agent 定位诊断)—— 依赖 `readAutopilotSettings` /
|
|
22445
|
-
* `getAutopilotStatus` / `resolveTargetAgent`
|
|
22446
|
-
* · `panelResolved`(无参面板请求落到哪个 CCC)—— 面板区块随机制退场
|
|
22447
|
-
* 留下的 = **进程 cwd/CCC + live 会话清单**(`acc-diag` ① 段仍需要)。
|
|
22448
|
-
*
|
|
22449
|
-
* 判据纪律:本模块**不得**反向依赖 autopilot 判据层(那会让退场半途而废)。
|
|
22450
|
-
*
|
|
22451
|
-
* ── 🔴 2026-09-25 I2 收敛(约束文档 §2.7 违反清单,L1 违反之一)────────────────
|
|
22452
|
-
* **本模块的 `ctx` 参数类型从 `Context` 改为 `unknown`** —— 消除本文件唯一的 `W2` 接触
|
|
22453
|
-
* (直接 import 宿主 `cordis` 的类型)。
|
|
22454
|
-
*
|
|
22455
|
-
* **为什么这是"真解耦"而不是"把类型藏起来"(判据):** `ctx` 在本模块**从不被解引用** ——
|
|
22456
|
-
* 它只被**原样透传**给 `hostSessions(ctx)`,而后者(`host/access.ts`,**L0**)的参数
|
|
22457
|
-
* 本就声明为 `ctx: unknown` ⇒ `Context` 这个 import **在语义上是多余的**,
|
|
22458
|
-
* 它没有参与任何类型检查,只贡献了一处 L1→宿主 的接触点。
|
|
22459
|
-
* 🔵 **证据**:grep 本文件 `ctx` 全部 5 处命中 —— 3 处在参数/调用位、2 处在注释,
|
|
22460
|
-
* **零处**属性访问(`ctx.xxx`)。
|
|
22461
|
-
*/
|
|
22462
|
-
/**
|
|
22463
|
-
* 从 dsh 会话 log 读取标题(latest-wins `session/title` 事件)——
|
|
22464
|
-
* **标题不在 `sessions.list()` 条目上**(wire/对象均无 title 字段;F3 命名经
|
|
22465
|
-
* `sessionTitle.rename` 写进 session log),必须从 events 提取(rebuild 同款读取模式)。
|
|
22466
|
-
*/
|
|
22467
|
-
function readSessionTitle(session) {
|
|
22468
|
-
try {
|
|
22469
|
-
const events = sessionEvents(session);
|
|
22470
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
22471
|
-
const e = events[i];
|
|
22472
|
-
if (e?.type === "session/title" && typeof e.data?.title === "string" && e.data.title.trim() !== "") return e.data.title.trim();
|
|
22473
|
-
}
|
|
22474
|
-
} catch {}
|
|
22475
|
-
return null;
|
|
22476
|
-
}
|
|
22477
|
-
/**
|
|
22478
|
-
* 逐条 live 会话(`cccRoot` = 该会话 cwd 的 CCC 归属,无 → null)。
|
|
22479
|
-
*
|
|
22480
|
-
* CCC 枚举("本机有哪些 CCC")**不在这里**,归 `ccc-roots.listCccs`(并集语义);
|
|
22481
|
-
* 本函数保留的是它**独有**的那部分:会话维度 + cwd 归属 + 标题 + id。
|
|
22482
|
-
*
|
|
22483
|
-
* @param ctx 插件上下文(**只透传**给 L0 取数口,本模块不解引用 ⇒ 类型为 `unknown`)
|
|
22484
|
-
* @returns 逐条 live 会话
|
|
22485
|
-
*/
|
|
22486
|
-
function listLiveSessions(ctx) {
|
|
22487
|
-
const out = [];
|
|
22488
|
-
try {
|
|
22489
|
-
const sessions = hostSessions(ctx);
|
|
22490
|
-
for (const s of sessions?.list?.() ?? []) {
|
|
22491
|
-
const cwd = s?.header?.cwd ?? null;
|
|
22492
|
-
out.push({
|
|
22493
|
-
id: s?.id ?? "",
|
|
22494
|
-
cwd,
|
|
22495
|
-
cccRoot: cwd ? cccRootForCwd(cwd) : null,
|
|
22496
|
-
title: readSessionTitle(s)
|
|
22497
|
-
});
|
|
22498
|
-
}
|
|
22499
|
-
} catch {}
|
|
22500
|
-
return out;
|
|
22501
|
-
}
|
|
22502
|
-
function diagLive(ctx) {
|
|
22503
|
-
const processCwd = process.cwd();
|
|
22504
|
-
return {
|
|
22505
|
-
processCwd,
|
|
22506
|
-
processCcc: cccRootForCwd(processCwd),
|
|
22507
|
-
liveSessions: listLiveSessions(ctx)
|
|
22508
|
-
};
|
|
22509
|
-
}
|
|
22510
|
-
//#endregion
|
|
22511
22835
|
//#region src/surface/observation.ts
|
|
22512
22836
|
/**
|
|
22513
22837
|
* surface/observation.ts — 容器状态模型的**取数层唯一出口**(C5「观察面归一」,S142 2026-09-15)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as cccRootForCwd } from "./ccc-roots-
|
|
1
|
+
import { r as cccRootForCwd } from "./ccc-roots-CR1IWLDJ.js";
|
|
2
2
|
import { _ as resolveInside, g as readUtf8 } from "./ccc-CyYJL4F2.js";
|
|
3
|
-
import { D as parseTrajectoryCode, M as sessionsRoot, S as getLastActiveSessionInfo, j as sessionEvents, x as getActiveSessionInfo } from "./profile-
|
|
3
|
+
import { D as parseTrajectoryCode, M as sessionsRoot, S as getLastActiveSessionInfo, j as sessionEvents, x as getActiveSessionInfo } from "./profile-CQWuuopQ.js";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { n as agentCwdFor, r as cccRootForCwd, s as hostAgents } from "./ccc-roots-
|
|
1
|
+
import { n as agentCwdFor, r as cccRootForCwd, s as hostAgents } from "./ccc-roots-CR1IWLDJ.js";
|
|
2
2
|
import { g as readUtf8, o as isSafeModeOn, p as readCognitiveTrajectorySkills, u as readBlacklist } from "./ccc-CyYJL4F2.js";
|
|
3
|
-
import { M as sessionsRoot, O as readActiveSessionMd, V as isoLocal, d as trajectoryDirNameForSession, f as unregisterTrajectorySession$1, l as trajectoryConstraintForSession, m as DEFAULT_SESSION_SCOPE, o as readTrajectoryConstraintAt, u as registerTrajectorySession$1 } from "./profile-
|
|
3
|
+
import { M as sessionsRoot, O as readActiveSessionMd, V as isoLocal, d as trajectoryDirNameForSession, f as unregisterTrajectorySession$1, l as trajectoryConstraintForSession, m as DEFAULT_SESSION_SCOPE, o as readTrajectoryConstraintAt, u as registerTrajectorySession$1 } from "./profile-CQWuuopQ.js";
|
|
4
4
|
import { a as readGitTrack, i as localstorePath } from "./service-DDmHSglV.js";
|
|
5
|
-
import { d as constraintCognitiveTrajectory, m as profileMsmWhitelist, n as PLUGIN_SOURCE, o as readLastBound, p as createProfilePromptReader, u as buildTrajectoryBasePrompt, v as ACC_VERSION } from "./message-source-
|
|
5
|
+
import { d as constraintCognitiveTrajectory, m as profileMsmWhitelist, n as PLUGIN_SOURCE, o as readLastBound, p as createProfilePromptReader, u as buildTrajectoryBasePrompt, v as ACC_VERSION } from "./message-source-HLXLQuvR.js";
|
|
6
6
|
import { basename, dirname, join, resolve } from "node:path";
|
|
7
7
|
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
package/lib/trajectory/core.d.ts
CHANGED
|
@@ -62,8 +62,15 @@
|
|
|
62
62
|
* 2026-10-20(D173 兑现,owner 令):「常驻」= 轨迹的**自我声明**,落地为 **`resident`** 只读动作。
|
|
63
63
|
* 判据:它管的是**被找到**,不是**不被销毁** ⇒ 纯读、不碰生命周期、不进系统提示词。
|
|
64
64
|
* ⚠️ 这也修正了 v1.77.1 的一处说明面失真(手册曾描述一个**不存在**的动作,D179(a))。
|
|
65
|
+
* 2026-10-22(owner 令「在 trajectory 加上触发入口即可,剩下的 CCC 搞定」):新增
|
|
66
|
+
* **`interrupted`** —— 列出**以 `interrupted` 结尾**的 dsh 会话(进程在轮次完成前退出)。
|
|
67
|
+
* 判据(R↓,为何 ACC 只出**入口**不管周期):**周期性归属调用方的认知轨迹**,
|
|
68
|
+
* ACC 只回答"此刻哪些会话以 interrupted 结尾"这一个问题。⇒ 本动作**纯读**、
|
|
69
|
+
* **不判周期、不自动续跑、不改任何会话日志**(是否续、续谁、几点续 = CCC 的事)。
|
|
70
|
+
* ⚠️ 判据的锋利处:**只认"以 interrupted 结尾"**,不认"曾经中断过"—— 长会话历史上
|
|
71
|
+
* 可中断几十次(实测单会话 33 次),按"曾经"判会把早已恢复的会话全部当成待续。
|
|
65
72
|
*/
|
|
66
|
-
type CognitiveTrajectoryAction = 'list' | 'show' | 'create' | 'use' | 'rebuild' | 'archive' | 'uid' | 'send-now' | 'send-later' | 'cro-guide' | 'resident';
|
|
73
|
+
type CognitiveTrajectoryAction = 'list' | 'show' | 'create' | 'use' | 'rebuild' | 'archive' | 'uid' | 'send-now' | 'send-later' | 'cro-guide' | 'resident' | 'interrupted';
|
|
67
74
|
export declare const COGNITIVE_TRAJECTORY_ACTIONS: readonly CognitiveTrajectoryAction[];
|
|
68
75
|
/**
|
|
69
76
|
* 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中断轮次发现(`interrupted` 会话扫描)
|
|
3
|
+
*
|
|
4
|
+
* ## 这个模块解决什么
|
|
5
|
+
*
|
|
6
|
+
* DSH 的会话日志里,**进程在某一轮完成前退出**(被 kill/崩溃/关机超时)时,
|
|
7
|
+
* 宿主重放时会为该轮补上收尾事件:
|
|
8
|
+
*
|
|
9
|
+
* ```jsonl
|
|
10
|
+
* turn/end {"turn":66,"reason":{"kind":"interrupted"}}
|
|
11
|
+
* step/end {"turn":66,"step":85}
|
|
12
|
+
* session/end-seed {}
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* 宿主**只补收尾,不续跑** —— 那一轮的工作就此停在那里。本模块负责把这些会话找出来,
|
|
16
|
+
* 交给上层(认知轨迹)决定要不要续。
|
|
17
|
+
*
|
|
18
|
+
* ## 判据(三条同时成立,缺一不可)
|
|
19
|
+
*
|
|
20
|
+
* 1. 日志**最后一条事件是 `session/end-seed`** —— 这是"上一进程已结束、本进程重放"的标记;
|
|
21
|
+
* 2. 该标记**之前最近的 `turn/end`** 的 `reason.kind === 'interrupted'`;
|
|
22
|
+
* 3. `session/end-seed` 之后**没有新事件**(由判据 1 保证)。
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ **为什么不能只看"存在过 interrupted"**:一个长会话历史上可能中断几十次
|
|
25
|
+
* (本容器实测单会话 33 次)。只看"曾经中断"会把早已恢复的会话全部当成待续 ⇒ 灾难。
|
|
26
|
+
*
|
|
27
|
+
* ## 为什么不整文件解压
|
|
28
|
+
*
|
|
29
|
+
* DSH 日志是 **zstd 多帧追加**(实测单会话 24705 帧 / 47 MB)。原生
|
|
30
|
+
* `zlib.zstdDecompressSync` 只解**第一帧**,对多帧直接报 `Unknown frame descriptor`。
|
|
31
|
+
* ⇒ 采用**尾部窗口 + 倒序逐帧解压**:只读文件末尾 N 字节,从后往前找帧魔数,
|
|
32
|
+
* 解到凑够所需事件数即停。实测 8h 窗口 6 个会话 **20 ms**。
|
|
33
|
+
*
|
|
34
|
+
* ## 世代识别复用 `cleanup.ts`
|
|
35
|
+
*
|
|
36
|
+
* 日志文件名有多个世代(`session.jsonl` / `session.v3.jsonl.zstd` / `session.v4.jsonl.zstd`…),
|
|
37
|
+
* 宿主按世代写且**迁移保留源文件**。⇒ 本模块**不硬编码文件名**,走
|
|
38
|
+
* {@link findSessionLog}(与清理/体积统计同一判据),避免"新世代对本模块不可见"。
|
|
39
|
+
*
|
|
40
|
+
* ## 边界
|
|
41
|
+
*
|
|
42
|
+
* - **只读**:不改任何会话日志。
|
|
43
|
+
* - **不判周期**:何时扫、扫完做什么归调用方(CCC 的认知轨迹)。本模块只回答
|
|
44
|
+
* 「此刻哪些会话以 interrupted 结尾」。
|
|
45
|
+
*/
|
|
46
|
+
/** zstd 是否可用(不可用时扫描会返回"读不到事件",而不是假装成功) */
|
|
47
|
+
export declare const ZSTD_AVAILABLE: boolean;
|
|
48
|
+
/** 默认回看窗口:8 小时(owner 2026-10-22 定) */
|
|
49
|
+
export declare const DEFAULT_LOOKBACK_MS: number;
|
|
50
|
+
/** 日志里被我们识别的事件形状(只取用得到的字段) */
|
|
51
|
+
interface SessionEvent {
|
|
52
|
+
type?: string;
|
|
53
|
+
time?: number;
|
|
54
|
+
data?: {
|
|
55
|
+
turn?: number;
|
|
56
|
+
step?: number;
|
|
57
|
+
reason?: {
|
|
58
|
+
kind?: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** 一个"以 interrupted 结尾"的会话 */
|
|
63
|
+
export interface InterruptedSession {
|
|
64
|
+
/** 会话目录名(= dsh 会话 id) */
|
|
65
|
+
sessionId: string;
|
|
66
|
+
/** 日志绝对路径(当前世代) */
|
|
67
|
+
logPath: string;
|
|
68
|
+
/** 被中断的轮次号 */
|
|
69
|
+
turn: number | null;
|
|
70
|
+
/** 日志最后一条事件的时间(epoch ms,取不到则 null) */
|
|
71
|
+
lastEventAt: number | null;
|
|
72
|
+
/** 日志文件 mtime(epoch ms) */
|
|
73
|
+
mtimeMs: number;
|
|
74
|
+
/** 该轮里"有 call 无 result"的工具名(**尽力而为**,可能因尾部窗口截断而不全) */
|
|
75
|
+
pendingTools: string[];
|
|
76
|
+
}
|
|
77
|
+
/** 扫描结果 */
|
|
78
|
+
export interface ScanResult {
|
|
79
|
+
/** 以 interrupted 结尾的会话(最新在前) */
|
|
80
|
+
sessions: InterruptedSession[];
|
|
81
|
+
/** 窗口内实际检查的会话数 */
|
|
82
|
+
scanned: number;
|
|
83
|
+
/** 窗口内但未命中(含正常结束)的会话数 */
|
|
84
|
+
skipped: number;
|
|
85
|
+
/** 读取失败条目(诊断用,不阻断) */
|
|
86
|
+
errors: Array<{
|
|
87
|
+
sessionId: string;
|
|
88
|
+
message: string;
|
|
89
|
+
}>;
|
|
90
|
+
/** 扫描耗时(ms) */
|
|
91
|
+
elapsedMs: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 判定一组尾部事件是否「以 interrupted 结尾」。
|
|
95
|
+
* 不命中 → null。
|
|
96
|
+
*/
|
|
97
|
+
export declare function detectInterruptedEnd(events: SessionEvent[]): {
|
|
98
|
+
turn: number | null;
|
|
99
|
+
pendingTools: string[];
|
|
100
|
+
} | null;
|
|
101
|
+
/**
|
|
102
|
+
* 扫描一个 **project 目录**(形如 `<sessions-root>/--home-yh-home-home-serenity--/`)。
|
|
103
|
+
*
|
|
104
|
+
* @param projectDir DSH 项目目录(其下每个子目录是一个会话)
|
|
105
|
+
* @param options.lookbackMs 回看窗口(缺省 8h)
|
|
106
|
+
* @param options.now 注入"当前时刻"(测试用)
|
|
107
|
+
* @param options.liveIds 需要跳过的会话 id(正在跑的会话不该被判为待续)
|
|
108
|
+
*/
|
|
109
|
+
export declare function scanInterruptedSessions(projectDir: string, options?: {
|
|
110
|
+
lookbackMs?: number;
|
|
111
|
+
now?: number;
|
|
112
|
+
liveIds?: ReadonlySet<string>;
|
|
113
|
+
}): ScanResult;
|
|
114
|
+
/**
|
|
115
|
+
* 由 CCC 根推导它的 DSH 项目目录名(`--home-yh-home-home-serenity--`)。
|
|
116
|
+
*
|
|
117
|
+
* 规则取自宿主:把绝对路径的 `/` 换成 `-`,并在前后各加 `--`。
|
|
118
|
+
* ⚠️ 这是**推导**,不是真相源 —— 实际目录名以磁盘为准;调用方应在不存在时
|
|
119
|
+
* 回退到"扫整个 sessions root"或直接报未找到。
|
|
120
|
+
*/
|
|
121
|
+
export declare function projectDirNameForCwd(cwd: string): string;
|
|
122
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.87.
|
|
3
|
+
"version": "1.87.4",
|
|
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": {
|