@shgroup/dsh-serenity-hooks 1.27.2 → 1.27.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/lib/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { a as findSerenityRoot, c as matchBlacklist, d as readHandymanConfig, i as findGitRoot, l as pathInside, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as resolveInside, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-CfDrlfA7.js";
3
3
  import { a as resolveRoleSystemPrompt, i as readSkiffRoles, l as systemPromptSource, r as isSkiffSessionId, s as roleToolWhitelist } from "./skiff-role-Dw7UKCUm.js";
4
- import { C as splitModel, E as skiffRoleFor, S as requireWhitelistedModel, T as writeProgress, _ as buildRoundPrompt, a as startSkiffDebugServer, b as newStopToken, c as askSkiff, d as skiffMsmGate, f as skiffSessionInfo, g as HANDYMAN_GUIDE, h as unregisterSkiffSession, l as createSkiffAgent, m as skiffTrajectoryEnabled, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionSnapshot, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as getSkiffAgent, v as handymanProgressPaths, w as writeFailedStatus, x as readProgress, y as listActiveHandymen } from "./skiff-debug-vLsdk7wV.js";
5
- import { a as weixinSessionIdFor, c as checkLocalstoreGitCompliance, d as runLocalStore, i as readWeixinSettings, l as localstorePath, n as matchWeixinRoute, r as readWeixinCredential, s as LOCALSTORE_SCOPES, t as extractWeixinText, u as readGitTrack } from "./weixin-route-CvAWARN3.js";
6
- import { n as sendTextMessage, t as getUpdates } from "./weixin-api-b8HLmcPE.js";
4
+ import { C as splitModel, E as skiffRoleFor, S as requireWhitelistedModel, T as writeProgress, _ as buildRoundPrompt, a as startSkiffDebugServer, b as newStopToken, c as askSkiff, d as skiffMsmGate, f as skiffSessionInfo, g as HANDYMAN_GUIDE, h as unregisterSkiffSession, l as createSkiffAgent, m as skiffTrajectoryEnabled, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionSnapshot, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as getSkiffAgent, v as handymanProgressPaths, w as writeFailedStatus, x as readProgress, y as listActiveHandymen } from "./skiff-debug-BRlc17GU.js";
5
+ import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-EVu56Ytp.js";
6
+ import { a as sendTextMessage, i as getUpdates, n as downloadMedia, o as sendTyping, r as getConfig, s as sniffImageExt, t as TypingStatus } from "./weixin-api-DAugVJ-f.js";
7
7
  import z from "@deepseek-ai/schemastery";
8
8
  import { defineTool } from "@deepseek-ai/dsh-tools";
9
9
  import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
@@ -6136,36 +6136,46 @@ const skiffAdminTool = defineTool({
6136
6136
  }
6137
6137
  });
6138
6138
  //#endregion
6139
- //#region src/autotrajectory.ts
6140
- var autotrajectory_exports = /* @__PURE__ */ __exportAll({
6139
+ //#region src/autopilot-trajectory.ts
6140
+ var autopilot_trajectory_exports = /* @__PURE__ */ __exportAll({
6141
+ AUDIT_HISTORY_MAX: () => 50,
6141
6142
  AUTO_DIR_SUFFIX: () => AUTO_DIR_SUFFIX,
6143
+ BIAS_OUTPUT_MAX: () => BIAS_OUTPUT_MAX,
6144
+ BIAS_RUN_TIMEOUT_MS: () => BIAS_RUN_TIMEOUT_MS,
6142
6145
  DEFAULT_AVOID_HOURS: () => DEFAULT_AVOID_HOURS,
6143
6146
  DEFAULT_BIAS_PROVIDER: () => DEFAULT_BIAS_PROVIDER,
6147
+ DEFAULT_MAX_DAILY_WAKES: () => 24,
6148
+ LEGACY_BIAS_PROVIDER: () => LEGACY_BIAS_PROVIDER,
6144
6149
  MOTIVATION_MARKER: () => MOTIVATION_MARKER,
6145
6150
  TICK_MS: () => TICK_MS,
6146
6151
  beijingHour: () => beijingHour,
6147
6152
  buildWakeMessage: () => buildWakeMessage,
6153
+ collectAutopilotCccs: () => collectAutopilotCccs,
6154
+ dailyWakeCount: () => dailyWakeCount,
6148
6155
  diagLive: () => diagLive,
6149
6156
  fetchBiasContent: () => fetchBiasContent,
6150
- getAutoTrajectoryStatus: () => getAutoTrajectoryStatus,
6157
+ getAutopilotStatus: () => getAutopilotStatus,
6151
6158
  inAllowedWakeWindow: () => inAllowedWakeWindow,
6152
- isAutoTrajectorySession: () => isAutoTrajectorySession,
6159
+ isAutopilotSession: () => isAutopilotSession,
6153
6160
  listLiveSessions: () => listLiveSessions,
6154
- performAutoTrajectoryWake: () => performAutoTrajectoryWake,
6155
- readAutoTrajectorySettings: () => readAutoTrajectorySettings,
6161
+ performAutopilotWake: () => performAutopilotWake,
6162
+ readAutopilotSettings: () => readAutopilotSettings,
6156
6163
  readSelfGeneratedMotivation: () => readSelfGeneratedMotivation,
6157
- registerAutoTrajectory: () => registerAutoTrajectory,
6158
- resolveAutoTrajectoryCcc: () => resolveAutoTrajectoryCcc,
6164
+ recordWake: () => recordWake,
6165
+ registerAutopilot: () => registerAutopilot,
6159
6166
  resolveTargetMd: () => resolveTargetMd,
6160
- shouldWake: () => shouldWake
6167
+ shouldWake: () => shouldWake,
6168
+ wakeHistoryFor: () => wakeHistoryFor
6161
6169
  });
6162
6170
  const PLUGIN_SOURCE$3 = {
6163
6171
  kind: "plugin",
6164
6172
  plugin: "dsh-serenity-hooks"
6165
6173
  };
6166
- /** 偏见内容提供者脚本缺省名(CCC 根目录下;用户拍板:tool 直接运行,缺失报错要求实现) */
6167
- const DEFAULT_BIAS_PROVIDER = "autotrajectory-bias.ts";
6168
- /** 自主轨迹会话目录后缀标志(用户拍板:后缀) */
6174
+ /** 偏见内容提供者脚本缺省名(CCC 根目录下;正式版 autopilot-bias.ts) */
6175
+ const DEFAULT_BIAS_PROVIDER = "autopilot-bias.ts";
6176
+ /** 旧默认偏见脚本名(autotrajectory 时代;未显式配置时回退——pangu 等已配置 CCC 零迁移) */
6177
+ const LEGACY_BIAS_PROVIDER = "autotrajectory-bias.ts";
6178
+ /** 自主轨迹会话目录后缀标志(--auto 保留——简短且历史会话 S060--auto 已存在) */
6169
6179
  const AUTO_DIR_SUFFIX = "--auto";
6170
6180
  /** 调度 tick 周期(10min——验证形态"一个就够") */
6171
6181
  const TICK_MS = 6e5;
@@ -6176,8 +6186,14 @@ const DEFAULT_AVOID_HOURS = {
6176
6186
  start: 8,
6177
6187
  end: 18
6178
6188
  };
6179
- function readAutoTrajectorySettings(root) {
6180
- return loadSerenityConfig(root).autotrajectory ?? null;
6189
+ /** 偏见脚本输出上限(沙箱:截断防失控——正式版 v1.27.4) */
6190
+ const BIAS_OUTPUT_MAX = 8192;
6191
+ /** 偏见脚本执行超时(正式版沙箱;原 600s 过长阻塞 tick) */
6192
+ const BIAS_RUN_TIMEOUT_MS = 6e4;
6193
+ /** 读配置:新键 autopilotTrajectory 优先,旧键 autotrajectory 回退(正式化兼容) */
6194
+ function readAutopilotSettings(root) {
6195
+ const cfg = loadSerenityConfig(root);
6196
+ return cfg.autopilotTrajectory ?? cfg.autotrajectory ?? null;
6181
6197
  }
6182
6198
  /**
6183
6199
  * 北京时间(UTC+8)当前小时——不依赖服务器时区(服务器可能 UTC/本地任意)。
@@ -6185,6 +6201,11 @@ function readAutoTrajectorySettings(root) {
6185
6201
  function beijingHour(nowMs) {
6186
6202
  return Math.floor((nowMs + 288e5) % 864e5 / 36e5);
6187
6203
  }
6204
+ /** 当日已用唤起次数(按北京时间日;轮次预算 maxDailyWakes 判定) */
6205
+ function dailyWakeCount(history, nowMs) {
6206
+ const day = Math.floor((nowMs + 288e5) / 864e5);
6207
+ return history.filter((r) => Math.floor((r.time + 288e5) / 864e5) === day).length;
6208
+ }
6188
6209
  /**
6189
6210
  * 唤起窗口判定:北京时间 [avoidStart, avoidEnd) 内不唤起(缺省 8~18)。
6190
6211
  * 单段避开(start<=end):窗口 = [0,start) ∪ [end,24);跨零点避开(start>end):窗口 = [end,start)。
@@ -6197,7 +6218,7 @@ function inAllowedWakeWindow(nowMs, avoid) {
6197
6218
  return h >= end && h < start;
6198
6219
  }
6199
6220
  /** 标志位判定:SESSION.md 所在目录名以 --auto 结尾 → 自主轨迹形态 */
6200
- function isAutoTrajectorySession(mdPath) {
6221
+ function isAutopilotSession(mdPath) {
6201
6222
  return basename(dirname(mdPath)).endsWith(AUTO_DIR_SUFFIX);
6202
6223
  }
6203
6224
  /**
@@ -6213,12 +6234,14 @@ function resolveTargetMd(root, cfg) {
6213
6234
  return existsSync(md) ? md : null;
6214
6235
  }
6215
6236
  /**
6216
- * 唤起条件(纯逻辑,可测):enabled + 未在运行 + 目录标志 + mtime 超间隔 + 窗口允许。
6237
+ * 唤起条件(纯逻辑,可测):enabled + 未在运行 + 目录标志 + mtime 超间隔 + 窗口允许 + 预算未超。
6217
6238
  */
6218
- function shouldWake(settings, mdPath, nowMs, running) {
6239
+ function shouldWake(settings, mdPath, nowMs, running, history = []) {
6219
6240
  if (!settings?.enabled || running) return false;
6220
- if (!mdPath || !isAutoTrajectorySession(mdPath)) return false;
6241
+ if (!mdPath || !isAutopilotSession(mdPath)) return false;
6221
6242
  if (!inAllowedWakeWindow(nowMs, settings.avoidWakeHours)) return false;
6243
+ const maxDaily = Math.max(1, settings.maxDailyWakes ?? 24);
6244
+ if (dailyWakeCount(history, nowMs) >= maxDaily) return false;
6222
6245
  try {
6223
6246
  const mtime = statSync(mdPath).mtimeMs;
6224
6247
  const hours = Math.max(1, settings.intervalHours ?? 12);
@@ -6241,9 +6264,10 @@ function readSelfGeneratedMotivation(mdPath) {
6241
6264
  }
6242
6265
  }
6243
6266
  /**
6244
- * 偏见内容:直接运行 CCC 根目录下偏见提供者脚本(biasProvider,缺省 autotrajectory-bias.ts)→ stdout。
6267
+ * 偏见内容:直接运行 CCC 根目录下偏见提供者脚本(biasProvider,缺省 autopilot-bias.ts
6268
+ * 未显式配置且新默认缺失 → 回退旧默认 autotrajectory-bias.ts——pangu 兼容)→ stdout。
6245
6269
  * 脚本缺失 → 返回 { text: null, error: 提示实现 }(唤起侧报错要求实现,不静默跳过)。
6246
- * 路径逃逸校验(resolveInside);bun 优先,node 兜底;600s 超时。
6270
+ * 路径逃逸校验(resolveInside);bun 优先,node 兜底;**60s 超时 + 8KB 输出截断(沙箱)**。
6247
6271
  */
6248
6272
  async function fetchBiasContent(root, providerRel) {
6249
6273
  let scriptAbs;
@@ -6255,15 +6279,19 @@ async function fetchBiasContent(root, providerRel) {
6255
6279
  error: `biasProvider 路径逃逸(须在 CCC 根内): ${providerRel}`
6256
6280
  };
6257
6281
  }
6282
+ if (!existsSync(scriptAbs) && providerRel === "autopilot-bias.ts") {
6283
+ const legacy = resolveInside(root, LEGACY_BIAS_PROVIDER);
6284
+ if (existsSync(legacy)) scriptAbs = legacy;
6285
+ }
6258
6286
  if (!existsSync(scriptAbs)) return {
6259
6287
  text: null,
6260
- error: `请在 CCC 根目录实现偏见内容提供者脚本: ${providerRel}stdout 输出偏见内容一行;acc_msm exec autotrajectory-exp init 可生成模板)`
6288
+ error: `请在 CCC 根目录实现偏见内容提供者脚本: ${providerRel}(或旧默认 ${LEGACY_BIAS_PROVIDER};stdout 输出偏见内容一行;acc_msm exec autopilot-trajectory init 可生成模板)`
6261
6289
  };
6262
6290
  const runs = [["bun", [scriptAbs]], [process.execPath, [scriptAbs]]];
6263
6291
  for (const [cmd, args] of runs) try {
6264
6292
  const r = spawnSync(cmd, args, {
6265
6293
  encoding: "utf-8",
6266
- timeout: 6e5,
6294
+ timeout: BIAS_RUN_TIMEOUT_MS,
6267
6295
  stdio: [
6268
6296
  "ignore",
6269
6297
  "pipe",
@@ -6271,7 +6299,7 @@ async function fetchBiasContent(root, providerRel) {
6271
6299
  ]
6272
6300
  });
6273
6301
  if (r.status === 0) return {
6274
- text: (r.stdout ?? "").trim() || null,
6302
+ text: (r.stdout ?? "").trim().slice(0, 8192) || null,
6275
6303
  error: null
6276
6304
  };
6277
6305
  if (r.error?.code === "ENOENT") continue;
@@ -6294,24 +6322,43 @@ function buildWakeMessage(opts) {
6294
6322
  lines.push(`[轨迹焦点] ${opts.topPrompt}`);
6295
6323
  lines.push("");
6296
6324
  }
6297
- lines.push(`[自主轨迹唤起] — 距上次轨迹活动已满 ${opts.intervalHours} 小时,自动继续。`, "", `身份锚定:继续 ${opts.sessionName} 的 trajectory(SESSION.md: ${opts.mdPath})。`, "先验偏见:");
6325
+ lines.push(`[Autopilot Trajectory 唤起] — 距上次轨迹活动已满 ${opts.intervalHours} 小时,自动继续。`, "", `身份锚定:继续 ${opts.sessionName} 的 trajectory(SESSION.md: ${opts.mdPath})。`, "先验偏见:");
6298
6326
  if (opts.motivation) lines.push(` · 自生动机:${opts.motivation}`);
6299
6327
  if (opts.biasContent) lines.push(` · 偏见内容:${opts.biasContent}`);
6300
6328
  if (!opts.motivation && !opts.biasContent) lines.push(" · (无——本轮纯自主探索)");
6301
6329
  lines.push("", "任务:执行一轮自主认知(探索/反事实检验),把产出写入 SESSION.md", "「自主探索日志」段,并预写「下一轮动机」段。完成后自然结束。");
6302
6330
  return lines.join("\n");
6303
6331
  }
6304
- async function performAutoTrajectoryWake(ctx, root, settings, opts = {}) {
6332
+ /** 审计历史(root 最近唤醒记录;进程生命周期;上限 AUDIT_HISTORY_MAX/CCC) */
6333
+ const wakeHistory = /* @__PURE__ */ new Map();
6334
+ /** 追加审计记录(ring 截断) */
6335
+ function recordWake(root, rec) {
6336
+ const list = wakeHistory.get(root) ?? [];
6337
+ list.push(rec);
6338
+ if (list.length > 50) list.splice(0, list.length - 50);
6339
+ wakeHistory.set(root, list);
6340
+ }
6341
+ /** 读审计历史(root 的最近记录;无 → []) */
6342
+ function wakeHistoryFor(root) {
6343
+ return [...wakeHistory.get(root) ?? []];
6344
+ }
6345
+ /**
6346
+ * 执行一次唤起(时钟 tick 与面板「立即唤起」共用):
6347
+ * - force=false(时钟):完整校验——enabled / 目标命中 / --auto 标志 / 窗口 / 间隔 / 预算 / 偏见脚本
6348
+ * - force=true(手动调试,用户在场):跳过窗口/间隔/预算,仍校验 enabled / 目标命中 / --auto / 偏见脚本
6349
+ * 成功 → 注入前台会话(agent.steer,用户可见);返回结果供调用方(tick 打日志 / 面板显示 / 审计)。
6350
+ */
6351
+ async function performAutopilotWake(ctx, root, settings, opts = {}) {
6305
6352
  if (!settings?.enabled) return {
6306
6353
  ok: false,
6307
- detail: "autotrajectory 未启用(enabled=false)"
6354
+ detail: "Autopilot Trajectory 未启用(enabled=false)"
6308
6355
  };
6309
6356
  const mdPath = resolveTargetMd(root, settings);
6310
6357
  if (!mdPath) return {
6311
6358
  ok: false,
6312
6359
  detail: "目标会话未命中(session 未配置或 AGENT_SESSIONS 无匹配)"
6313
6360
  };
6314
- if (!isAutoTrajectorySession(mdPath)) return {
6361
+ if (!isAutopilotSession(mdPath)) return {
6315
6362
  ok: false,
6316
6363
  detail: "目标会话目录无 --auto 标志(AGENT_SESSIONS/<date>--<desc>--auto/)"
6317
6364
  };
@@ -6340,7 +6387,7 @@ async function performAutoTrajectoryWake(ctx, root, settings, opts = {}) {
6340
6387
  ok: false,
6341
6388
  detail: `目标会话 agent 不可得——${diagnoseTargetUnavailable(ctx, mdPath) ?? "目标会话未打开或命名未生效"}`
6342
6389
  };
6343
- const provider = settings.biasProvider?.trim() || "autotrajectory-bias.ts";
6390
+ const provider = settings.biasProvider?.trim() || "autopilot-bias.ts";
6344
6391
  const biasRes = await fetchBiasContent(root, provider);
6345
6392
  if (biasRes.error) return {
6346
6393
  ok: false,
@@ -6368,54 +6415,62 @@ async function performAutoTrajectoryWake(ctx, root, settings, opts = {}) {
6368
6415
  };
6369
6416
  }
6370
6417
  /**
6371
- * 装配(index.ts apply 调用)。时钟唤起(v1.26.14 修复——用户"手动唤起正常,时钟唤起不工作"):
6418
+ * 装配(index.ts apply 调用)。时钟唤起(v1.26.14 修复 + v1.27.4 多 CCC 独立):
6372
6419
  *
6373
- * 根因:旧实现启动时一次性 `resolveAutoTrajectoryRoot(ctx)`——web 进程启动时 live 会话
6374
- * 往往为空(用户尚未打开实验 CCC 会话)→ root=null settings=null **定时器根本不启动**;
6375
- * 手动唤起走 HTTP 端点(点击时重新解析 root)→ 正常。
6420
+ * v1.26.14 根因:旧实现启动时一次性解析 root——web 进程启动时 live 会话往往为空 → 定时器不启动。
6421
+ * 修复:① 每次 tick 动态解析 root+settings 监听 session/created 启动定时器 ③ 优先实验 CCC。
6376
6422
  *
6377
- * 修复:
6378
- * **动态解析**:每次 tick 重新解析 root + settings(不绑定启动时值)——live 会话变化即跟上;
6379
- * **事件驱动启动**:监听 `session/created`(live 会话出现)→ 启动定时器(进程启动时若
6380
- * 已有实验 CCC 会话则立即启动);
6381
- * **优先实验 CCC**:resolveAutoTrajectoryCcc(配置了 autotrajectory live CCC)优先于
6382
- * 进程 cwd/任一 live(多 CCC 同实例时时钟唤起绑定实验 CCC,而非当前维护会话 CCC)。
6423
+ * **v1.27.4 多 CCC 独立(用户"4个CCC能各自有autotrajectory吗")**:
6424
+ * - 单定时器保留(TICK_MS 10min),每次 tick **遍历所有 live+enabled CCC**(collectAutopilotCccs)
6425
+ * 各自评估 shouldWake + 各自唤起——每 CCC 的 interval/session/bias/topPrompt/窗口独立
6426
+ * - `running` 守卫 **per-CCC**(不同 CCC 唤起互不阻塞)
6427
+ * - **全局串行化**(wakeChain:同 tick 多 CCC 到点 依次唤起,防模型并发挤兑)
6383
6428
  *
6384
- * 零资源占用语义保留:未配置/未启用 → tick 内直接 return(定时器存在但每 10min 一次空检查,
6429
+ * 零资源占用语义保留:无 enabled CCC → tick 内直接 return(定时器存在但每 10min 一次空检查,
6385
6430
  * unref 不阻塞进程退出)。
6386
6431
  */
6387
- function registerAutoTrajectory(ctx) {
6432
+ function registerAutopilot(ctx) {
6388
6433
  let timer = null;
6389
- let running = false;
6390
- /** 解析当前应绑定的实验 CCC:配置了 autotrajectory 的 live CCC 优先,回退进程 cwd/任一 live */
6391
- const resolveRoot = () => resolveAutoTrajectoryCcc(ctx) ?? resolveAutoTrajectoryRoot(ctx);
6434
+ const runningByRoot = /* @__PURE__ */ new Map();
6435
+ let wakeChain = Promise.resolve();
6392
6436
  const tick = () => {
6393
- if (running) return;
6394
- const root = resolveRoot();
6395
- const settings = root ? readAutoTrajectorySettings(root) : null;
6396
- if (!settings?.enabled) return;
6397
- if (!shouldWake(settings, resolveTargetMd(root, settings), Date.now(), running)) return;
6398
- running = true;
6399
- (async () => {
6400
- try {
6401
- const res = await performAutoTrajectoryWake(ctx, root, settings, { force: false });
6402
- if (res.ok) console.log(`[serenity-hooks] ✓ 自主轨迹唤起(${res.detail})`);
6403
- else console.warn(`[serenity-hooks] 自主轨迹唤起跳过:${res.detail}`);
6404
- } catch (err) {
6405
- console.warn(`[serenity-hooks] ✗ 自主轨迹唤起失败: ${String(err?.message ?? err)}`);
6406
- } finally {
6407
- running = false;
6408
- }
6409
- })();
6437
+ const roots = collectAutopilotCccs(ctx);
6438
+ if (roots.length === 0) return;
6439
+ for (const root of roots) {
6440
+ if (runningByRoot.get(root)) continue;
6441
+ const settings = readAutopilotSettings(root);
6442
+ if (!settings?.enabled) continue;
6443
+ if (!shouldWake(settings, resolveTargetMd(root, settings), Date.now(), false, wakeHistoryFor(root))) continue;
6444
+ runningByRoot.set(root, true);
6445
+ wakeChain = wakeChain.then(async () => {
6446
+ try {
6447
+ const res = await performAutopilotWake(ctx, root, settings, { force: false });
6448
+ recordWake(root, {
6449
+ time: Date.now(),
6450
+ ok: res.ok,
6451
+ detail: res.detail
6452
+ });
6453
+ if (res.ok) console.log(`[serenity-hooks] ✓ Autopilot Trajectory 唤起(${res.detail})`);
6454
+ else console.warn(`[serenity-hooks] ✗ Autopilot Trajectory 唤起跳过:${res.detail}`);
6455
+ } catch (err) {
6456
+ recordWake(root, {
6457
+ time: Date.now(),
6458
+ ok: false,
6459
+ detail: `唤起异常: ${String(err?.message ?? err)}`
6460
+ });
6461
+ console.warn(`[serenity-hooks] ✗ Autopilot Trajectory 唤起失败: ${String(err?.message ?? err)}`);
6462
+ } finally {
6463
+ runningByRoot.set(root, false);
6464
+ }
6465
+ });
6466
+ }
6410
6467
  };
6411
6468
  const startTimer = () => {
6412
6469
  if (timer) return;
6413
- const root = resolveRoot();
6414
- const settings = root ? readAutoTrajectorySettings(root) : null;
6415
- if (!settings?.enabled) return;
6470
+ if (collectAutopilotCccs(ctx).length === 0) return;
6416
6471
  timer = setInterval(tick, TICK_MS);
6417
6472
  timer.unref();
6418
- console.log(`[serenity-hooks] ✓ 自主轨迹定时器启动(${root},intervalHours=${settings.intervalHours ?? 12})`);
6473
+ console.log(`[serenity-hooks] ✓ Autopilot Trajectory 定时器启动(${collectAutopilotCccs(ctx).length} CCC 启用)`);
6419
6474
  tick();
6420
6475
  };
6421
6476
  startTimer();
@@ -6461,7 +6516,7 @@ function readSessionTitle(session) {
6461
6516
  return null;
6462
6517
  }
6463
6518
  /**
6464
- * 诊断:目标会话 agent 为何不可得(供 performAutoTrajectoryWake 失败信息——
6519
+ * 诊断:目标会话 agent 为何不可得(供 performAutopilotWake 失败信息——
6465
6520
  * 区分"live 会话里无匹配" vs "匹配但 agent 未加载")。返回诊断文本(无则 null)。
6466
6521
  */
6467
6522
  function diagnoseTargetUnavailable(ctx, mdPath) {
@@ -6488,8 +6543,8 @@ function diagnoseTargetUnavailable(ctx, mdPath) {
6488
6543
  if (sameCccTitles.length > 0) return `目标 CCC 内 live 会话标题: [${sameCccTitles.join(", ")}]——均不匹配 ${sid}(目标会话未在 WebUI 打开,或命名未生效)`;
6489
6544
  return `目标 CCC 内无 live 会话(先在 WebUI 打开 ${sid} 会话后重试)`;
6490
6545
  }
6491
- /** 自主轨迹绑定的 CCC 根:进程 cwd 上溯 .serenity 优先,回退任一 live 会话 root */
6492
- function resolveAutoTrajectoryRoot(ctx) {
6546
+ /** Autopilot 绑定的回退 CCC 根:进程 cwd 上溯 .serenity 优先,回退任一 live 会话 root */
6547
+ function resolveAutopilotRoot(ctx) {
6493
6548
  const fromCwd = findSerenityRoot(process.cwd());
6494
6549
  if (fromCwd) return fromCwd;
6495
6550
  try {
@@ -6501,14 +6556,15 @@ function resolveAutoTrajectoryRoot(ctx) {
6501
6556
  } catch {}
6502
6557
  return null;
6503
6558
  }
6504
- function getAutoTrajectoryStatus(root) {
6505
- const cfg = readAutoTrajectorySettings(root);
6559
+ function getAutopilotStatus(root) {
6560
+ const cfg = readAutopilotSettings(root);
6506
6561
  const now = Date.now();
6507
6562
  const base = {
6508
6563
  configured: cfg !== null,
6509
6564
  enabled: cfg?.enabled ?? false,
6510
6565
  intervalHours: Math.max(1, cfg?.intervalHours ?? 12),
6511
- biasProvider: cfg?.biasProvider?.trim() || "autotrajectory-bias.ts",
6566
+ maxDailyWakes: Math.max(1, cfg?.maxDailyWakes ?? 24),
6567
+ biasProvider: cfg?.biasProvider?.trim() || "autopilot-bias.ts",
6512
6568
  topPrompt: cfg?.topPrompt?.trim() || null,
6513
6569
  session: cfg?.session ?? null,
6514
6570
  avoidWakeHours: {
@@ -6516,7 +6572,8 @@ function getAutoTrajectoryStatus(root) {
6516
6572
  end: cfg?.avoidWakeHours?.end ?? DEFAULT_AVOID_HOURS.end
6517
6573
  },
6518
6574
  beijingHour: beijingHour(now),
6519
- windowAllowed: inAllowedWakeWindow(now, cfg?.avoidWakeHours)
6575
+ windowAllowed: inAllowedWakeWindow(now, cfg?.avoidWakeHours),
6576
+ recentWakes: wakeHistoryFor(root).slice(-10).reverse()
6520
6577
  };
6521
6578
  if (!cfg) return {
6522
6579
  ...base,
@@ -6535,9 +6592,9 @@ function getAutoTrajectoryStatus(root) {
6535
6592
  ...base,
6536
6593
  target: {
6537
6594
  dirName: basename(dirname(mdPath)),
6538
- autoFlag: isAutoTrajectorySession(mdPath),
6595
+ autoFlag: isAutopilotSession(mdPath),
6539
6596
  idleHours: Math.max(0, idleHours),
6540
- wakeable: shouldWake(cfg, mdPath, now, false)
6597
+ wakeable: shouldWake(cfg, mdPath, now, false, wakeHistoryFor(root))
6541
6598
  }
6542
6599
  };
6543
6600
  }
@@ -6559,41 +6616,40 @@ function listLiveSessions(ctx) {
6559
6616
  return out;
6560
6617
  }
6561
6618
  /**
6562
- * 面板解析:GET /serenity/autotrajectory workspace 参数时——
6563
- * **优先「配置了 autotrajectorylive CCC」**(用户实验状态所在;v1.26.14 修复:
6564
- * resolveWorkspace 解析到第一个 live 会话(如 home-serenity)→ 面板显示"未配置",
6565
- * 用户实验 CCC(pangu)检测不到)。优先级:已启用 → 已配置(未启用)→ null。
6619
+ * 收集所有「配置了 Autopilot Trajectory live CCC」——**v1.27.4 多 CCC 独立**:
6620
+ * 返回 enabled 的 CCC 列表(时钟 tick 遍历目标);无 enabled → []。
6621
+ * (原 resolveAutoTrajectoryCcc 单目标语义被 collect 取代——面板/诊断用 enableOnly=false 取全量)
6566
6622
  */
6567
- function resolveAutoTrajectoryCcc(ctx) {
6623
+ function collectAutopilotCccs(ctx, opts = {}) {
6568
6624
  const enabled = [];
6569
6625
  const configured = [];
6570
6626
  for (const s of listLiveSessions(ctx)) {
6571
6627
  if (!s.cccRoot) continue;
6572
- const cfg = readAutoTrajectorySettings(s.cccRoot);
6628
+ const cfg = readAutopilotSettings(s.cccRoot);
6573
6629
  if (!cfg) continue;
6574
6630
  if (cfg.enabled) {
6575
6631
  if (!enabled.includes(s.cccRoot)) enabled.push(s.cccRoot);
6576
6632
  } else if (!configured.includes(s.cccRoot)) configured.push(s.cccRoot);
6577
6633
  }
6578
- return enabled[0] ?? configured[0] ?? null;
6634
+ return opts.includeDisabled ? [...enabled, ...configured] : enabled;
6579
6635
  }
6580
6636
  function diagLive(ctx) {
6581
6637
  const liveSessions = listLiveSessions(ctx);
6582
6638
  const processCwd = process.cwd();
6583
6639
  const processCcc = findSerenityRoot(processCwd);
6584
- const panelResolved = resolveAutoTrajectoryCcc(ctx);
6585
- const autotrajectoryCccs = [];
6640
+ const panelResolved = collectAutopilotCccs(ctx)[0] ?? resolveAutopilotRoot(ctx);
6641
+ const autopilotCccs = [];
6586
6642
  const seen = /* @__PURE__ */ new Set();
6587
6643
  for (const s of liveSessions) {
6588
6644
  if (!s.cccRoot || seen.has(s.cccRoot)) continue;
6589
- const cfg = readAutoTrajectorySettings(s.cccRoot);
6645
+ const cfg = readAutopilotSettings(s.cccRoot);
6590
6646
  if (!cfg) continue;
6591
6647
  seen.add(s.cccRoot);
6592
- const status = getAutoTrajectoryStatus(s.cccRoot);
6648
+ const status = getAutopilotStatus(s.cccRoot);
6593
6649
  const mdPath = resolveTargetMd(s.cccRoot, cfg);
6594
6650
  const agentResolved = mdPath ? resolveTargetAgent(ctx, mdPath) !== null : false;
6595
6651
  const agentDiagnosis = mdPath && !agentResolved ? diagnoseTargetUnavailable(ctx, mdPath) : null;
6596
- autotrajectoryCccs.push({
6652
+ autopilotCccs.push({
6597
6653
  root: s.cccRoot,
6598
6654
  enabled: status.enabled,
6599
6655
  session: status.session,
@@ -6606,17 +6662,18 @@ function diagLive(ctx) {
6606
6662
  processCwd,
6607
6663
  processCcc,
6608
6664
  liveSessions,
6609
- autotrajectoryCccs,
6665
+ autopilotCccs,
6610
6666
  panelResolved
6611
6667
  };
6612
6668
  }
6613
6669
  //#endregion
6614
- //#region src/tools/autotrajectory-exp.ts
6670
+ //#region src/tools/autopilot-trajectory.ts
6615
6671
  /**
6616
- * autotrajectory-exp.ts — 自主轨迹实验一站式管理工具(v1.26.12 实验提案,默认关)
6672
+ * autopilot-trajectory.ts — Autopilot Trajectory 一站式管理工具(v1.26.12 实验
6673
+ * autotrajectory-exp → v1.27.4 正式化改名)
6617
6674
  *
6618
- * 定位:dsp **只提供工具与知识**,不向 CCC 自动安装任何东西(实验可能失败,不污染 CCC)——
6619
- * 实验是 CCC 的自选动作:agent 调本工具(doc/全报告)即懂实验,init/random/check 辅助,
6675
+ * 定位:dsp **只提供工具与知识**,不向 CCC 自动安装任何东西(机制是 CCC 的自选动作)——
6676
+ * agent 调本工具(doc/全报告)即懂机制,init/random/check 辅助,
6620
6677
  * 实际执行(写配置/写偏见脚本/标记会话)由 CCC 自己决定、自己用现有工具完成。
6621
6678
  *
6622
6679
  * 实现:薄封装——exec 包内静态脚本(npm files 含 experiments/),脚本是单一真相源。
@@ -6625,24 +6682,24 @@ function diagLive(ctx) {
6625
6682
  * 直接读 live 会话/标题/agent(脚本 diag 看不到运行时),输出实例级诊断报告。
6626
6683
  */
6627
6684
  /**
6628
- * 定位包内实验脚本(npm files 分发 experiments/autotrajectory/)。
6685
+ * 定位包内脚本(npm files 分发 experiments/autopilot-trajectory/)。
6629
6686
  * 布局差异:tsdown bundle 后 import.meta.url 指向 lib/index.js(lib → 包根 1 层);
6630
6687
  * vitest 源码直跑时指向 src/tools/x.ts(src/tools → 包根 2 层)——逐级上溯查找,
6631
- * 两种布局都稳(找到 experiments/autotrajectory/scripts/autotrajectory-exp.ts 即止)。
6688
+ * 两种布局都稳(找到 experiments/autopilot-trajectory/scripts/autopilot-trajectory.ts 即止)。
6632
6689
  */
6633
6690
  function findExpScript(startDir) {
6634
6691
  let cur = startDir;
6635
6692
  while (true) {
6636
- const cand = join(cur, "experiments", "autotrajectory", "scripts", "autotrajectory-exp.ts");
6693
+ const cand = join(cur, "experiments", "autopilot-trajectory", "scripts", "autopilot-trajectory.ts");
6637
6694
  if (existsSync(cand)) return cand;
6638
6695
  const parent = dirname(cur);
6639
6696
  if (parent === cur) return null;
6640
6697
  cur = parent;
6641
6698
  }
6642
6699
  }
6643
- /** 包内实验脚本(上溯查找;找不到 → execute 报错提示包完整性) */
6700
+ /** 包内脚本(上溯查找;找不到 → execute 报错提示包完整性) */
6644
6701
  const EXP_SCRIPT = findExpScript(dirname(fileURLToPath(import.meta.url)));
6645
- const AUTO_TRAJECTORY_EXP_ACTIONS = [
6702
+ const AUTOPILOT_ACTIONS = [
6646
6703
  "all",
6647
6704
  "init",
6648
6705
  "random",
@@ -6665,31 +6722,31 @@ function renderText(value) {
6665
6722
  /** 渲染 diag-live 报告为可读文本(EAP:结构清晰、命中点显式) */
6666
6723
  function renderDiagLive(r) {
6667
6724
  const lines = [
6668
- "═══ 自主轨迹进程内诊断(diag-live,v1.26.14)═══",
6725
+ "═══ Autopilot Trajectory 进程内诊断(diag-live,v1.26.14)═══",
6669
6726
  `进程 cwd: ${r.processCwd}(CCC: ${r.processCcc ?? "无 .serenity"})`,
6670
- `面板解析(无参 GET 目标): ${r.panelResolved ?? "无配置 autotrajectory 的 live CCC"}`,
6727
+ `面板解析(无参 GET 目标): ${r.panelResolved ?? "无配置 Autopilot 的 live CCC"}`,
6671
6728
  "",
6672
6729
  `── live 会话(${r.liveSessions.length})──`
6673
6730
  ];
6674
6731
  if (r.liveSessions.length === 0) lines.push(" (无 live 会话——WebUI 未打开任何会话;定时器绑定依赖 live 会话 cwd)");
6675
6732
  for (const s of r.liveSessions) lines.push(` · ${s.id}${s.title ? ` [${s.title}]` : " [无标题]"}${s.cwd ? ` cwd=${s.cwd}` : " cwd=无"}${s.cccRoot ? ` → CCC ${s.cccRoot}` : ""}`);
6676
- lines.push("", `── 配置了 autotrajectory 的 CCC(${r.autotrajectoryCccs.length})──`);
6677
- if (r.autotrajectoryCccs.length === 0) lines.push(" (无——live 会话中无任何 CCC 配置 autotrajectory)");
6678
- for (const c of r.autotrajectoryCccs) {
6733
+ lines.push("", `── 配置了 Autopilot Trajectory 的 CCC(${r.autopilotCccs.length})──`);
6734
+ if (r.autopilotCccs.length === 0) lines.push(" (无——live 会话中无任何 CCC 配置 autopilotTrajectory)");
6735
+ for (const c of r.autopilotCccs) {
6679
6736
  lines.push(` · ${c.root}(enabled=${c.enabled})`);
6680
6737
  lines.push(` session=${c.session ?? "未配置"} | 目标=${c.target ? `${c.target.dirName}(--auto ${c.target.autoFlag ? "✓" : "✗"},空闲 ${c.target.idleHours.toFixed(1)}h)` : "未命中"}`);
6681
6738
  lines.push(` agent 定位: ${c.agentResolved ? "✓ 可注入" : `✗ 不可得——${c.agentDiagnosis ?? "未知"}`}`);
6682
6739
  }
6683
6740
  return lines.join("\n");
6684
6741
  }
6685
- /** 创建 autotrajectory-exp 工具(闭包捕获 ctx → diag-live 进程内诊断;v1.26.14 */
6686
- function createAutoTrajectoryExpTool(ctx) {
6742
+ /** 创建 autopilot-trajectory 工具(闭包捕获 ctx → diag-live 进程内诊断;v1.26.14 + v1.27.4 改名) */
6743
+ function createAutopilotTool(ctx) {
6687
6744
  return defineTool({
6688
- name: "autotrajectory-exp",
6689
- description: "自主轨迹实验(Self-Sustaining Trajectory)一站式管理——实验提案 v1.26.14,默认关。无参/action=all:全报告(背景摘要 + 就绪检查 + 状态 + 下一步)——CCC agent 看一次即完整理解实验并知道怎么开始;init:初始化辅助(写配置 + 生成偏见提供者脚本模板);random:运行偏见提供者脚本输出当前偏见内容;diag:唤起条件链诊断(--ccc <path> 指定 CCC,无参递归扫描 /home/yh 两层——覆盖任意实验 CCC 位置;逐条件输出 + 阻断点 + 修复建议);diag-live:**进程内诊断**(v1.26.14——live 会话清单/标题/agent 定位/面板解析目标;排查\"面板检测不到实验 CCC\");doc:实验定义全文;check/status/guide:单项。**topPromptv1.26.17,轨迹焦点)**:CCC 定义 autotrajectory 时自己填写本轨迹核心焦点(顶层提示词),每次唤起最先注入——稳定焦点锚定防漂移(实验观察:无焦点多轮唤起轨迹腐化),与偏见内容(每轮随机探索)互补。实验是 CCC 的自选动作——dsp 只提供工具与知识,不自动安装任何东西。",
6745
+ name: "autopilot-trajectory",
6746
+ description: "Autopilot Trajectory(自动巡航轨迹,正式版 v1.27.4;前身 autotrajectory 实验)一站式管理。无参/action=all:全报告(背景摘要 + 就绪检查 + 状态 + 下一步)——CCC agent 看一次即完整理解并知道怎么开始;init:初始化辅助(写配置 + 生成偏见提供者脚本模板);random:运行偏见提供者脚本输出当前偏见内容;diag:唤起条件链诊断(--ccc <path> 指定 CCC,无参递归扫描 /home/yh 两层;逐条件输出 + 阻断点 + 修复建议);diag-live:**进程内诊断**(live 会话清单/标题/agent 定位/面板解析目标;排查\"面板检测不到实验 CCC\");doc:定义全文;check/status/guide:单项。**topPrompt(轨迹焦点)**:CCC 定义时自己填写本轨迹核心焦点(顶层提示词),每次唤起最先注入——稳定焦点锚定防漂移,与偏见内容(每轮随机探索)互补。多 CCC 独立(v1.27.4):每个 live+enabled CCC 各自时钟唤起。机制是 CCC 的自选动作——dsp 只提供工具与知识,不自动安装任何东西。",
6690
6747
  parameters: { action: {
6691
6748
  type: "string",
6692
- enum: [...AUTO_TRAJECTORY_EXP_ACTIONS],
6749
+ enum: [...AUTOPILOT_ACTIONS],
6693
6750
  required: true,
6694
6751
  description: "Subcommand"
6695
6752
  } },
@@ -6702,12 +6759,12 @@ function createAutoTrajectoryExpTool(ctx) {
6702
6759
  const root = findSerenityRoot(agentCwd$1(exec));
6703
6760
  const result = {};
6704
6761
  if (!EXP_SCRIPT) {
6705
- result.error = "autotrajectory-exp 实验脚本未随安装分发(npm 包缺 experiments/autotrajectory/)——请检查包完整性";
6762
+ result.error = "autopilot-trajectory 脚本未随安装分发(npm 包缺 experiments/autopilot-trajectory/)——请检查包完整性";
6706
6763
  return result;
6707
6764
  }
6708
6765
  const script = EXP_SCRIPT;
6709
6766
  if (!existsSync(script)) {
6710
- result.error = `autotrajectory-exp 脚本缺失(${script})——实验包未随安装分发,请检查 npm 包完整性`;
6767
+ result.error = `autopilot-trajectory 脚本缺失(${script})——包未随安装分发,请检查 npm 包完整性`;
6711
6768
  return result;
6712
6769
  }
6713
6770
  const env = { ...process.env };
@@ -6727,7 +6784,7 @@ function createAutoTrajectoryExpTool(ctx) {
6727
6784
  return result;
6728
6785
  }
6729
6786
  if (r.error?.code === "ENOENT") {
6730
- result.error = "autotrajectory-exp 需要 bun 运行时(bun not found in PATH)";
6787
+ result.error = "autopilot-trajectory 需要 bun 运行时(bun not found in PATH)";
6731
6788
  return result;
6732
6789
  }
6733
6790
  result.error = r.stderr?.trim() || r.stdout?.trim() || `exit ${r.status ?? "?"}`;
@@ -8034,7 +8091,7 @@ const FILE_UPLOAD_PATH = "/serenity/file-upload";
8034
8091
  const CONFIG_PATH = "/serenity/config";
8035
8092
  const CCCS_PATH = "/serenity/cccs";
8036
8093
  const PUBLIC_ASK_PATH = "/serenity/public-ask";
8037
- const AUTOTRAJECTORY_PATH = "/serenity/autotrajectory";
8094
+ const AUTOPILOT_PATH = "/serenity/autopilot-trajectory";
8038
8095
  const WEIXIN_PATH = "/serenity/weixin";
8039
8096
  /** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
8040
8097
  const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
@@ -8342,7 +8399,7 @@ function registerStatusApi(ctx, opts = {}) {
8342
8399
  workspace: url.searchParams.get("workspace") ?? void 0
8343
8400
  });
8344
8401
  const root = findSerenityRoot(workspace) ?? "";
8345
- const { discoverCccs } = await import("./skiff-debug-vLsdk7wV.js").then((n) => n.i);
8402
+ const { discoverCccs } = await import("./skiff-debug-BRlc17GU.js").then((n) => n.i);
8346
8403
  sendJson$1(res, 200, { cccs: await discoverCccs(ctx, root) });
8347
8404
  } catch (err) {
8348
8405
  sendJson$1(res, 400, { error: err.message ?? String(err) });
@@ -8396,7 +8453,7 @@ function registerStatusApi(ctx, opts = {}) {
8396
8453
  });
8397
8454
  ctx.webServer.register({
8398
8455
  kind: "exact",
8399
- path: AUTOTRAJECTORY_PATH,
8456
+ path: AUTOPILOT_PATH,
8400
8457
  handler: async (req, res) => {
8401
8458
  try {
8402
8459
  if (req.method === "POST") {
@@ -8415,18 +8472,18 @@ function registerStatusApi(ctx, opts = {}) {
8415
8472
  workspace: body.workspace
8416
8473
  });
8417
8474
  const root = findSerenityRoot(workspace);
8418
- const effectiveRoot = !body.workspace && !body.sessionId ? (await Promise.resolve().then(() => autotrajectory_exports)).resolveAutoTrajectoryCcc(ctx) ?? root : root;
8475
+ const effectiveRoot = body.ccc?.trim() || !body.workspace && !body.sessionId ? body.ccc?.trim() ?? (await Promise.resolve().then(() => autopilot_trajectory_exports)).collectAutopilotCccs(ctx)[0] ?? root : root;
8419
8476
  if (!effectiveRoot) {
8420
8477
  sendJson$1(res, 404, { error: `no CCC found from workspace: ${workspace}` });
8421
8478
  return;
8422
8479
  }
8423
- const { performAutoTrajectoryWake, readAutoTrajectorySettings } = await Promise.resolve().then(() => autotrajectory_exports);
8424
- const settings = readAutoTrajectorySettings(effectiveRoot);
8480
+ const { performAutopilotWake, readAutopilotSettings } = await Promise.resolve().then(() => autopilot_trajectory_exports);
8481
+ const settings = readAutopilotSettings(effectiveRoot);
8425
8482
  if (!settings) {
8426
- sendJson$1(res, 400, { error: "autotrajectory 未配置(.opencode/serenity.json 缺段)" });
8483
+ sendJson$1(res, 400, { error: "Autopilot Trajectory 未配置(.opencode/serenity.json 缺段)" });
8427
8484
  return;
8428
8485
  }
8429
- const result = await performAutoTrajectoryWake(ctx, effectiveRoot, settings, { force: true });
8486
+ const result = await performAutopilotWake(ctx, effectiveRoot, settings, { force: true });
8430
8487
  sendJson$1(res, result.ok ? 200 : 400, result);
8431
8488
  return;
8432
8489
  }
@@ -8440,13 +8497,13 @@ function registerStatusApi(ctx, opts = {}) {
8440
8497
  workspace: url.searchParams.get("workspace") ?? void 0
8441
8498
  });
8442
8499
  const root = findSerenityRoot(workspace);
8443
- const { getAutoTrajectoryStatus, resolveAutoTrajectoryCcc } = await Promise.resolve().then(() => autotrajectory_exports);
8444
- const effectiveRoot = !url.searchParams.get("workspace") && !url.searchParams.get("sessionId") ? resolveAutoTrajectoryCcc(ctx) ?? root : root;
8500
+ const { getAutopilotStatus, collectAutopilotCccs } = await Promise.resolve().then(() => autopilot_trajectory_exports);
8501
+ const effectiveRoot = url.searchParams.get("ccc")?.trim() ?? (!url.searchParams.get("workspace") && !url.searchParams.get("sessionId") ? collectAutopilotCccs(ctx)[0] : null) ?? root;
8445
8502
  if (!effectiveRoot) {
8446
8503
  sendJson$1(res, 200, { status: null });
8447
8504
  return;
8448
8505
  }
8449
- sendJson$1(res, 200, { status: getAutoTrajectoryStatus(effectiveRoot) });
8506
+ sendJson$1(res, 200, { status: getAutopilotStatus(effectiveRoot) });
8450
8507
  } catch (err) {
8451
8508
  sendJson$1(res, 400, { error: err.message ?? String(err) });
8452
8509
  }
@@ -8468,11 +8525,11 @@ function registerStatusApi(ctx, opts = {}) {
8468
8525
  sendJson$1(res, 400, { error: chk.error });
8469
8526
  return;
8470
8527
  }
8471
- const { readWeixinSettings } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8528
+ const { readWeixinSettings } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8472
8529
  const { weixinBridgeStatus } = await Promise.resolve().then(() => weixin_bridge_exports);
8473
8530
  const settings = readWeixinSettings(chk.root);
8474
8531
  const bridge = weixinBridgeStatus().find((b) => b.ccc === chk.root);
8475
- const { readWeixinCredential } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8532
+ const { readWeixinCredential } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8476
8533
  const accounts = (settings.accounts ?? []).map((a) => ({
8477
8534
  accountId: a.accountId,
8478
8535
  name: a.name ?? void 0,
@@ -8501,10 +8558,10 @@ function registerStatusApi(ctx, opts = {}) {
8501
8558
  }
8502
8559
  const root = chk.root;
8503
8560
  if (body.action === "login-start") {
8504
- const { fetchQRCode } = await import("./weixin-api-b8HLmcPE.js").then((n) => n.r);
8561
+ const { fetchQRCode } = await import("./weixin-api-DAugVJ-f.js").then((n) => n.c);
8505
8562
  const { randomUUID } = await import("node:crypto");
8506
8563
  for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
8507
- const { readWeixinSettings } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8564
+ const { readWeixinSettings } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8508
8565
  const qr = await fetchQRCode({ botType: readWeixinSettings(root).botType ?? void 0 });
8509
8566
  const loginKey = randomUUID();
8510
8567
  weixinLogins.set(loginKey, {
@@ -8525,7 +8582,7 @@ function registerStatusApi(ctx, opts = {}) {
8525
8582
  sendJson$1(res, 400, { error: "missing accountId" });
8526
8583
  return;
8527
8584
  }
8528
- const { removeWeixinAccount } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8585
+ const { removeWeixinAccount } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8529
8586
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
8530
8587
  removeWeixinAccount(root, body.accountId);
8531
8588
  syncCccBridge(ctx, root);
@@ -8538,7 +8595,7 @@ function registerStatusApi(ctx, opts = {}) {
8538
8595
  sendJson$1(res, 400, { error: "invalid routes (expected [{user, role}, ...])" });
8539
8596
  return;
8540
8597
  }
8541
- const { saveWeixinRoutes } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8598
+ const { saveWeixinRoutes } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8542
8599
  const { readSkiffRoles } = await import("./skiff-role-Dw7UKCUm.js").then((n) => n.c);
8543
8600
  const roles = readSkiffRoles(root);
8544
8601
  for (const r of routes) if (!roles.has(r.role)) {
@@ -8551,7 +8608,7 @@ function registerStatusApi(ctx, opts = {}) {
8551
8608
  }
8552
8609
  if (body.action === "set-enabled") {
8553
8610
  const enabled = body.enabled === true;
8554
- const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8611
+ const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8555
8612
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
8556
8613
  const settings = readWeixinSettings(root);
8557
8614
  if (enabled && (settings.accounts ?? []).length === 0) {
@@ -8597,8 +8654,8 @@ function registerStatusApi(ctx, opts = {}) {
8597
8654
  sendJson$1(res, 200, { status: "expired" });
8598
8655
  return;
8599
8656
  }
8600
- const { pollQRStatus } = await import("./weixin-api-b8HLmcPE.js").then((n) => n.r);
8601
- const { readWeixinSettings } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8657
+ const { pollQRStatus } = await import("./weixin-api-DAugVJ-f.js").then((n) => n.c);
8658
+ const { readWeixinSettings } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8602
8659
  const settings = readWeixinSettings(login.root);
8603
8660
  const status = await pollQRStatus({
8604
8661
  baseUrl: void 0,
@@ -8613,7 +8670,7 @@ function registerStatusApi(ctx, opts = {}) {
8613
8670
  });
8614
8671
  return;
8615
8672
  }
8616
- const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-CvAWARN3.js").then((n) => n.o);
8673
+ const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-EVu56Ytp.js").then((n) => n.u);
8617
8674
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
8618
8675
  const accountId = nextWeixinAccountId(settings);
8619
8676
  upsertWeixinAccount(login.root, {
@@ -10547,6 +10604,56 @@ var weixin_bridge_exports = /* @__PURE__ */ __exportAll({
10547
10604
  const bridges = /* @__PURE__ */ new Map();
10548
10605
  /** 轮询间隔(getupdates 失败后重试延迟;成功 = 立即续轮询) */
10549
10606
  const POLL_RETRY_MS = 3e3;
10607
+ /** 媒体大小上限(M6:20MB,对齐 vlm-describe;超限降级告知不落盘) */
10608
+ const MEDIA_MAX_BYTES = 20971520;
10609
+ /** typing_ticket 缓存(每 (accountId, fromUserId);getconfig 一次后续复用——对齐参考实现 typingTicketCache) */
10610
+ const typingTicketByUser = /* @__PURE__ */ new Map();
10611
+ function typingCacheKey(accountId, fromUserId) {
10612
+ return `${accountId}|${fromUserId}`;
10613
+ }
10614
+ /** 处理开始:getconfig(无缓存时)→ sendtyping status=1(微信侧显示"正在输入...")。
10615
+ * 任何失败静默(typing 不影响主流程——对齐参考实现 typingCallbacks try/catch 吞错)。 */
10616
+ async function sendTypingStart(cred, accountId, fromUserId, contextToken) {
10617
+ try {
10618
+ const key = typingCacheKey(accountId, fromUserId);
10619
+ let ticket = typingTicketByUser.get(key);
10620
+ if (!ticket) {
10621
+ ticket = (await getConfig({
10622
+ baseUrl: cred.baseUrl,
10623
+ token: cred.token,
10624
+ ilinkUserId: fromUserId,
10625
+ contextToken: contextToken ?? ""
10626
+ })).typing_ticket;
10627
+ if (ticket) typingTicketByUser.set(key, ticket);
10628
+ }
10629
+ if (!ticket) return;
10630
+ await sendTyping({
10631
+ baseUrl: cred.baseUrl,
10632
+ token: cred.token,
10633
+ ilinkUserId: fromUserId,
10634
+ typingTicket: ticket,
10635
+ status: TypingStatus.TYPING
10636
+ });
10637
+ } catch (err) {
10638
+ console.log(`[serenity-hooks] weixin-bridge typing start skipped: ${err instanceof Error ? err.message : String(err)}`);
10639
+ }
10640
+ }
10641
+ /** 处理结束(含异常路径 finally):sendtyping status=0;缓存保留(ticket 可复用)。 */
10642
+ async function sendTypingStop(cred, accountId, fromUserId) {
10643
+ try {
10644
+ const ticket = typingTicketByUser.get(typingCacheKey(accountId, fromUserId));
10645
+ if (!ticket) return;
10646
+ await sendTyping({
10647
+ baseUrl: cred.baseUrl,
10648
+ token: cred.token,
10649
+ ilinkUserId: fromUserId,
10650
+ typingTicket: ticket,
10651
+ status: TypingStatus.CANCEL
10652
+ });
10653
+ } catch (err) {
10654
+ console.log(`[serenity-hooks] weixin-bridge typing stop skipped: ${err instanceof Error ? err.message : String(err)}`);
10655
+ }
10656
+ }
10550
10657
  /** 单账号轮询循环:getupdates 长轮询 → 逐消息分发 → 回复回写 */
10551
10658
  async function runAccountLoop(ctx, root, accountId, cred, loop) {
10552
10659
  let buf = "";
@@ -10579,16 +10686,35 @@ async function runAccountLoop(ctx, root, accountId, cred, loop) {
10579
10686
  * 固定 id resume-or-create(v1.27.2):磁盘已有持久化 log → resume(历史延续,
10580
10687
  * 重启后记忆保留——真正的"同用户长期延续");无 log(首次)→ create。
10581
10688
  * "新的对话已开始"通知仅真正首次(create)时发送;resume/进程内延续不发。
10582
- */
10583
- async function handleIncoming(ctx, root, _accountId, cred, msg) {
10689
+ *
10690
+ * 完善(v1.27.3):
10691
+ * - **语音支持**:`voice_item.text` = 微信服务端自带语音转写 → 与文本同路径进对话
10692
+ * (无需下载/ASR);语音无转写 → 降级提示"暂时无法解析"
10693
+ * - **正在输入**:处理前 sendtyping 1(微信显示"正在输入..."),处理后(含异常)0
10694
+ * - **媒体接收(图片/文件)**:桥侧 CDN 下载 + AES 解密 → 落盘 CCC 根
10695
+ * `_tmp/weixin-inbound/<userhash>/` → question 注入「存在性 + 路径」(ACC 层只保证
10696
+ * 可达性——"让会话知道文件的存在并可以拿到";识别/解析归角色 LLM 决策,不编排)。
10697
+ * 降级不静默:下载失败 / 超 20MB → 注入说明进对话;typing 窗口覆盖下载(M7)。
10698
+ */
10699
+ async function handleIncoming(ctx, root, accountId, cred, msg) {
10584
10700
  const fromUserId = msg.from_user_id;
10585
10701
  if (!fromUserId) return;
10586
10702
  const text = extractWeixinText(msg);
10587
- if (!text) return;
10703
+ const mediaRefs = extractWeixinMedia(msg);
10588
10704
  const settings = readWeixinSettings(root);
10589
10705
  if (!settings.enabled) return;
10590
10706
  const roleName = matchWeixinRoute(settings.routes ?? [], fromUserId);
10591
10707
  if (!roleName) return;
10708
+ if (!text && mediaRefs.length === 0) {
10709
+ if (hasVoiceItem(msg)) await sendTextMessage({
10710
+ baseUrl: cred.baseUrl,
10711
+ token: cred.token,
10712
+ toUserId: fromUserId,
10713
+ text: "(抱歉,暂时无法解析这条语音消息,请尝试发送文字)",
10714
+ contextToken: msg.context_token
10715
+ }).catch(() => {});
10716
+ return;
10717
+ }
10592
10718
  try {
10593
10719
  const role = readSkiffRoles(root).get(roleName);
10594
10720
  if (!role) {
@@ -10610,15 +10736,53 @@ async function handleIncoming(ctx, root, _accountId, cred, msg) {
10610
10736
  text: "(新的对话已开始)",
10611
10737
  contextToken: msg.context_token
10612
10738
  }).catch(() => {});
10613
- const answer = (await askSkiff(ctx, ref.agent, text, void 0, { includeTrajectory: false })).answer ?? "";
10614
- if (answer === "") return;
10615
- await sendTextMessage({
10616
- baseUrl: cred.baseUrl,
10617
- token: cred.token,
10618
- toUserId: fromUserId,
10619
- text: stripThink(answer),
10620
- contextToken: msg.context_token
10621
- });
10739
+ await sendTypingStart(cred, accountId, fromUserId, msg.context_token);
10740
+ try {
10741
+ const mediaNotes = [];
10742
+ const degradedNotes = [];
10743
+ for (const mediaRef of mediaRefs) {
10744
+ const mediaType = mediaRef.kind === "image" ? "image_item" : "file_item";
10745
+ const label = mediaRef.kind === "image" ? "一张图片" : `文件 ${mediaRef.fileName ?? "(未命名)"}`;
10746
+ const result = await downloadMedia({
10747
+ item: mediaRef.item,
10748
+ mediaType
10749
+ });
10750
+ if (!result) {
10751
+ degradedNotes.push(`(用户发送了${label},但下载失败——可请用户重发)`);
10752
+ continue;
10753
+ }
10754
+ if (result.data.length > MEDIA_MAX_BYTES) {
10755
+ degradedNotes.push(`(用户发送了${label},但超过 20MB 大小限制)`);
10756
+ continue;
10757
+ }
10758
+ try {
10759
+ const inboundDir = weixinInboundDir(root, fromUserId);
10760
+ mkdirSync(inboundDir, { recursive: true });
10761
+ const fname = mediaRef.kind === "image" ? `img_${Date.now()}_${randomBytes(4).toString("hex")}.${sniffImageExt(result.data)}` : sanitizeFileName$1(result.fileName ?? "") || `file_${Date.now()}_${randomBytes(4).toString("hex")}`;
10762
+ const abs = join(inboundDir, fname);
10763
+ writeFileSync(abs, result.data);
10764
+ const rel = relative(root, abs);
10765
+ mediaNotes.push(`(用户发送了${mediaRef.kind === "image" ? "一张图片" : `文件 ${fname}`},已保存到 ${rel})`);
10766
+ } catch {
10767
+ degradedNotes.push(`(媒体保存失败,请重试)`);
10768
+ }
10769
+ }
10770
+ const parts = [];
10771
+ if (text) parts.push(text);
10772
+ parts.push(...mediaNotes, ...degradedNotes);
10773
+ const question = parts.join("\n");
10774
+ const answer = (await askSkiff(ctx, ref.agent, question, void 0, { includeTrajectory: false })).answer ?? "";
10775
+ if (answer === "") return;
10776
+ await sendTextMessage({
10777
+ baseUrl: cred.baseUrl,
10778
+ token: cred.token,
10779
+ toUserId: fromUserId,
10780
+ text: stripThink(answer),
10781
+ contextToken: msg.context_token
10782
+ });
10783
+ } finally {
10784
+ await sendTypingStop(cred, accountId, fromUserId);
10785
+ }
10622
10786
  } catch (err) {
10623
10787
  const msg = err instanceof Error ? err.message : String(err);
10624
10788
  console.log(`[serenity-hooks] weixin-bridge error (ccc=${root}): ${msg}`);
@@ -10750,7 +10914,7 @@ function apply(ctx, config) {
10750
10914
  ctx.tools.register(createRebuildTool(ctx));
10751
10915
  ctx.tools.register(localstoreTool);
10752
10916
  ctx.tools.register(skiffAdminTool);
10753
- ctx.tools.register(createAutoTrajectoryExpTool(ctx));
10917
+ ctx.tools.register(createAutopilotTool(ctx));
10754
10918
  }
10755
10919
  if (config.guards) registerGuards(ctx, { configPaths: config.serenityConfigPaths });
10756
10920
  if (config.keeper) registerKeeper(ctx, {
@@ -10776,7 +10940,7 @@ function apply(ctx, config) {
10776
10940
  registerBootstrap(ctx);
10777
10941
  registerSkiff(ctx);
10778
10942
  registerAcp(ctx);
10779
- registerAutoTrajectory(ctx);
10943
+ registerAutopilot(ctx);
10780
10944
  registerWeixinBridge(ctx);
10781
10945
  }
10782
10946
  /**