oasis_test_v2 2.2.7 → 2.2.9

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.
Files changed (2) hide show
  1. package/dist/index.js +668 -147
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -665,7 +665,7 @@ function isAgentAvatarPresetRef(ref2) {
665
665
  const id = ref2.slice(AGENT_AVATAR_PRESET_PREFIX.length);
666
666
  return AGENT_AVATAR_PRESET_IDS.includes(id);
667
667
  }
668
- var AGENT_AVATAR_PRESET_PREFIX, AGENT_AVATAR_LEGACY_PRESET_IDS, AGENT_AVATAR_V2_PRESET_IDS, AGENT_AVATAR_PRESET_IDS;
668
+ var AGENT_AVATAR_PRESET_PREFIX, AGENT_AVATAR_LEGACY_PRESET_IDS, AGENT_AVATAR_V2_PRESET_IDS, AGENT_AVATAR_PRESET_IDS, SYSTEM_AVATAR_REF;
669
669
  var init_avatar = __esm({
670
670
  "../contract/src/avatar.ts"() {
671
671
  "use strict";
@@ -695,6 +695,7 @@ var init_avatar = __esm({
695
695
  ...AGENT_AVATAR_LEGACY_PRESET_IDS,
696
696
  ...AGENT_AVATAR_V2_PRESET_IDS
697
697
  ];
698
+ SYSTEM_AVATAR_REF = "preset:system";
698
699
  }
699
700
  });
700
701
 
@@ -910,10 +911,15 @@ var init_link_windows = __esm({
910
911
  });
911
912
 
912
913
  // ../contract/src/registry.ts
913
- var AGENT_PREFS_DEFAULTS, SkillOwnedByAnotherCompanyError;
914
+ function isSystemActorId(id) {
915
+ return typeof id === "string" && id.startsWith(SYSTEM_ACTOR_ID_PREFIX);
916
+ }
917
+ var SYSTEM_ACTOR_ID_PREFIX, SYSTEM_ACTOR_DISPLAY_NAME, AGENT_PREFS_DEFAULTS, SkillOwnedByAnotherCompanyError;
914
918
  var init_registry = __esm({
915
919
  "../contract/src/registry.ts"() {
916
920
  "use strict";
921
+ SYSTEM_ACTOR_ID_PREFIX = "actor:system";
922
+ SYSTEM_ACTOR_DISPLAY_NAME = "\u7CFB\u7EDF";
917
923
  AGENT_PREFS_DEFAULTS = { memoryEnabled: true };
918
924
  SkillOwnedByAnotherCompanyError = class extends Error {
919
925
  constructor(skillId, companyId) {
@@ -17406,6 +17412,7 @@ ${supplemental.taskAppend.trim()}
17406
17412
  ...Object.keys(jobEnv).length > 0 ? { env: jobEnv } : {},
17407
17413
  ...provisioned?.wrapperPaths && provisioned.wrapperPaths.length > 0 ? { wrapperPaths: provisioned.wrapperPaths } : {},
17408
17414
  ...provisioned?.requiredTools && provisioned.requiredTools.length > 0 ? { requiredTools: provisioned.requiredTools } : {},
17415
+ ...provisioned?.requiredToolVersions && Object.keys(provisioned.requiredToolVersions).length > 0 ? { requiredToolVersions: provisioned.requiredToolVersions } : {},
17409
17416
  // 凭据本体随 job 下发,供**节点侧本机注入**(见上方 provision 注释)。
17410
17417
  ...provisioned?.connectorCreds && provisioned.connectorCreds.length > 0 ? { connectorCreds: provisioned.connectorCreds } : {},
17411
17418
  ...executionEnv !== void 0 ? { executionEnv } : {},
@@ -29522,7 +29529,7 @@ async function sweepStaleChatTurns(deps) {
29522
29529
  `[chat-turn-sweep] \u8F6E\u6B21 ${turn.id}\uFF08\u4F1A\u8BDD ${turn.chatSessionId}\uFF09\u6536\u53E3\u4E3A ${outcome.status}\uFF08${outcome.reason}\uFF09\uFF0C\u4F1A\u8BDD\u69FD\u5DF2\u91CA\u653E`
29523
29530
  );
29524
29531
  const settledStatus = outcome.settled ? outcome.status : void 0;
29525
- if (deps.onTurnSettled && settledStatus && settledStatus !== "succeeded") {
29532
+ if (deps.onTurnSettled && settledStatus) {
29526
29533
  await deps.onTurnSettled({
29527
29534
  chatSessionId: turn.chatSessionId,
29528
29535
  turnId: turn.id,
@@ -162346,6 +162353,17 @@ var init_planner = __esm({
162346
162353
  });
162347
162354
 
162348
162355
  // ../server/src/domains/collab/workorders.ts
162356
+ function systemActorFallback(id) {
162357
+ return isSystemActorId(id) ? { name: SYSTEM_ACTOR_DISPLAY_NAME, avatar: SYSTEM_AVATAR_REF } : void 0;
162358
+ }
162359
+ function actorRefFieldsOf(a) {
162360
+ const avatar = a.avatar ?? (a.kind === "system" ? SYSTEM_AVATAR_REF : void 0);
162361
+ return {
162362
+ name: a.name,
162363
+ ...a.roles[0] ? { role: a.roles[0] } : {},
162364
+ ...avatar ? { avatar } : {}
162365
+ };
162366
+ }
162349
162367
  function overviewRootIdOf(arts) {
162350
162368
  const roots = arts.filter((a) => a.inputs.length === 0);
162351
162369
  if (roots.length <= 1) return roots[0]?.id;
@@ -162606,6 +162624,7 @@ var init_workorders = __esm({
162606
162624
  "../server/src/domains/collab/workorders.ts"() {
162607
162625
  "use strict";
162608
162626
  init_src5();
162627
+ init_src();
162609
162628
  init_node_state();
162610
162629
  init_planner();
162611
162630
  init_workorder_detail();
@@ -195837,7 +195856,15 @@ var init_service3 = __esm({
195837
195856
  ...companyId ? { companyId } : {},
195838
195857
  ...snapshot ? { parentConversationSnapshot: snapshot } : {},
195839
195858
  extraSystemPrompt: EXPERT_DELEGATION_SYSTEM_PROMPT,
195840
- ..._DelegationService.inboundBundle(inbound) ? { workspaceBundle: _DelegationService.inboundBundle(inbound) } : {}
195859
+ ..._DelegationService.inboundBundle(inbound) ? { workspaceBundle: _DelegationService.inboundBundle(inbound) } : {},
195860
+ /* 派发这一侧要拿它们建 `chat_items` 账本(见本文件 `DelegationDispatchRequest` 的注释)。
195861
+ 轮次口径与 `bindChildRunToPlaceholder` / `seedChildRoundRows` **逐字一致**:
195862
+ `record.roundSummaries.length + 1`。三处一旦不同源,item 就会挂到上一轮那条回答行上。 */
195863
+ assistantMessageId: childRoundMessageIds(
195864
+ record8.childSessionId,
195865
+ record8.roundSummaries.length + 1
195866
+ ).placeholderId,
195867
+ chatTurnId: turnId
195841
195868
  });
195842
195869
  outcomeSettled = true;
195843
195870
  await opts?.onDispatched?.().catch((err) => console.warn(`[delegation] \u5F85\u8F6C\u8FBE\u6807\u8BB0\u5DF2\u6D88\u8D39\u5931\u8D25\uFF08${record8.id}\uFF09: ${String(err)}`));
@@ -195996,11 +196023,12 @@ var init_service3 = __esm({
195996
196023
  );
195997
196024
  }
195998
196025
  }
195999
- async settleFromSweep(childSessionId, reason, companyId) {
196026
+ async settleFromSweep(childSessionId, reason, companyId, opts) {
196000
196027
  const store = await this.options.resolveStore(companyId).catch(() => null);
196001
196028
  if (!store) return null;
196002
196029
  const record8 = await store.getByChildSession(childSessionId).catch(() => null);
196003
196030
  if (!record8 || record8.state !== "running") return record8;
196031
+ if (opts?.outcome === "succeeded") return this.settleSucceededFromSweep(record8, companyId);
196004
196032
  const at = this.now();
196005
196033
  const settledSeqAtRound = record8.settledSeq + 1;
196006
196034
  const newSummary = {
@@ -196020,6 +196048,58 @@ var init_service3 = __esm({
196020
196048
  if (settled) await this.notifySettled(settled, companyId);
196021
196049
  return settled;
196022
196050
  }
196051
+ /**
196052
+ * 清扫拍把这一轮收成 **succeeded** 时的收口(2026-09-10 生产实测的那个洞)。
196053
+ *
196054
+ * 什么时候会走到这里:`settleRound` 是**派发那一刻建的闭包**,serve 一重启它就随老进程消失,
196055
+ * 这一轮改由 `chat-recovery` 重挂、跑完、把 assistant 行写成 done;随后清扫拍按「行已终态」
196056
+ * 把轮次收成 succeeded。此前这一支不外报,于是**没有任何人**去记这一轮、判终态、通知父会话、
196057
+ * 把产物搬回主会话工作区——专家干完了,页面上却永远转圈。
196058
+ * 现场:委派 e6196ab7 第 6 轮 04:02 写完 `outputs/org-collaboration-relationship.md` 并收尾,
196059
+ * 轮次 04:03:30 succeeded,台账仍是 `running`/5 轮、`settled_at` 空。
196060
+ *
196061
+ * 与失败那一支的差别只有三处,其余(追加式记账、`settledSeq`、唯一出口 `notifySettled`)逐字相同:
196062
+ * ① 摘要正文取**子会话这一轮真正的回答**,不是「被时钟闭合」那句;
196063
+ * ② 收成 `done`,不带 `failureKind`,`consecutiveFails` 归零;
196064
+ * ③ 多一道幂等闸,见下。
196065
+ *
196066
+ * **幂等闸**:`roundSummaries.length > dispatchSeq` 就说明这一轮 `settleRound` 已经记过了
196067
+ * (第 N 轮用的是 `dispatchSeq = N-1`),直接收手。没有它,「本进程没重启、`settleRound`
196068
+ * 正在后台跑收尾」与这一拍撞车时会把同一轮记两遍——这正是这一支原先被整个关掉的理由,
196069
+ * 现在用一道显式的闸换掉那个一刀切。
196070
+ */
196071
+ async settleSucceededFromSweep(record8, companyId) {
196072
+ const store = await this.options.resolveStore(companyId).catch(() => null);
196073
+ if (!store) return record8;
196074
+ const roundsRecorded = record8.roundSummaries.length;
196075
+ const thisRound = (record8.dispatchSeq ?? 0) + 1;
196076
+ if (roundsRecorded >= thisRound) return record8;
196077
+ const at = this.now();
196078
+ const text5 = await this.readChildRoundAnswer(record8, thisRound, companyId);
196079
+ const settledSeqAtRound = record8.settledSeq + 1;
196080
+ const newSummary = {
196081
+ round: (record8.roundSummaries[record8.roundSummaries.length - 1]?.round ?? 0) + 1,
196082
+ text: text5 || "\u8FD9\u4E00\u8F6E\u5DF2\u5B8C\u6210\uFF08\u6536\u5C3E\u65F6\u6B63\u6587\u6CA1\u8BFB\u5230\uFF0C\u4EA7\u7269\u4EE5\u5DE5\u4F5C\u533A\u91CC\u7684\u6587\u4EF6\u4E3A\u51C6\uFF09\u3002",
196083
+ at,
196084
+ settledSeq: settledSeqAtRound
196085
+ };
196086
+ const settled = await store.appendRoundSummary(record8.id, newSummary, {
196087
+ state: "done",
196088
+ consecutiveFails: 0,
196089
+ settledSeq: settledSeqAtRound,
196090
+ settledAt: at
196091
+ }).catch(() => null);
196092
+ if (settled) await this.notifySettled(settled, companyId);
196093
+ return settled;
196094
+ }
196095
+ /** 子会话第 `round` 轮那条回答行的正文(`childRoundMessageIds` 是唯一的 id 口径)。 */
196096
+ async readChildRoundAnswer(record8, round, companyId) {
196097
+ const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
196098
+ if (!chatStore?.listMessages) return "";
196099
+ const { placeholderId } = childRoundMessageIds(record8.childSessionId, round);
196100
+ const messages = await chatStore.listMessages(record8.childSessionId).catch(() => []);
196101
+ return (messages.find((m2) => m2.id === placeholderId)?.content ?? "").trim();
196102
+ }
196023
196103
  /**
196024
196104
  * 队列里还有话没送到专家眼前 → 起下一轮,**把队列内容拼进 message**(§6.2.1 第二条路径)。
196025
196105
  *
@@ -197101,7 +197181,11 @@ function toChatDispatchRequest(request2) {
197101
197181
  delegatedChildTurn: true,
197102
197182
  // §6.1.3 去程:`--file` 带来的字节就在这一格。**丢了它命令照样回 202**,
197103
197183
  // 而专家的工作区里什么都没有——这正是上一版真实发生过的形态。
197104
- ...request2.workspaceBundle ? { workspaceBundle: request2.workspaceBundle } : {}
197184
+ ...request2.workspaceBundle ? { workspaceBundle: request2.workspaceBundle } : {},
197185
+ // 这两格漏了同样**不报错**:子会话照跑、正文照回,只是 `chat_items` 的行没有 message_id、
197186
+ // turnId 回落成 `chat-run-turn:<runId>`,右栏面板的增量流对不上轮——正是本文件存在的理由。
197187
+ ...request2.assistantMessageId ? { assistantMessageId: request2.assistantMessageId } : {},
197188
+ ...request2.chatTurnId ? { chatTurnId: request2.chatTurnId } : {}
197105
197189
  };
197106
197190
  }
197107
197191
  function applyWorkspaceBundle(target, bundle) {
@@ -197321,27 +197405,77 @@ var init_assembly = __esm({
197321
197405
  });
197322
197406
 
197323
197407
  // ../server/src/domains/delegations/child-live-turn.ts
197324
- function registerDelegatedChildTurn(liveChat, childSessionId, session) {
197408
+ function registerDelegatedChildTurn(liveChat, childSessionId, session, deps = {}) {
197409
+ const log3 = deps.log ?? ((m2) => console.warn(m2));
197325
197410
  const appendInput = session.appendInput;
197326
- if (typeof appendInput !== "function" || session.canAppendInput === false) return;
197411
+ const canAppend = typeof appendInput === "function" && session.canAppendInput !== false;
197412
+ const ledger = new ChatItemLedger({
197413
+ ...deps.items ? { items: deps.items } : {},
197414
+ sessionId: childSessionId,
197415
+ turnId: deps.turnId ?? fallbackTurnId(session.runId),
197416
+ ...session.runId ? { runId: session.runId } : {},
197417
+ ...deps.assistantMessageId ? { messageId: deps.assistantMessageId } : {},
197418
+ ...deps.versionSeed !== void 0 ? { versionSeed: deps.versionSeed } : {},
197419
+ log: log3
197420
+ });
197327
197421
  const ctrl = liveChat.start(childSessionId, {
197328
197422
  runtimeSessionId: session.id,
197329
197423
  ...session.runId ? { runId: session.runId } : {},
197330
197424
  kill: () => {
197331
197425
  void session.kill?.();
197332
197426
  },
197333
- appendInput: (input) => appendInput.call(session, input),
197427
+ // 纪律 ①:插不进去的 runtime 也登记,只是这两格照实报。
197428
+ ...canAppend && appendInput ? { appendInput: (input) => appendInput.call(session, input) } : {},
197334
197429
  // **每次现读**,不快照:一轮跑到收尾时 stdin 会先关,那之后 runtime 自己会回
197335
197430
  // `session-closing`,判断权本就该留在它那儿(同 `/api/chat` 那条路的写法)。
197336
197431
  get canAppendInput() {
197337
- return session.canAppendInput !== false;
197432
+ return typeof session.appendInput === "function" && session.canAppendInput !== false;
197433
+ }
197434
+ }, { items: ledger });
197435
+ if (deps.assistantMessageId) ctrl.setAssistantMessageId(deps.assistantMessageId);
197436
+ const hasChannels = typeof session.onOutput === "function" || typeof session.onTelemetry === "function" || typeof session.onLiveEvent === "function";
197437
+ const normalized4 = typeof session.onNormalizedProviderEvent === "function" ? {
197438
+ onNormalizedProviderEvent: session.onNormalizedProviderEvent.bind(session),
197439
+ finishTurn: (signal) => session.finishNormalizedTurn?.(signal)
197440
+ } : hasChannels ? attachStreamNormalizer(
197441
+ {
197442
+ id: session.id,
197443
+ onOutput: (cb) => session.onOutput?.(cb),
197444
+ onTelemetry: (cb) => session.onTelemetry?.(cb),
197445
+ onLiveEvent: (cb) => session.onLiveEvent?.(cb),
197446
+ ...session.supportsLiveProtocol !== void 0 ? { supportsLiveProtocol: session.supportsLiveProtocol } : {}
197447
+ },
197448
+ {
197449
+ providerName: deps.runtimeKind ?? "runtime",
197450
+ fallbackTurnId: `oasis-turn:${session.runId ?? session.id}`
197451
+ }
197452
+ ) : null;
197453
+ normalized4?.onNormalizedProviderEvent((event) => {
197454
+ try {
197455
+ ctrl.applyNormalizedEvent(event);
197456
+ } catch {
197338
197457
  }
197339
197458
  });
197340
- void session.done.then(() => ctrl.finish("done"), () => ctrl.finish("error"));
197459
+ const settle = async (signal) => {
197460
+ try {
197461
+ normalized4?.finishTurn(signal);
197462
+ } catch {
197463
+ }
197464
+ try {
197465
+ ledger.finish(signal);
197466
+ await ledger.drain();
197467
+ } catch (err) {
197468
+ log3(`[delegation] \u5B50\u4F1A\u8BDD ${childSessionId} \u8D26\u672C\u6536\u5C3E\u5931\u8D25: ${String(err)}`);
197469
+ }
197470
+ ctrl.finish(signal === "completed" ? "done" : "error");
197471
+ };
197472
+ void session.done.then(() => settle("completed"), () => settle("failed"));
197341
197473
  }
197342
197474
  var init_child_live_turn = __esm({
197343
197475
  "../server/src/domains/delegations/child-live-turn.ts"() {
197344
197476
  "use strict";
197477
+ init_src6();
197478
+ init_chat_item_ledger();
197345
197479
  }
197346
197480
  });
197347
197481
 
@@ -197455,10 +197589,12 @@ var init_tool_home = __esm({
197455
197589
  });
197456
197590
 
197457
197591
  // ../connectors/src/_base/install.ts
197458
- function installPlan(cmd, platform2, toolsPrefix) {
197459
- if (cmd === "lark-cli") {
197592
+ function installPlan(cmd, platform2, toolsPrefix, version2) {
197593
+ const npmPkg = NPM_TOOL_PACKAGES[cmd];
197594
+ if (npmPkg) {
197460
197595
  const npm = platform2 === "win32" ? "npm.cmd" : "npm";
197461
- return [{ file: npm, args: ["install", "-g", "--prefix", toolsPrefix, "@larksuite/cli"], ensureDir: toolsPrefix }];
197596
+ const spec = version2 ? `${npmPkg}@${version2}` : npmPkg;
197597
+ return [{ file: npm, args: ["install", "-g", "--prefix", toolsPrefix, spec], ensureDir: toolsPrefix }];
197462
197598
  }
197463
197599
  const pkg = cmd === "gh" ? { brew: "gh", apt: "gh", dnf: "gh", pacman: "github-cli", apk: "github-cli", winget: "GitHub.cli", choco: "gh", scoop: "gh" } : { brew: "git", apt: "git", dnf: "git", pacman: "git", apk: "git", winget: "Git.Git", choco: "git", scoop: "git" };
197464
197600
  if (platform2 === "darwin") {
@@ -197503,17 +197639,54 @@ function defaultDeps() {
197503
197639
  mkdirp: (dir) => {
197504
197640
  (0, import_node_fs9.mkdirSync)(dir, { recursive: true });
197505
197641
  },
197506
- log: (msg) => console.warn(msg)
197642
+ log: (msg) => console.warn(msg),
197643
+ installedVersion: async (pkg, prefix) => readInstalledVersion(pkg, prefix)
197507
197644
  };
197508
197645
  }
197509
- async function ensureToolInstalled(cmd, deps = {}) {
197646
+ function readInstalledVersion(pkg, prefix) {
197647
+ const parts = pkg.split("/");
197648
+ for (const mid of [["lib", "node_modules"], ["node_modules"]]) {
197649
+ const file = (0, import_node_path11.join)(prefix, ...mid, ...parts, "package.json");
197650
+ if (!(0, import_node_fs9.existsSync)(file)) continue;
197651
+ try {
197652
+ const v2 = JSON.parse((0, import_node_fs9.readFileSync)(file, "utf8")).version;
197653
+ if (typeof v2 === "string" && v2) return v2;
197654
+ } catch {
197655
+ }
197656
+ }
197657
+ return null;
197658
+ }
197659
+ async function alignToWantedVersion(cmd, wanted, d) {
197660
+ if (alignedVersion.get(cmd) === wanted) return;
197661
+ const pkg = NPM_TOOL_PACKAGES[cmd];
197662
+ if (!pkg) return;
197663
+ const installed = await d.installedVersion(pkg, d.toolsPrefix);
197664
+ if (!installed) return;
197665
+ if (installed === wanted) {
197666
+ alignedVersion.set(cmd, wanted);
197667
+ return;
197668
+ }
197669
+ const npm = d.platform === "win32" ? "npm.cmd" : "npm";
197670
+ d.log(`[connector-install] ${cmd} ${installed} \u2192 ${wanted}\uFF08\u670D\u52A1\u7AEF\u767B\u8BB0\u7248\u672C\uFF09\uFF0C\u5B89\u88C5\u4E2D\u2026`);
197671
+ try {
197672
+ d.mkdirp(d.toolsPrefix);
197673
+ await d.run(npm, ["install", "-g", "--prefix", d.toolsPrefix, `${pkg}@${wanted}`]);
197674
+ alignedVersion.set(cmd, wanted);
197675
+ d.log(`[connector-install] ${cmd} \u5DF2\u5BF9\u9F50\u5230 ${wanted}`);
197676
+ } catch (err) {
197677
+ d.log(`[connector-install] ${cmd} \u5BF9\u9F50\u5230 ${wanted} \u5931\u8D25\uFF0C\u7EE7\u7EED\u7528 ${installed}\uFF1A${String(err)}`);
197678
+ }
197679
+ }
197680
+ async function ensureToolInstalled(cmd, opts = {}) {
197681
+ const { versions, ...deps } = opts;
197510
197682
  const d = { ...defaultDeps(), ...deps };
197511
- if (confirmed.has(cmd)) return true;
197512
- if (await d.isPresent(cmd)) {
197683
+ const wanted = versions?.[cmd];
197684
+ if (confirmed.has(cmd) || await d.isPresent(cmd)) {
197513
197685
  confirmed.add(cmd);
197686
+ if (wanted) await alignToWantedVersion(cmd, wanted, d);
197514
197687
  return true;
197515
197688
  }
197516
- const steps = installPlan(cmd, d.platform, d.toolsPrefix);
197689
+ const steps = installPlan(cmd, d.platform, d.toolsPrefix, wanted);
197517
197690
  if (steps.length === 0) {
197518
197691
  d.log(`[connector-install] ${cmd} \u7F3A\u5931\uFF0C\u4E14 ${d.platform} \u4E0B\u65E0\u5DF2\u77E5\u81EA\u52A8\u5B89\u88C5\u6CD5\u2014\u2014\u8BF7\u624B\u52A8\u5B89\u88C5`);
197519
197692
  return false;
@@ -197526,6 +197699,7 @@ async function ensureToolInstalled(cmd, deps = {}) {
197526
197699
  await d.run(step.file, step.args);
197527
197700
  if (await d.isPresent(cmd)) {
197528
197701
  confirmed.add(cmd);
197702
+ if (wanted) alignedVersion.set(cmd, wanted);
197529
197703
  d.log(`[connector-install] ${cmd} \u5B89\u88C5\u6210\u529F\uFF08${step.file} ${step.args.join(" ")}\uFF09`);
197530
197704
  return true;
197531
197705
  }
@@ -197536,15 +197710,15 @@ async function ensureToolInstalled(cmd, deps = {}) {
197536
197710
  d.log(`[connector-install] ${cmd} \u81EA\u52A8\u5B89\u88C5\u672A\u6210\u529F\u2014\u2014\u5C06\u56DE\u9000\u5230 wrapper/\u547D\u4EE4\u81EA\u8EAB\u62A5\u9519`);
197537
197711
  return false;
197538
197712
  }
197539
- async function ensureConnectorTools(tools, deps = {}) {
197713
+ async function ensureConnectorTools(tools, opts = {}) {
197540
197714
  if (!tools || tools.length === 0) return [];
197541
197715
  const missing = [];
197542
197716
  for (const cmd of tools) {
197543
- if (!await ensureToolInstalled(cmd, deps)) missing.push(cmd);
197717
+ if (!await ensureToolInstalled(cmd, opts)) missing.push(cmd);
197544
197718
  }
197545
197719
  return missing;
197546
197720
  }
197547
- var import_node_child_process12, import_node_util2, import_node_fs9, import_node_path11, execFileAsync, confirmed;
197721
+ var import_node_child_process12, import_node_util2, import_node_fs9, import_node_path11, execFileAsync, NPM_TOOL_PACKAGES, confirmed, alignedVersion;
197548
197722
  var init_install = __esm({
197549
197723
  "../connectors/src/_base/install.ts"() {
197550
197724
  "use strict";
@@ -197554,7 +197728,9 @@ var init_install = __esm({
197554
197728
  import_node_path11 = require("node:path");
197555
197729
  init_tool_home();
197556
197730
  execFileAsync = (0, import_node_util2.promisify)(import_node_child_process12.execFile);
197731
+ NPM_TOOL_PACKAGES = { "lark-cli": "@larksuite/cli" };
197557
197732
  confirmed = /* @__PURE__ */ new Set();
197733
+ alignedVersion = /* @__PURE__ */ new Map();
197558
197734
  }
197559
197735
  });
197560
197736
 
@@ -201625,7 +201801,7 @@ var init_connector_adapter = __esm({
201625
201801
  async spawn(job) {
201626
201802
  const t0 = Date.now();
201627
201803
  if (job.requiredTools?.length) {
201628
- const missing = await ensureConnectorTools(job.requiredTools);
201804
+ const missing = await ensureConnectorTools(job.requiredTools, { versions: job.requiredToolVersions });
201629
201805
  if (missing.length) log("[adapter]", ` connector tools still missing (agent may fail): ${missing.join(", ")}`);
201630
201806
  }
201631
201807
  const prepared = await prepareConnectorsForJob(job);
@@ -201746,7 +201922,7 @@ async function draftActorResolver(registry2) {
201746
201922
  const byId = new Map(actors.map((a) => [a.id, a]));
201747
201923
  return (id) => {
201748
201924
  const a = byId.get(id);
201749
- return a ? { name: a.name, ...a.roles[0] ? { role: a.roles[0] } : {}, ...a.avatar ? { avatar: a.avatar } : {} } : void 0;
201925
+ return a ? actorRefFieldsOf(a) : systemActorFallback(id);
201750
201926
  };
201751
201927
  }
201752
201928
  async function draftRoleStaffingResolver(registry2) {
@@ -203289,13 +203465,22 @@ async function startOasisServer(opts) {
203289
203465
  resolveStore: opts.delegationStoreFor,
203290
203466
  resolveChatSessions: resolveChatSessionsForDelegation,
203291
203467
  resolveActors: async (companyId) => (await actorsDomain2.resolveCtx(companyId)).service,
203292
- // **派发完顺手把子会话这一轮登记进 live 注册表**(v2 bug 0088)。缺这一步时下面那行
203293
- // `appendToChild` 恒回 `no-live-turn`——注册表里从来没有以 `childSessionId` 为键的轮,
203294
- // 于是每一条 `--continue` 转达都落队列,专家要等本轮跑完才看见「手上这段作废」。
203295
- // 理由与两条纪律见 `child-live-turn.ts` 的文件头。
203468
+ /* **派发完顺手把子会话这一轮登记进 live 注册表,并给它接上 `chat_items` 账本。**
203469
+ 两件事的成因不同,都在 `child-live-turn.ts` 的文件头:
203470
+ · 不登记 → 下面那行 `appendToChild` 恒回 `no-live-turn`,每一条 `--follow-up` 转达
203471
+ 都落队列,专家要等本轮跑完才看见「手上这段作废」(v2 bug 0088);
203472
+ · 不接账本 → 子会话在 `chat_items` 里只有 storage 层镜像来的一问一答两行纯文字,
203473
+ 右栏面板只能靠详情接口拿 run_id 去轨迹表**现折**,跑的过程中一片空白。 */
203296
203474
  dispatchChat: async (request2) => {
203297
203475
  const session = await dispatchChat(request2);
203298
- registerDelegatedChildTurn(liveChat, request2.chatSessionId, session);
203476
+ const itemStore = opts.resolveChatItems ? await opts.resolveChatItems(request2.companyId).catch(() => void 0) : opts.chatItems;
203477
+ const versionSeed = itemStore ? await itemStore.sessionVersionCursor(request2.chatSessionId).catch(() => 0) : 0;
203478
+ registerDelegatedChildTurn(liveChat, request2.chatSessionId, session, {
203479
+ ...itemStore ? { items: itemStore } : {},
203480
+ ...request2.chatTurnId ? { turnId: request2.chatTurnId } : {},
203481
+ ...request2.assistantMessageId ? { assistantMessageId: request2.assistantMessageId } : {},
203482
+ versionSeed
203483
+ });
203299
203484
  return session;
203300
203485
  },
203301
203486
  appendToChild: (childSessionId, text5, extra) => liveChat.append(childSessionId, text5, extra),
@@ -204070,6 +204255,7 @@ async function startOasisServer(opts) {
204070
204255
  const rawBody = Buffer.concat(chunks).toString("utf8");
204071
204256
  const companyCtx = opts.resolveCompanyContext ? await opts.resolveCompanyContext(actor, req.headers, url.pathname) : void 0;
204072
204257
  const currentCompanyId = companyCtx?.kind === "ok" ? companyCtx.companyId : void 0;
204258
+ const connectorActorsService = async (companyId) => opts.actors ? (await opts.actors.resolveCtx(companyId ?? currentCompanyId)).service : void 0;
204073
204259
  const wodrafts = opts.resolveWorkorderDrafts ? await opts.resolveWorkorderDrafts(currentCompanyId) : opts.workorderDrafts;
204074
204260
  const wodraftPlannerIssues = (opts.resolveWorkorderDraftPlannerIssues && await opts.resolveWorkorderDraftPlannerIssues(currentCompanyId)) ?? opts.workorderDraftPlannerIssues ?? defaultDraftPlannerIssuesStore;
204075
204261
  const engine = await resolveEngine(currentCompanyId);
@@ -206133,12 +206319,12 @@ ${composed}`;
206133
206319
  const actorCtx = await opts.resolveActorContext(body2.actorId).catch(() => null);
206134
206320
  if (actorCtx) {
206135
206321
  const { mkdtempSync: mkdtempSync6, mkdirSync: mkdirSync25, writeFileSync: writeFileSync18 } = await import("node:fs");
206136
- const { join: join39, dirname: dirname36 } = await import("node:path");
206322
+ const { join: join40, dirname: dirname36 } = await import("node:path");
206137
206323
  const { tmpdir: tmpdir12 } = await import("node:os");
206138
- const dir = mkdtempSync6(join39(tmpdir12(), "oasis-chat-"));
206324
+ const dir = mkdtempSync6(join40(tmpdir12(), "oasis-chat-"));
206139
206325
  if (actorCtx.config?.prompt) {
206140
206326
  for (const [rel, content3] of Object.entries(splitIdentityFiles2(actorCtx.config.prompt))) {
206141
- const file = join39(dir, rel);
206327
+ const file = join40(dir, rel);
206142
206328
  mkdirSync25(dirname36(file), { recursive: true });
206143
206329
  writeFileSync18(file, content3);
206144
206330
  }
@@ -206150,12 +206336,12 @@ ${composed}`;
206150
206336
  const s2 = byId.get(id);
206151
206337
  return s2 ? `- **${s2.name}** (\`${s2.id}\`): ${s2.description}` : `- \`${id}\`\uFF08\u672A\u5728\u6280\u80FD\u5E93\u4E2D\uFF0C\u53EF\u80FD\u5DF2\u5378\u8F7D\uFF09`;
206152
206338
  });
206153
- writeFileSync18(join39(dir, "SKILLS.md"), ["# \u53EF\u7528\u6280\u80FD", "", "\u4EE5\u4E0B\u6280\u80FD\u5DF2\u4E3A\u4F60\u542F\u7528\uFF0C\u53EF\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u4F7F\u7528\uFF1A", "", ...lines].join("\n"));
206339
+ writeFileSync18(join40(dir, "SKILLS.md"), ["# \u53EF\u7528\u6280\u80FD", "", "\u4EE5\u4E0B\u6280\u80FD\u5DF2\u4E3A\u4F60\u542F\u7528\uFF0C\u53EF\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u4F7F\u7528\uFF1A", "", ...lines].join("\n"));
206154
206340
  }
206155
206341
  if (opts.materializeSkills) {
206156
206342
  const skillFiles = await opts.materializeSkills(body2.actorId, "claude").catch(() => ({}));
206157
206343
  for (const [rel, content3] of Object.entries(skillFiles)) {
206158
- const file = join39(dir, rel);
206344
+ const file = join40(dir, rel);
206159
206345
  mkdirSync25(dirname36(file), { recursive: true });
206160
206346
  writeFileSync18(file, content3);
206161
206347
  }
@@ -206169,7 +206355,7 @@ ${composed}`;
206169
206355
  const modeNote = c.mode === "oauth" ? "OAuth \xB7 \u51ED\u8BC1\u7531\u5E73\u53F0\u7BA1\u7406\uFF0C\u901A\u8FC7\u5BF9\u5E94 CLI wrapper \u8C03\u7528" : "\u76F4\u63A5\u5199\u5165 \xB7 \u51ED\u8BC1\u5DF2\u6CE8\u5165\u73AF\u5883\u53D8\u91CF";
206170
206356
  return `- **${c.name}** (\`${c.id}\`): ${statusNote} \xB7 ${modeNote}`;
206171
206357
  });
206172
- writeFileSync18(join39(dir, "CONNECTORS.md"), ["# \u53EF\u7528\u8FDE\u63A5\u5668", "", "\u4EE5\u4E0B\u8FDE\u63A5\u5668\u5DF2\u4E3A\u672C\u6B21\u4F1A\u8BDD\u914D\u7F6E\uFF0C\u51ED\u8BC1\u5DF2\u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u6216 CLI wrapper \u6CE8\u5165\uFF0C\u65E0\u9700\u624B\u52A8\u914D\u7F6E\uFF1A", "", ...lines].join("\n"));
206358
+ writeFileSync18(join40(dir, "CONNECTORS.md"), ["# \u53EF\u7528\u8FDE\u63A5\u5668", "", "\u4EE5\u4E0B\u8FDE\u63A5\u5668\u5DF2\u4E3A\u672C\u6B21\u4F1A\u8BDD\u914D\u7F6E\uFF0C\u51ED\u8BC1\u5DF2\u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u6216 CLI wrapper \u6CE8\u5165\uFF0C\u65E0\u9700\u624B\u52A8\u914D\u7F6E\uFF1A", "", ...lines].join("\n"));
206173
206359
  }
206174
206360
  spawnCwd = dir;
206175
206361
  }
@@ -206254,7 +206440,7 @@ ${composed}`;
206254
206440
  }
206255
206441
  if (req.method === "POST" && /^\/api\/connectors\/[^/]+\/disconnect$/.test(url.pathname)) {
206256
206442
  const connId = decodeURIComponent(url.pathname.split("/")[3] ?? "");
206257
- const svc = opts.actors?.service;
206443
+ const svc = await connectorActorsService();
206258
206444
  if (!svc) {
206259
206445
  res.writeHead(501).end(JSON.stringify({ error: "actors service disabled" }));
206260
206446
  return;
@@ -206280,7 +206466,7 @@ ${composed}`;
206280
206466
  }
206281
206467
  if (req.method === "DELETE" && /^\/api\/connectors\/[^/]+$/.test(url.pathname)) {
206282
206468
  const connId = decodeURIComponent(url.pathname.slice("/api/connectors/".length));
206283
- const svc = opts.actors?.service;
206469
+ const svc = await connectorActorsService();
206284
206470
  if (!svc) {
206285
206471
  res.writeHead(501).end(JSON.stringify({ error: "actors service disabled" }));
206286
206472
  return;
@@ -206299,6 +206485,31 @@ ${composed}`;
206299
206485
  }
206300
206486
  return;
206301
206487
  }
206488
+ if (req.method === "DELETE" && /^\/api\/actors\/[^/]+\/connectors\/[^/]+$/.test(url.pathname)) {
206489
+ const parts = url.pathname.split("/");
206490
+ const actorId = decodeURIComponent(parts[3] ?? "");
206491
+ const connId = decodeURIComponent(parts[5] ?? "");
206492
+ const svc = await connectorActorsService();
206493
+ if (!svc) {
206494
+ res.writeHead(501).end(JSON.stringify({ error: "actors service disabled" }));
206495
+ return;
206496
+ }
206497
+ try {
206498
+ const { variablesDeleted } = await svc.deleteActorConnector(actorId, connId);
206499
+ let channelDisabled = false;
206500
+ if (connId === "feishu" && channelService) {
206501
+ const b2 = await channelService.getBindingForActor(actorId);
206502
+ if (b2 && b2.status !== "revoked") {
206503
+ await channelService.disable(actorId);
206504
+ channelDisabled = true;
206505
+ }
206506
+ }
206507
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, variablesDeleted, channelDisabled }));
206508
+ } catch (e) {
206509
+ res.writeHead(500).end(JSON.stringify({ error: String(e) }));
206510
+ }
206511
+ return;
206512
+ }
206302
206513
  if (url.pathname === "/api/connectors/feishu/setup" && req.method === "GET") {
206303
206514
  res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
206304
206515
  const send = (obj2) => res.write(`data: ${JSON.stringify(obj2)}
@@ -206348,8 +206559,8 @@ ${composed}`;
206348
206559
  if (!errStr && pollData["client_id"]) {
206349
206560
  const clientId = String(pollData["client_id"]);
206350
206561
  const clientSecret = pollData["client_secret"] ? String(pollData["client_secret"]) : void 0;
206351
- if (opts.actors && clientSecret) {
206352
- const svc = opts.actors.service;
206562
+ const svc = clientSecret ? await connectorActorsService() : void 0;
206563
+ if (svc && clientSecret) {
206353
206564
  const credScope = actorIdParam ? { scope: "personal", actorId: actorIdParam } : { scope: "global" };
206354
206565
  await svc.putVariable({ key: "FEISHU_APP_ID", value: clientId, ...credScope, connectorId: "feishu", encrypted: true });
206355
206566
  await svc.putVariable({ key: "FEISHU_APP_SECRET", value: clientSecret, ...credScope, connectorId: "feishu", encrypted: true });
@@ -206412,6 +206623,8 @@ ${composed}`;
206412
206623
  });
206413
206624
  githubAppPending.put(state, {
206414
206625
  ...actorId ? { actorId } : {},
206626
+ // 回调那一步没有鉴权头,解析不出公司——只能在这里记下来带过去。
206627
+ ...currentCompanyId ? { companyId: currentCompanyId } : {},
206415
206628
  employeeSlug: nameSlug,
206416
206629
  createdAtMs: Date.now(),
206417
206630
  // 存下这次选的归属——回调时要拿它跟 GitHub 返回的 owner 比对。
@@ -206432,7 +206645,7 @@ ${composed}`;
206432
206645
  if (url.pathname === "/api/connectors/github/app/orgs" && req.method === "POST") {
206433
206646
  try {
206434
206647
  const { actorId } = JSON.parse(rawBody || "{}");
206435
- const svc = opts.actors?.service;
206648
+ const svc = await connectorActorsService();
206436
206649
  if (!svc) throw new Error("actors service unavailable");
206437
206650
  const token = await svc.revealResolvedVariable("GITHUB_TOKEN", actorId);
206438
206651
  if (!token) {
@@ -206465,7 +206678,7 @@ ${composed}`;
206465
206678
  }
206466
206679
  if (url.pathname === "/api/connectors/github/app/available" && req.method === "POST") {
206467
206680
  try {
206468
- const svc = opts.actors?.service;
206681
+ const svc = await connectorActorsService();
206469
206682
  if (!svc) throw new Error("actors service unavailable");
206470
206683
  const rows = await svc.listVariables();
206471
206684
  const plain = (key, actorId) => rows.find((r) => r.key === key && r.actorId === actorId && !r.encrypted)?.maskedValue ?? "";
@@ -206528,7 +206741,7 @@ ${composed}`;
206528
206741
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "\u7F3A\u5C11 appId" }));
206529
206742
  return;
206530
206743
  }
206531
- const svc = opts.actors?.service;
206744
+ const svc = await connectorActorsService();
206532
206745
  if (!svc) throw new Error("actors service unavailable");
206533
206746
  const rows = await svc.listVariables();
206534
206747
  const owners = rows.filter((r) => r.key === "GITHUB_APP_ID" && !r.encrypted && r.maskedValue === appId);
@@ -206575,7 +206788,7 @@ ${composed}`;
206575
206788
  bad(400, "\u8FD9\u4E0D\u50CF\u4E00\u4E2A\u79C1\u94A5\u6587\u4EF6\u2014\u2014\u8BF7\u4E0A\u4F20 GitHub \u4E0B\u8F7D\u7684 .pem\uFF08\u5185\u5BB9\u4EE5 -----BEGIN ... PRIVATE KEY----- \u5F00\u5934\uFF09");
206576
206789
  return;
206577
206790
  }
206578
- const svc = opts.actors?.service;
206791
+ const svc = await connectorActorsService();
206579
206792
  if (!svc) throw new Error("actors service unavailable");
206580
206793
  let meta;
206581
206794
  try {
@@ -206633,7 +206846,7 @@ ${composed}`;
206633
206846
  if (url.pathname === "/api/connectors/github/app/permissions" && req.method === "POST") {
206634
206847
  try {
206635
206848
  const { actorId } = JSON.parse(rawBody || "{}");
206636
- const svc = opts.actors?.service;
206849
+ const svc = await connectorActorsService();
206637
206850
  if (!svc) throw new Error("actors service unavailable");
206638
206851
  const appId = await svc.revealResolvedVariable("GITHUB_APP_ID", actorId);
206639
206852
  const privateKeyPem = await svc.revealResolvedVariable("GITHUB_APP_PRIVATE_KEY", actorId);
@@ -206687,7 +206900,7 @@ ${composed}`;
206687
206900
  fail(problem.message);
206688
206901
  return;
206689
206902
  }
206690
- const svc = opts.actors?.service;
206903
+ const svc = await connectorActorsService(pending.companyId);
206691
206904
  if (!svc) throw new Error("actors service unavailable");
206692
206905
  const scope = pending.actorId ? { scope: "personal", actorId: pending.actorId } : { scope: "global" };
206693
206906
  const put3 = (key, value2, encrypted) => svc.putVariable({ key, value: value2, ...scope, connectorId: "github", encrypted });
@@ -206756,7 +206969,7 @@ ${composed}`;
206756
206969
  reply({ ok: false, error: "pending" });
206757
206970
  return;
206758
206971
  }
206759
- const svc = opts.actors?.service;
206972
+ const svc = await connectorActorsService();
206760
206973
  if (!svc) throw new Error("actors service unavailable");
206761
206974
  const scope = pending.actorId ? { scope: "personal", actorId: pending.actorId } : { scope: "global" };
206762
206975
  const put3 = (key, value2, encrypted) => svc.putVariable({ key, value: value2, ...scope, connectorId: "github", encrypted });
@@ -206845,8 +207058,8 @@ ${composed}`;
206845
207058
  throw new Error(String(data["error_description"] ?? errStr ?? "login failed"));
206846
207059
  }
206847
207060
  if (!data["access_token"]) throw new Error("no access_token in response");
206848
- if (opts.actors) {
206849
- const svc = opts.actors.service;
207061
+ const svc = await connectorActorsService();
207062
+ if (svc) {
206850
207063
  if (actorId) {
206851
207064
  await svc.putVariable({ key: "FEISHU_APP_ID", value: appId, scope: "personal", actorId, connectorId: "feishu", encrypted: true });
206852
207065
  } else {
@@ -206936,7 +207149,16 @@ ${composed}`;
206936
207149
  body: JSON.stringify({ client_id: GH_CLIENT_ID, scope: "repo,read:user,read:org" })
206937
207150
  });
206938
207151
  const d = await dr.json();
206939
- send({ type: "code", code: d.user_code, url: d.verification_uri });
207152
+ const verifyUrl = (() => {
207153
+ try {
207154
+ const u = new URL(d.verification_uri);
207155
+ u.searchParams.set("user_code", d.user_code);
207156
+ return u.toString();
207157
+ } catch {
207158
+ return d.verification_uri;
207159
+ }
207160
+ })();
207161
+ send({ type: "code", code: d.user_code, url: verifyUrl });
206940
207162
  const deadline = Date.now() + d.expires_in * 1e3;
206941
207163
  while (!closed && Date.now() < deadline) {
206942
207164
  await new Promise((r) => setTimeout(r, d.interval * 1e3));
@@ -206947,8 +207169,8 @@ ${composed}`;
206947
207169
  });
206948
207170
  const p2 = await pr.json();
206949
207171
  if (p2.access_token) {
206950
- if (opts.actors) {
206951
- const svc = opts.actors.service;
207172
+ const svc = await connectorActorsService();
207173
+ if (svc) {
206952
207174
  const connId = "github";
206953
207175
  await svc.upsertConnector({ id: connId, name: "GitHub", mode: "oauth", status: "connected", account: "github" });
206954
207176
  for (const [k2, v2] of [["GIT_AUTHOR_NAME", actorNameParam], ["GIT_AUTHOR_EMAIL", ""], ["GIT_COMMITTER_NAME", actorNameParam], ["GIT_COMMITTER_EMAIL", ""], ["EMAIL", ""]])
@@ -207051,7 +207273,7 @@ ${composed}`;
207051
207273
  * **接进既有那一拍,不新写一套扫描**——新写的那套迟早和既有判据漂移。
207052
207274
  * 委派域没装配时返回 null,调用方照常。
207053
207275
  */
207054
- settleDelegationFromSweep: (childSessionId, reason, companyId) => delegationService?.settleFromSweep(childSessionId, reason, companyId) ?? Promise.resolve(null),
207276
+ settleDelegationFromSweep: (childSessionId, reason, companyId, opts2) => delegationService?.settleFromSweep(childSessionId, reason, companyId, opts2) ?? Promise.resolve(null),
207055
207277
  close: async () => {
207056
207278
  if (broadcastTimer) clearInterval(broadcastTimer);
207057
207279
  if (pendingSystemNoticesRecoveryTimer) clearInterval(pendingSystemNoticesRecoveryTimer);
@@ -218233,9 +218455,32 @@ var init_service5 = __esm({
218233
218455
  const a = await this.opts.store.getActor(id);
218234
218456
  if (!a) throw new Error(`actor not found: ${id}`);
218235
218457
  await this.upsertActor({ ...a, status: "disabled" }, by);
218458
+ await this.dropRuntimeBindingOfDeletedAgent(a.id, a.kind, by);
218236
218459
  if (a.status === "active") await this.opts.onActorDisabled?.(id, by);
218237
218460
  return a;
218238
218461
  }
218462
+ /**
218463
+ * 删掉 Agent 时把它跟 runtime 的绑定一起撤掉。
218464
+ *
218465
+ * 为什么必须做(2026-09-09 线上实测):删除只写 `status='disabled'`,`actor_bindings` 那行
218466
+ * 原样留着且仍是 `active`。而 `GET /api/actors` 会把已删 Agent 过滤掉、`GET /api/bindings` 不会——
218467
+ * 运行时管理页拿两份数据对着算「运行 Agent」,于是已删的 Agent 继续占着一格,
218468
+ * 名字还查不回来,页面上直接露出 `asst-xxx-cbf34f` 这种裸 id。didi 组织实测:yx_claude 上
218469
+ * 12 条 active 绑定里 7 条的 Agent 早被删了(其中 5 条是助理)。
218470
+ *
218471
+ * 顺带修好另一处:`pickAssistantRuntime` 用「(nodeId,kind) 上的 active 绑定数」当负载做均衡,
218472
+ * 已删 Agent 的残留绑定会把负载算高,把新助理往别的机器上赶。
218473
+ *
218474
+ * 只对 agent 生效:真人被移出组织是「离岗」不是删除,PRD 明确其助理照常在岗、账号回来还能接上。
218475
+ * 幂等:removeBinding 对没有绑定的 actor 是 no-op,所以重复删也安全(存量脏数据也能靠再删一次修好)。
218476
+ */
218477
+ async dropRuntimeBindingOfDeletedAgent(id, kind, by) {
218478
+ if (kind !== "agent") return;
218479
+ const existing = await this.opts.store.getBinding(id);
218480
+ if (!existing) return;
218481
+ await this.opts.store.removeBinding(id);
218482
+ await this.audit({ kind: "binding_change", actorId: id, by, at: this.now(), detail: { removed: true, reason: "agent_deleted" } });
218483
+ }
218239
218484
  /**
218240
218485
  * 原子「若当前 active 则停用」(QA R10):与 disableActor 的区别在于——
218241
218486
  * 只在 actor 当前**确为 active** 时才写,返回 `ok:true + before + seq`;已被并发者/别的路径先 disable
@@ -218259,6 +218504,7 @@ var init_service5 = __esm({
218259
218504
  if (!res.ok) return res;
218260
218505
  this.opts.onRolesChanged?.(id, []);
218261
218506
  await this.audit({ kind: "actor_upsert", actorId: id, by, at: this.now(), detail: { status: "disabled", roles: res.before.roles } });
218507
+ await this.dropRuntimeBindingOfDeletedAgent(id, res.before.kind, by);
218262
218508
  await this.opts.onActorDisabled?.(id, by);
218263
218509
  return { ok: true, before: res.before, seq };
218264
218510
  }
@@ -218859,39 +219105,42 @@ ${input.description}
218859
219105
  const c = cfg ?? await this.opts.store.latestConfig(actorId);
218860
219106
  return new Set(c?.connectorIds ?? []);
218861
219107
  }
218862
- /** 设置某员工×连接器的连接记录(启用/停用)。无则创建,有则翻转 enabled。 */
218863
- async setActorConnectorEnabled(actorId, connectorId, enabled) {
218864
- const conn = { actorId, connectorId, enabled, updatedAt: this.now() };
219108
+ /**
219109
+ * 设置某员工×连接器的连接记录(启用/停用)。无则创建,有则翻转 enabled
219110
+ *
219111
+ * `configuredBy` 只在**授权流程走完**时传(「谁为这名员工把它接上的」)。普通开关不传,
219112
+ * 此时把已有值原样带回——**只增不抹**:翻一次开关不该把授权时记下的人擦掉。
219113
+ * pg 侧另有 `COALESCE` 兜同一条,两层都做是因为内存 store 是整行替换。
219114
+ */
219115
+ async setActorConnectorEnabled(actorId, connectorId, enabled, configuredBy) {
219116
+ const keep = configuredBy ?? (await this.opts.store.listActorConnectorConnections(actorId)).find((c) => c.connectorId === connectorId)?.configuredBy;
219117
+ const conn = {
219118
+ actorId,
219119
+ connectorId,
219120
+ enabled,
219121
+ updatedAt: this.now(),
219122
+ ...keep ? { configuredBy: keep } : {}
219123
+ };
218865
219124
  await this.opts.store.upsertActorConnectorConnection(conn);
218866
219125
  return conn;
218867
219126
  }
218868
- listActorConnectorConnections(actorId) {
218869
- return this.opts.store.listActorConnectorConnections(actorId);
218870
- }
218871
219127
  /**
218872
- * 移除某员工与某连接器的连接(组织页连接器详情的「移除连接」,2026-09-09 原型 1600:5765)。
218873
- *
218874
- * 删两样东西,缺一不可:
218875
- * ① 这名员工**自己**那份连接器凭据(`scope=personal` 且 `connectorId` 命中的变量行)——
218876
- * 不删的话,「移除」之后 `actorConnected` 里还有它,卡片照样在,读作「删不掉」;
218877
- * ② 「员工×连接器」的连接记录——不删的话 `effective` 里还有它(enabled=false),
218878
- * 卡片变成一张「已停用」的僵尸卡,而人要的是它消失。
219128
+ * 删除**一条**「员工 × 连接器」连接:连接记录 + 该员工这个连接器的**个人凭据变量**。
218879
219129
  *
218880
- * **不碰组织那条 connector 行**(决策 0080 修订五的同一条理由):组织级连接是别人配的资产,
218881
- * 一名员工点「移除连接」不该把全组织的连接拆掉。组织级的断开在「管理 > 连接器」。
218882
- * 于是:组织已连接时,移除个人连接后这名员工会**回落到组织默认**——卡片仍在,但身份那一行
218883
- * 变回组织账号。这是对的,不是没删干净。
219130
+ * 与开关的区别(发起人 2026-09-09 定的,两颗按钮并存):开关是「暂时不用」,凭据留着、
219131
+ * 打开就能接着用;删除是「不要了」,凭据清掉、再要用得重走一遍授权。
218884
219132
  *
218885
- * 幂等:没有个人凭据、没有记录时照样返回成功(`variablesDeleted: 0`)——重复点、并发点都不该报错。
219133
+ * **只动这名员工自己的东西**:组织级变量(scope=global)一个不碰——那是别人也在用的。
219134
+ * 飞书对话通道的下线不在这里做(本服务够不到 channelService),由路由层在删完凭据后补一刀。
218886
219135
  */
218887
- async removeActorConnector(actorId, connectorId) {
218888
- const rows = await this.opts.store.listVariables(actorId);
218889
- const mine = rows.filter(
218890
- (v2) => v2.scope === "personal" && v2.actorId === actorId && v2.connectorId === connectorId
218891
- );
218892
- for (const v2 of mine) await this.opts.store.deleteVariable(v2.key, actorId);
219136
+ async deleteActorConnector(actorId, connectorId) {
219137
+ const personal = (await this.opts.store.listVariables(actorId)).filter((v2) => v2.scope === "personal" && v2.actorId === actorId && v2.connectorId === connectorId);
219138
+ for (const v2 of personal) await this.deleteVariable(v2.key, actorId);
218893
219139
  await this.opts.store.deleteActorConnectorConnection(actorId, connectorId);
218894
- return { removed: true, variablesDeleted: mine.length };
219140
+ return { variablesDeleted: personal.length };
219141
+ }
219142
+ listActorConnectorConnections(actorId) {
219143
+ return this.opts.store.listActorConnectorConnections(actorId);
218895
219144
  }
218896
219145
  /**
218897
219146
  * 员工连接器面板所需状态:连接记录 + 组织级已连接集合 + 该员工已授权(有个人 connector 变量)集合。
@@ -220860,14 +221109,9 @@ function actorsDomain(opts) {
220860
221109
  const { service } = await resolveCtx(req.auth.companyId);
220861
221110
  const b2 = req.body;
220862
221111
  if (typeof b2?.enabled !== "boolean") throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 enabled (boolean)");
220863
- const conn = await service.setActorConnectorEnabled(req.params.id, req.params.connectorId, b2.enabled);
221112
+ const conn = b2.enabled ? await service.setActorConnectorEnabled(req.params.id, req.params.connectorId, true, req.auth.actor) : await service.setActorConnectorEnabled(req.params.id, req.params.connectorId, false);
220864
221113
  return { status: 200, body: conn };
220865
221114
  });
220866
- router.delete("/api/actors/:id/connectors/:connectorId", async (req) => {
220867
- const { service } = await resolveCtx(req.auth.companyId);
220868
- const r = await service.removeActorConnector(req.params.id, req.params.connectorId);
220869
- return { status: 200, body: r };
220870
- });
220871
221115
  const requireVariableManager = (req) => {
220872
221116
  if (!isHumanActor(req.auth.actor)) {
220873
221117
  throw new ApiError(
@@ -226090,7 +226334,13 @@ function reviewSummary(snap, reviews, events) {
226090
226334
  const name = node2?.title ?? first.nodeId;
226091
226335
  const latest = latestActivityReviews(reviews, events);
226092
226336
  const requirements = snap.requirements.filter((r) => r.nodeId === first.nodeId && r.reviewerActorId === first.reviewerActorId);
226093
- const judgement = judgeWork({ requirements: requirements.length ? requirements : reviews, reviews });
226337
+ for (const requirement of requirements) {
226338
+ const current = reviews.find((r) => r.id === requirement.latestReviewId);
226339
+ if (!current) continue;
226340
+ const index2 = latest.findIndex((r) => r.reviewGroup === requirement.reviewGroup);
226341
+ if (index2 >= 0) latest[index2] = current;
226342
+ }
226343
+ const judgement = judgeWork({ requirements: requirements.length ? requirements : latest, reviews: latest });
226094
226344
  const pending = latest.filter((r) => !r.cancelledAt && !r.verdict && reviewState(r) === "running");
226095
226345
  if (work?.acceptanceState === "accepted" || work?.acceptedAt || work?.acceptanceState !== "rejected" && judgement === "passed") {
226096
226346
  return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
@@ -226126,6 +226376,66 @@ var init_review_activity = __esm({
226126
226376
  }
226127
226377
  });
226128
226378
 
226379
+ // ../server/src/domains/collab/review-requirement-activity.ts
226380
+ function reviewRequirementActivity(snap, events, ref2) {
226381
+ const previous3 = /* @__PURE__ */ new Map();
226382
+ const titles = new Map(snap.nodes.map((n) => [n.id, n.title]));
226383
+ const cards = [];
226384
+ const label = (r) => `${ref2(r.reviewerActorId).name || "\u672A\u547D\u540D\u5BA1\u6838\u4EBA"}${r.source === "closure" ? "\uFF08\u7ED3\u6848\u5BA1\u6838\uFF09" : ""}`;
226385
+ const names = (rows) => [...new Set(rows.map(label))].join("\u3001") || "\u65E0";
226386
+ for (const rec of [...events].sort((a, b2) => a.seq - b2.seq)) {
226387
+ if (rec.status === "pending" || rec.status === "failed") continue;
226388
+ const e = rec.event;
226389
+ if (e.kind === "plan.changed") {
226390
+ for (const node2 of e.addNodes ?? []) previous3.set(node2.id, node2.reviewers ?? []);
226391
+ for (const id of e.removeNodes ?? []) previous3.delete(id);
226392
+ continue;
226393
+ }
226394
+ if (e.kind === "plan.add_node") {
226395
+ previous3.set(e.nodeId, e.reviewers ?? []);
226396
+ continue;
226397
+ }
226398
+ if (e.kind !== "plan.update_review_requirements") continue;
226399
+ const before = previous3.get(e.nodeId);
226400
+ const after = e.reviewers;
226401
+ const beforeKeys = new Set(before?.map(requirementKey));
226402
+ const afterKeys = new Set(after.map(requirementKey));
226403
+ const added = before ? after.filter((r) => !beforeKeys.has(requirementKey(r))) : [];
226404
+ const removed = before?.filter((r) => !afterKeys.has(requirementKey(r))) ?? [];
226405
+ const nodeTitle = titles.get(e.nodeId) || "\u5DF2\u79FB\u9664\u7684\u8282\u70B9";
226406
+ const system = rec.actorId.startsWith("actor:system:");
226407
+ const closureAdded = system && added.length > 0 && !removed.length && added.every((r) => r.source === "closure");
226408
+ cards.push({
226409
+ id: `wo:${rec.seq}`,
226410
+ seq: rec.seq,
226411
+ at: rec.createdAt,
226412
+ updatedAt: rec.createdAt,
226413
+ nodeId: e.nodeId,
226414
+ nodeTitle,
226415
+ executor: system ? { ...ref2(rec.actorId), name: "\u7CFB\u7EDF" } : ref2(rec.actorId),
226416
+ ...rec.handActorId ? { handActor: ref2(rec.handActorId) } : {},
226417
+ phase: "done",
226418
+ status: closureAdded ? `\u4E3A\u300A${nodeTitle}\u300B\u8865\u5145\u4E86\u7ED3\u6848\u5BA1\u6838\u8981\u6C42\u3002` : `\u66F4\u65B0\u4E86\u300A${nodeTitle}\u300B\u7684\u5BA1\u6838\u5217\u8868\u3002`,
226419
+ detail: [
226420
+ ...added.length ? [`\u65B0\u589E\uFF1A${names(added)}`] : [],
226421
+ ...removed.length ? [`\u79FB\u9664\uFF1A${names(removed)}`] : [],
226422
+ `\u66F4\u65B0\u540E\uFF1A${names(after)}`
226423
+ ].join("\n"),
226424
+ artifacts: [],
226425
+ actions: []
226426
+ });
226427
+ previous3.set(e.nodeId, after);
226428
+ }
226429
+ return cards;
226430
+ }
226431
+ var requirementKey;
226432
+ var init_review_requirement_activity = __esm({
226433
+ "../server/src/domains/collab/review-requirement-activity.ts"() {
226434
+ "use strict";
226435
+ requirementKey = (r) => JSON.stringify([r.reviewerActorId, r.reviewGroup, r.source]);
226436
+ }
226437
+ });
226438
+
226129
226439
  // ../server/src/domains/collab/workorder-manager.ts
226130
226440
  function resolveWorkorderManager(snap) {
226131
226441
  const nodes = snap.nodes;
@@ -226299,12 +226609,16 @@ function buildWorkorderActivity(input) {
226299
226609
  const reasonKinds = new Set(reasons.map((r) => String(r.kind)));
226300
226610
  const executorId = w2?.assigneeActorId ?? rec.actorId;
226301
226611
  if (isPendingActor(executorId)) break;
226302
- const status = reasonKinds.has("rework") ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : reasonKinds.has("issue") ? ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
226303
- const drivingIssueId = str4(reasons.find((r) => r.kind === "issue")?.issueId);
226304
- if (drivingIssueId) {
226305
- workOfIssue.set(drivingIssueId, workId);
226306
- const ic = plainCommentIssueIds.has(drivingIssueId) ? issueCards.get(drivingIssueId) : void 0;
226307
- const commentAuthor = issueById.get(drivingIssueId)?.authorActorId;
226612
+ const forcedBy = str4(reasons.find((r) => r.kind === "forced")?.actorId);
226613
+ const gapDriven = reasons.some((r) => r.kind === "issue" && r.issueKind === "gap");
226614
+ const status = reasonKinds.has("rework") ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : gapDriven ? ACTIVITY_COPY.gapReworked(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("issue") ? ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : forcedBy ? ACTIVITY_COPY.forcedRerun(nameOf(forcedBy), nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
226615
+ for (const r of reasons) {
226616
+ if (r.kind !== "issue") continue;
226617
+ const issueId = str4(r.issueId);
226618
+ if (!issueId) continue;
226619
+ workOfIssue.set(issueId, workId);
226620
+ const ic = plainCommentIssueIds.has(issueId) ? issueCards.get(issueId) : void 0;
226621
+ const commentAuthor = issueById.get(issueId)?.authorActorId;
226308
226622
  if (ic && commentAuthor) {
226309
226623
  touch(ic, rec);
226310
226624
  ic.executorId = executorId;
@@ -226806,6 +227120,8 @@ function buildWorkorderActivity(input) {
226806
227120
  .../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(traced ? runIdOfCard(d) : void 0)
226807
227121
  };
226808
227122
  });
227123
+ cards.push(...reviewRequirementActivity(snap, events, ref2));
227124
+ cards.sort((a, b2) => a.updatedAt.localeCompare(b2.updatedAt) || a.seq - b2.seq || a.id.localeCompare(b2.id));
226809
227125
  return { workorderId, cards, truncated };
226810
227126
  }
226811
227127
  var ACTIVITY_EVENT_KINDS, ACTIVITY_EVENT_LIMIT, ACTIVITY_COPY, REWORK_ANNOTATION_PREFIX, ENGINE_RETRY_EXHAUSTED, BUTTON;
@@ -226815,9 +227131,12 @@ var init_activity2 = __esm({
226815
227131
  init_src();
226816
227132
  init_src4();
226817
227133
  init_review_activity();
227134
+ init_review_requirement_activity();
226818
227135
  init_workorder_manager();
226819
227136
  ACTIVITY_EVENT_KINDS = [
226820
227137
  "plan.changed",
227138
+ "plan.add_node",
227139
+ "plan.update_review_requirements",
226821
227140
  "plan.update_spec",
226822
227141
  "plan.node_retry",
226823
227142
  "work.create",
@@ -226856,6 +227175,22 @@ var init_activity2 = __esm({
226856
227175
  reworking: (node2) => `\u6B63\u5728\u6839\u636E\u5BA1\u6838\u4E0D\u901A\u8FC7\u539F\u56E0\u4FEE\u6539\u300A${node2}\u300B\u3002`,
226857
227176
  specChanged: (node2) => `\u6B63\u5728\u6839\u636E\u4EFB\u52A1\u8BF4\u660E\u7684\u66F4\u65B0\u8C03\u6574\u300A${node2}\u300B\u3002`,
226858
227177
  issueDriven: (node2) => `\u6B63\u5728\u6839\u636E\u53CD\u9988\u8C03\u6574\u300A${node2}\u300B\u3002`,
227178
+ /**
227179
+ * 缺口解开后**带着答复重开工**的那一轮(`reasons` 里有 `{kind:"issue", issueKind:"gap"}`)。
227180
+ *
227181
+ * 与 {@link issueDriven} 分开:那句说的是「按反馈调整」,而这一轮的事实是**卡点解除、重新开始**
227182
+ * ——中断链上最该看清的一拍。此前两者共用一句,恢复后的那一轮被说回泛泛的「正在根据反馈调整」。
227183
+ * 实测 next 213 次 / 23 单、v2 4 次,最近一次 2026-09-09(`issue` 里第二多的一档)。
227184
+ */
227185
+ gapReworked: (executor, node2) => `\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C${executor} \u5E26\u7740\u7B54\u590D\u91CD\u65B0\u5F00\u5DE5\u300A${node2}\u300B\u3002`,
227186
+ /**
227187
+ * 人点了 UI 的「重新运行」(`kernel-bridge.rerunNode`,ADR 0137)——`reasons` 里那条
227188
+ * `{kind:"forced", actorId}` 就是点的人。
227189
+ *
227190
+ * 此前没有自己的文案、落到 `working` 说成「正在处理《X》。」,**页面上完全看不出这一轮是
227191
+ * 有人手动强推的**。实测两条生产线合计 80 次(next 73 / v2 7,最近一次 2026-09-09)。
227192
+ */
227193
+ forcedRerun: (by, node2) => `${by} \u91CD\u65B0\u8FD0\u884C\u4E86\u300A${node2}\u300B\u3002`,
226859
227194
  /* 自动判定的那一刻**只说事实,不断言谁介入了**。
226860
227195
  `work.timeout` 由引擎发(`engine/activate.ts` 与 `server/run-settlement.ts`),失败后的重试
226861
227196
  也是引擎做的(`activate.ts` 的 `tryToRun`)——管理者根本不知道这两件事,此前却写成
@@ -228045,9 +228380,33 @@ function toAuditEntry(op) {
228045
228380
  summary: `${KIND_LABEL2[op.kind] ?? op.kind} \xB7 ${op.artifactId}`
228046
228381
  };
228047
228382
  }
228383
+ function nodeIdOfWorkId(workId) {
228384
+ if (!workId || !workId.startsWith("wk:")) return null;
228385
+ const body2 = workId.slice(3);
228386
+ const cut = body2.lastIndexOf(":");
228387
+ if (cut <= 0) return null;
228388
+ if (!/^\d+$/.test(body2.slice(cut + 1))) return null;
228389
+ const nodeId = body2.slice(0, cut);
228390
+ return nodeId.startsWith("artifact:") ? nodeId : null;
228391
+ }
228392
+ function nodeIdOfIssueId(issueId, model) {
228393
+ if (!issueId) return null;
228394
+ const anchored = model.annotations.get(issueId)?.anchor.target;
228395
+ if (anchored) {
228396
+ if (anchored.startsWith("artifact:")) return anchored;
228397
+ const fromWork = nodeIdOfWorkId(anchored);
228398
+ if (fromWork) return fromWork;
228399
+ }
228400
+ const body2 = issueId.replace(/^(?:ann:rework:)?(?:gap:|esc:|ann:)/, "");
228401
+ if (!body2.startsWith("artifact:")) return null;
228402
+ const cut = body2.lastIndexOf(":");
228403
+ if (cut <= 0) return null;
228404
+ const nodeId = body2.slice(0, cut);
228405
+ return nodeId.startsWith("artifact:") ? nodeId : null;
228406
+ }
228048
228407
  function eventToAuditEntry(rec) {
228049
228408
  const ev = rec.event;
228050
- const artifactId = rec.nodeId ?? rec.workId ?? "";
228409
+ const artifactId = rec.nodeId ?? nodeIdOfWorkId(rec.workId) ?? "";
228051
228410
  return {
228052
228411
  seq: rec.seq,
228053
228412
  at: rec.createdAt,
@@ -228057,16 +228416,25 @@ function eventToAuditEntry(rec) {
228057
228416
  summary: `${KIND_LABEL2[ev.kind] ?? ev.kind}${artifactId ? ` \xB7 ${artifactId}` : ""}`
228058
228417
  };
228059
228418
  }
228060
- function enrichAudit(entry, model, titleOf) {
228061
- const a = model.artifacts.get(entry.artifactId);
228062
- if (!a) return { ...entry, visibility: "deleted" };
228063
- const workspaceTitle = titleOf.get(a.workspace);
228064
- return {
228065
- ...entry,
228066
- nodeName: nodeLabel(a),
228067
- ...workspaceTitle ? { workspaceTitle } : {},
228068
- visibility: "ok"
228419
+ function auditAnchorsOf(rec) {
228420
+ const issueId = rec.event.issueId;
228421
+ return { workorderId: rec.workorderId, ...typeof issueId === "string" && issueId ? { issueId } : {} };
228422
+ }
228423
+ function enrichAudit(entry, model, titleOf, anchors = {}) {
228424
+ const withNode = (id, a) => {
228425
+ const workspaceTitle2 = titleOf.get(a.workspace);
228426
+ return { ...entry, artifactId: id, nodeName: nodeLabel(a), ...workspaceTitle2 ? { workspaceTitle: workspaceTitle2 } : {}, visibility: "ok" };
228069
228427
  };
228428
+ if (entry.artifactId) {
228429
+ const a = model.artifacts.get(entry.artifactId);
228430
+ if (!a) return { ...entry, visibility: "deleted" };
228431
+ return withNode(entry.artifactId, a);
228432
+ }
228433
+ const viaIssue = nodeIdOfIssueId(anchors.issueId, model);
228434
+ const viaIssueArtifact = viaIssue ? model.artifacts.get(viaIssue) : void 0;
228435
+ if (viaIssue && viaIssueArtifact) return withNode(viaIssue, viaIssueArtifact);
228436
+ const workspaceTitle = anchors.workorderId ? titleOf.get(anchors.workorderId) : void 0;
228437
+ return { ...entry, ...workspaceTitle ? { workspaceTitle } : {}, visibility: "ok" };
228070
228438
  }
228071
228439
  async function buildAudit(source, q2 = {}, model) {
228072
228440
  const limit = Math.min(Math.max(q2.limit ?? 100, 1), 500);
@@ -228075,8 +228443,8 @@ async function buildAudit(source, q2 = {}, model) {
228075
228443
  const items = [];
228076
228444
  let lastCursor = null;
228077
228445
  let truncated = false;
228078
- const push2 = (entry, cursor) => {
228079
- items.push(model && titleOf ? enrichAudit(entry, model, titleOf) : entry);
228446
+ const push2 = (entry, cursor, anchors) => {
228447
+ items.push(model && titleOf ? enrichAudit(entry, model, titleOf, anchors) : entry);
228080
228448
  lastCursor = cursor;
228081
228449
  };
228082
228450
  const match = (actor, kind, at) => {
@@ -228087,30 +228455,33 @@ async function buildAudit(source, q2 = {}, model) {
228087
228455
  return true;
228088
228456
  };
228089
228457
  if (q2.latest) {
228090
- let collected = [];
228458
+ const collected = [];
228091
228459
  if (source.engineStore) {
228092
228460
  const records = await source.engineStore.transaction((tx) => tx.listEvents(void 0, 0));
228093
228461
  for (const rec of records) {
228094
228462
  const ev = rec.event;
228095
228463
  if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
228096
- collected.push(eventToAuditEntry(rec));
228464
+ collected.push({ entry: eventToAuditEntry(rec), anchors: auditAnchorsOf(rec) });
228097
228465
  }
228098
228466
  } else if (source.oplog) {
228099
228467
  const src = source.oplog.readFilteredOps ? source.oplog.readFilteredOps({ ...q2.actor ? { actor: q2.actor } : {}, ...q2.kind ? { kind: q2.kind } : {} }, void 0, { latest: true }) : source.oplog.readAll();
228100
228468
  for await (const { op } of src) {
228101
228469
  if (!match(op.actor, op.kind, op.timestamp)) continue;
228102
- collected.push(toAuditEntry(op));
228470
+ collected.push({ entry: toAuditEntry(op) });
228103
228471
  }
228104
228472
  }
228105
228473
  const tail = collected.slice(-limit).reverse();
228106
- return { items: model && titleOf ? tail.map((e) => enrichAudit(e, model, titleOf)) : tail, cursor: null };
228474
+ return {
228475
+ items: model && titleOf ? tail.map(({ entry, anchors }) => enrichAudit(entry, model, titleOf, anchors)) : tail.map(({ entry }) => entry),
228476
+ cursor: null
228477
+ };
228107
228478
  }
228108
228479
  if (source.engineStore) {
228109
228480
  const records = await source.engineStore.transaction((tx) => tx.listEvents(void 0, fromSeq));
228110
228481
  for (const rec of records) {
228111
228482
  const ev = rec.event;
228112
228483
  if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
228113
- push2(eventToAuditEntry(rec), String(rec.seq));
228484
+ push2(eventToAuditEntry(rec), String(rec.seq), auditAnchorsOf(rec));
228114
228485
  if (items.length >= limit) {
228115
228486
  truncated = true;
228116
228487
  break;
@@ -228156,6 +228527,30 @@ var init_audit = __esm({
228156
228527
  "workorder.paused": "\u6682\u505C\u6D3E\u53D1",
228157
228528
  "workorder.resumed": "\u6062\u590D\u6D3E\u53D1",
228158
228529
  "workorder.sealed": "\u5C01\u5B58",
228530
+ // 其余新引擎事件 kind——缺标签时 summary 直接露英文 kind(生产实测:`plan.changed` 是量最大的
228531
+ // 一类审计条目,界面上就写着「plan.changed」)。这张表按 EngineEvent 的 kind 全集补齐。
228532
+ "plan.changed": "\u6539\u4EFB\u52A1\u56FE",
228533
+ "plan.add_edge": "\u63A5\u4F9D\u8D56",
228534
+ "plan.delete_edge": "\u65AD\u4F9D\u8D56",
228535
+ "plan.update_review_requirements": "\u6539\u8BC4\u5BA1\u8981\u6C42",
228536
+ "plan.update_fields": "\u6539\u5B57\u6BB5",
228537
+ "work.started": "\u5F00\u5DE5",
228538
+ "work.redispatch": "\u91CD\u6D3E",
228539
+ "work.snapshot_recorded": "\u5B58\u5FEB\u7167",
228540
+ "work.handoff_recorded": "\u8BB0\u4EA4\u63A5",
228541
+ "work.reject": "\u6253\u56DE",
228542
+ "work.content_edited": "\u6539\u5185\u5BB9",
228543
+ "work.conclude": "\u5B9A\u7A3F",
228544
+ "node.latest_proposed.rebased": "\u6362\u57FA\u7EBF",
228545
+ "review.started": "\u5F00\u59CB\u8BC4\u5BA1",
228546
+ "review.kill": "\u64A4\u8BC4\u5BA1",
228547
+ "review.timeout": "\u8BC4\u5BA1\u8D85\u65F6",
228548
+ "issue.resolution_proposed": "\u63D0\u89E3\u51B3\u65B9\u6848",
228549
+ "issue.resolution_rejected": "\u9A73\u56DE\u89E3\u51B3\u65B9\u6848",
228550
+ "workorder.created": "\u5EFA\u5DE5\u5355",
228551
+ "workorder.root_set": "\u5B9A\u6839\u8282\u70B9",
228552
+ "workorder.meta_changed": "\u6539\u5DE5\u5355\u4FE1\u606F",
228553
+ "workorder.ping": "\u5524\u9192",
228159
228554
  // 旧 op kind(oplog 回退路径)
228160
228555
  spawn_artifact: "\u5EFA\u4EA7\u7269",
228161
228556
  propose_revision: "\u63D0\u4FEE\u8BA2",
@@ -228974,11 +229369,7 @@ async function buildResolver(registry2) {
228974
229369
  const byId = new Map(actors.map((a) => [a.id, a]));
228975
229370
  return (id) => {
228976
229371
  const a = byId.get(id);
228977
- return a ? {
228978
- name: a.name,
228979
- ...a.roles[0] ? { role: a.roles[0] } : {},
228980
- ...a.avatar ? { avatar: a.avatar } : {}
228981
- } : void 0;
229372
+ return a ? actorRefFieldsOf(a) : systemActorFallback(id);
228982
229373
  };
228983
229374
  }
228984
229375
  async function buildProjectResolver(artifacts) {
@@ -233555,7 +233946,7 @@ function createChatSessionsDomain(opts) {
233555
233946
  const byId = new Map(actors.map((a) => [a.id, a]));
233556
233947
  const resolver2 = (id) => {
233557
233948
  const a = byId.get(id);
233558
- return a ? { name: a.name } : void 0;
233949
+ return a ? { name: a.name } : systemActorFallback(id);
233559
233950
  };
233560
233951
  const allSummaries = buildWorkorderSummaries(wkKernel.model, resolver2, (wo) => byWo.get(wo) ?? null, (wo) => held.has(wo), void 0, void 0, (role) => wkKernel.actorForRole(role));
233561
233952
  const summaryById = new Map(allSummaries.map((s2) => [s2.id, s2]));
@@ -237750,6 +238141,11 @@ var init_daemon_adapter = __esm({
237750
238141
  clearTimeout(entry.ackTimer);
237751
238142
  entry.ackTimer = void 0;
237752
238143
  }
238144
+ const reapTimer = this.sessionReapTimers.get(dispatchId);
238145
+ if (reapTimer) {
238146
+ clearTimeout(reapTimer);
238147
+ this.sessionReapTimers.delete(dispatchId);
238148
+ }
237753
238149
  entry.exited = info;
237754
238150
  for (const w2 of entry.appendWaiters.splice(0)) {
237755
238151
  clearTimeout(w2.timer);
@@ -237845,6 +238241,10 @@ var init_daemon_adapter = __esm({
237845
238241
  for (const [dispatchId, entry] of this.pending) {
237846
238242
  if (entry.nodeId !== daemonId || entry.exited || present.has(dispatchId)) continue;
237847
238243
  if (this.hub.hasSession?.(dispatchId)) continue;
238244
+ if (this.sessionReapTimers.has(dispatchId)) {
238245
+ this.log(`[dispatch-delivery] ${dispatchId}\uFF1A\u8282\u70B9 ${daemonId} \u7A7A\u5E93\u5B58\u4E0D\u8986\u76D6\u72EC\u7ACB\u4F1A\u8BDD\u65AD\u7EBF\u5BBD\u9650\uFF0C\u7ED3\u8BBA=unknown\uFF08\u4F9D\u636E\uFF1A\u6570\u636E\u9762\u66FE\u8FDE\u63A5\uFF0C\u7B49\u5F85\u91CD\u8FDE\u6216\u539F ${this.sessionReapGraceMs}ms \u671F\u9650\uFF09`);
238246
+ continue;
238247
+ }
237848
238248
  absent.push(dispatchId);
237849
238249
  }
237850
238250
  for (const dispatchId of absent) {
@@ -237877,8 +238277,7 @@ var init_daemon_adapter = __esm({
237877
238277
  */
237878
238278
  onSessionDown(dispatchId) {
237879
238279
  if (this.sessionReapTimers.has(dispatchId)) return;
237880
- const entry = this.pending.get(dispatchId);
237881
- if (!entry || entry.exited) return;
238280
+ if (this.settledDispatchIds.has(dispatchId)) return;
237882
238281
  const timer = setTimeout(() => {
237883
238282
  this.sessionReapTimers.delete(dispatchId);
237884
238283
  const e = this.pending.get(dispatchId);
@@ -239214,6 +239613,7 @@ ${ctx.nodeFault}
239214
239613
  env: { ...prov?.env ?? {}, OASIS_STAGE: "1", ...workspace ? { OASIS_WORKSPACE: workspace } : {} },
239215
239614
  ...prov?.wrapperPaths && prov.wrapperPaths.length > 0 ? { wrapperPaths: prov.wrapperPaths } : {},
239216
239615
  ...prov?.requiredTools && prov.requiredTools.length > 0 ? { requiredTools: prov.requiredTools } : {},
239616
+ ...prov?.requiredToolVersions && Object.keys(prov.requiredToolVersions).length > 0 ? { requiredToolVersions: prov.requiredToolVersions } : {},
239217
239617
  ...prov?.connectorCreds && prov.connectorCreds.length > 0 ? { connectorCreds: prov.connectorCreds } : {}
239218
239618
  };
239219
239619
  this.deps.log?.(`[coordinator] ${logMsg}`);
@@ -239774,6 +240174,7 @@ var init_postgres_registry = __esm({
239774
240174
  updated_at timestamptz NOT NULL,
239775
240175
  PRIMARY KEY (actor_id, connector_id)
239776
240176
  )`);
240177
+ await pool.query(`ALTER TABLE "${s2}".actor_connector_connections ADD COLUMN IF NOT EXISTS configured_by text`);
239777
240178
  await pool.query(`
239778
240179
  CREATE TABLE IF NOT EXISTS "${s2}".skill_catalog (
239779
240180
  id text PRIMARY KEY, -- slug
@@ -240161,10 +240562,11 @@ var init_postgres_registry = __esm({
240161
240562
  }
240162
240563
  async upsertActorConnectorConnection(c) {
240163
240564
  await this.pool.query(
240164
- `INSERT INTO ${this.s}.actor_connector_connections (actor_id, connector_id, enabled, updated_at)
240165
- VALUES ($1, $2, $3, $4)
240166
- ON CONFLICT (actor_id, connector_id) DO UPDATE SET enabled = $3, updated_at = $4`,
240167
- [c.actorId, c.connectorId, c.enabled, c.updatedAt]
240565
+ `INSERT INTO ${this.s}.actor_connector_connections (actor_id, connector_id, enabled, updated_at, configured_by)
240566
+ VALUES ($1, $2, $3, $4, $5)
240567
+ ON CONFLICT (actor_id, connector_id) DO UPDATE SET enabled = $3, updated_at = $4,
240568
+ configured_by = COALESCE($5, ${this.s}.actor_connector_connections.configured_by)`,
240569
+ [c.actorId, c.connectorId, c.enabled, c.updatedAt, c.configuredBy ?? null]
240168
240570
  );
240169
240571
  }
240170
240572
  async listActorConnectorConnections(actorId) {
@@ -240176,7 +240578,8 @@ var init_postgres_registry = __esm({
240176
240578
  actorId: row.actor_id,
240177
240579
  connectorId: row.connector_id,
240178
240580
  enabled: row.enabled,
240179
- updatedAt: new Date(row.updated_at).toISOString()
240581
+ updatedAt: new Date(row.updated_at).toISOString(),
240582
+ ...row.configured_by ? { configuredBy: row.configured_by } : {}
240180
240583
  }));
240181
240584
  }
240182
240585
  async listAllActorConnectorConnections() {
@@ -240187,7 +240590,8 @@ var init_postgres_registry = __esm({
240187
240590
  actorId: row.actor_id,
240188
240591
  connectorId: row.connector_id,
240189
240592
  enabled: row.enabled,
240190
- updatedAt: new Date(row.updated_at).toISOString()
240593
+ updatedAt: new Date(row.updated_at).toISOString(),
240594
+ ...row.configured_by ? { configuredBy: row.configured_by } : {}
240191
240595
  }));
240192
240596
  }
240193
240597
  async deleteActorConnectorConnection(actorId, connectorId) {
@@ -255201,6 +255605,98 @@ var init_chat_builtin_skills = __esm({
255201
255605
  }
255202
255606
  });
255203
255607
 
255608
+ // ../server/src/governance/connector-tool-versions.ts
255609
+ function connectorToolVersionsFile(dataDir) {
255610
+ return (0, import_node_path25.join)(dataDir, "connector-tools", "versions.json");
255611
+ }
255612
+ function registryBase() {
255613
+ const raw = process.env["npm_config_registry"] || process.env["NPM_CONFIG_REGISTRY"] || "https://registry.npmjs.org";
255614
+ return raw.replace(/\/+$/, "");
255615
+ }
255616
+ async function fetchLatestNpmVersion(pkg) {
255617
+ try {
255618
+ const url = `${registryBase()}/${pkg.split("/").map(encodeURIComponent).join("%2F")}/latest`;
255619
+ const res = await fetch(url, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(NPM_PROBE_TIMEOUT_MS) });
255620
+ if (!res.ok) return null;
255621
+ const v2 = (await res.json()).version;
255622
+ return typeof v2 === "string" && v2 ? v2 : null;
255623
+ } catch {
255624
+ return null;
255625
+ }
255626
+ }
255627
+ async function readRegisteredToolVersions(dataDir) {
255628
+ try {
255629
+ const raw = JSON.parse(await (0, import_promises14.readFile)(connectorToolVersionsFile(dataDir), "utf8"));
255630
+ if (!raw || typeof raw !== "object") return {};
255631
+ const out = {};
255632
+ for (const [k2, v2] of Object.entries(raw)) {
255633
+ if (typeof v2 === "string" && v2) out[k2] = v2;
255634
+ }
255635
+ return out;
255636
+ } catch {
255637
+ return {};
255638
+ }
255639
+ }
255640
+ async function writeRegisteredToolVersions(dataDir, versions) {
255641
+ const file = connectorToolVersionsFile(dataDir);
255642
+ await (0, import_promises14.mkdir)((0, import_node_path25.join)(dataDir, "connector-tools"), { recursive: true });
255643
+ const tmp = `${file}.${process.pid}.tmp`;
255644
+ await (0, import_promises14.writeFile)(tmp, `${JSON.stringify(versions, null, 2)}
255645
+ `, "utf8");
255646
+ await (0, import_promises14.rename)(tmp, file);
255647
+ }
255648
+ async function refreshConnectorToolVersions(opts) {
255649
+ const fetchLatest = opts.fetchLatest ?? fetchLatestNpmVersion;
255650
+ const log3 = opts.log ?? (() => {
255651
+ });
255652
+ const versions = await readRegisteredToolVersions(opts.dataDir);
255653
+ const results = [];
255654
+ let changed = false;
255655
+ for (const [tool, pkg] of Object.entries(NPM_TOOL_PACKAGES)) {
255656
+ const before = versions[tool];
255657
+ const latest = await fetchLatest(pkg);
255658
+ if (!latest) {
255659
+ results.push({ tool, pkg, ...before ? { from: before, to: before } : {}, outcome: "unreachable" });
255660
+ log3(`[connector-tools] ${pkg} \u95EE\u4E0D\u5230\u6700\u65B0\u7248\uFF0C\u6CBF\u7528\u767B\u8BB0\u7248\u672C ${before ?? "\uFF08\u65E0\uFF09"}`);
255661
+ continue;
255662
+ }
255663
+ if (before === latest) {
255664
+ results.push({ tool, pkg, from: before, to: latest, outcome: "unchanged" });
255665
+ continue;
255666
+ }
255667
+ versions[tool] = latest;
255668
+ changed = true;
255669
+ results.push({ tool, pkg, ...before ? { from: before } : {}, to: latest, outcome: "updated" });
255670
+ log3(`[connector-tools] ${pkg} \u767B\u8BB0\u7248\u672C ${before ?? "\uFF08\u65E0\uFF09"} \u2192 ${latest}`);
255671
+ }
255672
+ if (changed) {
255673
+ try {
255674
+ await writeRegisteredToolVersions(opts.dataDir, versions);
255675
+ } catch (err) {
255676
+ log3(`[connector-tools] \u767B\u8BB0\u7248\u672C\u843D\u76D8\u5931\u8D25\uFF08\u672C\u8FDB\u7A0B\u5185\u4ECD\u751F\u6548\uFF09\uFF1A${String(err)}`);
255677
+ }
255678
+ }
255679
+ return { versions, results };
255680
+ }
255681
+ function pickToolVersions(registered, tools) {
255682
+ const out = {};
255683
+ for (const t of tools) {
255684
+ const v2 = registered[t];
255685
+ if (v2) out[t] = v2;
255686
+ }
255687
+ return out;
255688
+ }
255689
+ var import_promises14, import_node_path25, NPM_PROBE_TIMEOUT_MS;
255690
+ var init_connector_tool_versions = __esm({
255691
+ "../server/src/governance/connector-tool-versions.ts"() {
255692
+ "use strict";
255693
+ import_promises14 = require("node:fs/promises");
255694
+ import_node_path25 = require("node:path");
255695
+ init_src8();
255696
+ NPM_PROBE_TIMEOUT_MS = 3e3;
255697
+ }
255698
+ });
255699
+
255204
255700
  // ../server/src/design/board-watch.ts
255205
255701
  function decideBoardLetter(c, nowMs, quietMs) {
255206
255702
  if (c.hasOpenWatchLetter) return false;
@@ -255953,6 +256449,7 @@ var init_src11 = __esm({
255953
256449
  init_node_store();
255954
256450
  init_tokens();
255955
256451
  init_collab();
256452
+ init_workorders();
255956
256453
  init_collab();
255957
256454
  init_closure_report();
255958
256455
  init_workorders();
@@ -255996,6 +256493,7 @@ var init_src11 = __esm({
255996
256493
  init_builtin_skills();
255997
256494
  init_chat_builtin_skills();
255998
256495
  init_connector_skills();
256496
+ init_connector_tool_versions();
255999
256497
  init_board_watch();
256000
256498
  init_ephemeral_project();
256001
256499
  init_run_settlement();
@@ -256858,6 +257356,7 @@ function materializeBuiltins(builtins, runtimeKind, opts) {
256858
257356
  var CONNECTOR_SKILL_SOURCES = [
256859
257357
  { connectorId: "feishu", baseUrl: FEISHU_WELL_KNOWN_SKILLS_BASE }
256860
257358
  ];
257359
+ var registeredToolVersions = {};
256861
257360
  async function readAllConnectorSkills(dataDir) {
256862
257361
  const out = [];
256863
257362
  for (const src of CONNECTOR_SKILL_SOURCES) {
@@ -256889,11 +257388,18 @@ async function refreshConnectorSkillsNow(dataDir, apply) {
256889
257388
  }
256890
257389
  }
256891
257390
  if (out.length > 0) apply(out);
257391
+ try {
257392
+ const { versions } = await refreshConnectorToolVersions({ dataDir, log: (m2) => console.log(m2) });
257393
+ registeredToolVersions = versions;
257394
+ } catch (err) {
257395
+ console.warn(`[serve] connector CLI \u767B\u8BB0\u7248\u672C\u5237\u65B0\u5931\u8D25\uFF0C\u6CBF\u7528\u4E0A\u4E00\u4EFD\uFF1A${String(err)}`);
257396
+ }
256892
257397
  return results;
256893
257398
  }
256894
257399
  function refreshConnectorSkillsInBackground(dataDir, apply) {
256895
257400
  void refreshConnectorSkillsNow(dataDir, apply);
256896
257401
  }
257402
+ var CONNECTOR_SKILL_REFRESH_MS = 24 * 60 * 6e4;
256897
257403
  var SESSION_TOKEN_GRACE_MS = 15 * 6e4;
256898
257404
  function makeResumeRetryProxy(first, spawnFallback) {
256899
257405
  const relay = () => {
@@ -257087,6 +257593,8 @@ async function buildActorProvision(service, actorId, scope, onBrokerRefused) {
257087
257593
  env,
257088
257594
  wrapperPaths: [oasisWrapperScript(), ...provision.wrapperPaths],
257089
257595
  requiredTools: provision.requiredTools,
257596
+ // 只挑本次真要用的那几个:job 里不塞无关工具的版本号。
257597
+ requiredToolVersions: pickToolVersions(registeredToolVersions, provision.requiredTools),
257090
257598
  connectorCreds: provision.connectorCreds,
257091
257599
  cleanup: provision.cleanup
257092
257600
  };
@@ -257658,7 +258166,8 @@ async function startServe(opts) {
257658
258166
  kernel: defaultCompanyKernel,
257659
258167
  // B2:读路径走 oplog 派生投影——同一 oplog 写后重放重建
257660
258168
  oplog,
257661
- actorNames: { resolve: (actorId) => registryActorNames.get(actorId) },
258169
+ // 系统身份(`actor:system*`)不在名册里,缓存必然打空——补同一份展示名,别让它退成裸 id。
258170
+ actorNames: { resolve: (actorId) => registryActorNames.get(actorId) ?? systemActorFallback(actorId)?.name },
257662
258171
  blobs,
257663
258172
  // 项目普通文件上传入 blob 库
257664
258173
  actorDirectory: registryStore,
@@ -257694,7 +258203,7 @@ async function startServe(opts) {
257694
258203
  ...engine2.blobs ? { blobs: engine2.blobs } : {},
257695
258204
  actorDirectory: companyRegistry,
257696
258205
  actorNames: {
257697
- resolve: (actorId) => perCompanyActorNames.get(companyId)?.get(actorId)
258206
+ resolve: (actorId) => perCompanyActorNames.get(companyId)?.get(actorId) ?? systemActorFallback(actorId)?.name
257698
258207
  },
257699
258208
  resolveActorContext: async (actorId) => {
257700
258209
  const config2 = await companyRegistry.latestConfig(actorId).catch(() => null);
@@ -257755,7 +258264,8 @@ async function startServe(opts) {
257755
258264
  ops: [],
257756
258265
  nodeOutputs: persisted.nodeOutputs,
257757
258266
  workspaceBindings: persisted.workspaceBindings,
257758
- actorNames: { resolve: (actorId) => registryActorNames.get(actorId) }
258267
+ // 系统身份(`actor:system*`)不在名册里,缓存必然打空——补同一份展示名,别让它退成裸 id。
258268
+ actorNames: { resolve: (actorId) => registryActorNames.get(actorId) ?? systemActorFallback(actorId)?.name }
257759
258269
  });
257760
258270
  };
257761
258271
  const traceDsn = process.env["OASIS_TRACE_PG_DSN"] ?? pgDsn;
@@ -258275,10 +258785,17 @@ async function startServe(opts) {
258275
258785
  });
258276
258786
  console.log(`[serve] loaded ${builtinSkills.length} builtin skill(s): ${builtinSkills.map((s2) => s2.id).join(", ") || "(none)"}`);
258277
258787
  connectorSkills = await readAllConnectorSkills(opts.dir);
258788
+ registeredToolVersions = await readRegisteredToolVersions(opts.dir);
258278
258789
  console.log(`[serve] connector skills\uFF08\u843D\u76D8\u526F\u672C\uFF09\uFF1A${connectorSkills.length} \u4E2A`);
258279
258790
  refreshConnectorSkillsInBackground(opts.dir, (s2) => {
258280
258791
  connectorSkills = s2;
258281
258792
  });
258793
+ const connectorSkillTimer = setInterval(() => {
258794
+ refreshConnectorSkillsInBackground(opts.dir, (s2) => {
258795
+ connectorSkills = s2;
258796
+ });
258797
+ }, CONNECTOR_SKILL_REFRESH_MS);
258798
+ connectorSkillTimer.unref?.();
258282
258799
  if (defaultCompanyMigration.createdCompany) {
258283
258800
  const seededAt = (/* @__PURE__ */ new Date()).toISOString();
258284
258801
  for (const devActor of ["actor:human:yx"]) {
@@ -259148,6 +259665,7 @@ async function startServe(opts) {
259148
259665
  env: provision.env,
259149
259666
  wrapperPaths: provision.wrapperPaths,
259150
259667
  ...provision.requiredTools.length > 0 ? { requiredTools: provision.requiredTools } : {},
259668
+ ...Object.keys(provision.requiredToolVersions).length > 0 ? { requiredToolVersions: provision.requiredToolVersions } : {},
259151
259669
  ...provision.connectorCreds.length > 0 ? { connectorCreds: provision.connectorCreds } : {}
259152
259670
  });
259153
259671
  const chunks = [];
@@ -260133,7 +260651,7 @@ async function startServe(opts) {
260133
260651
  console.warn(`[trace] chat run ${traceRunId} \u7EED\u8D26\u5F02\u5E38: ${String(err)}`);
260134
260652
  });
260135
260653
  };
260136
- const provision = isolatedKnowledgeTurn ? { env: {}, wrapperPaths: [], requiredTools: [], connectorCreds: [], cleanup: async () => void 0 } : await buildActorProvision(actorService, actorId);
260654
+ const provision = isolatedKnowledgeTurn ? { env: {}, wrapperPaths: [], requiredTools: [], requiredToolVersions: {}, connectorCreds: [], cleanup: async () => void 0 } : await buildActorProvision(actorService, actorId);
260137
260655
  const actorCtx = await buildActorContext(actorService, actorId).catch(() => null);
260138
260656
  const chatResolvedModel = sessionModelOverride ?? actorCtx?.config?.model ?? void 0;
260139
260657
  const chatModel = chatResolvedModel ?? await registryStore.getRuntimeConfig(`runtime:${binding.nodeId}:${binding.runtimeKind}`).then((c) => c?.model).catch(() => void 0) ?? void 0;
@@ -260273,6 +260791,7 @@ async function startServe(opts) {
260273
260791
  },
260274
260792
  wrapperPaths: provision.wrapperPaths,
260275
260793
  ...provision.requiredTools.length > 0 ? { requiredTools: provision.requiredTools } : {},
260794
+ ...Object.keys(provision.requiredToolVersions).length > 0 ? { requiredToolVersions: provision.requiredToolVersions } : {},
260276
260795
  ...provision.connectorCreds.length > 0 ? { connectorCreds: provision.connectorCreds } : {},
260277
260796
  ...requiredConnectors.length > 0 ? { requiredConnectorSlugs: requiredConnectors } : {}
260278
260797
  });
@@ -260809,7 +261328,8 @@ async function startServe(opts) {
260809
261328
  await server.settleDelegationFromSweep(
260810
261329
  chatSessionId,
260811
261330
  `\u5B50\u4F1A\u8BDD\u8FD9\u4E00\u8F6E\u88AB\u6062\u590D\u5668\u6536\u53E3\u4E3A ${status}\uFF08${reason}\uFF09`,
260812
- companyId
261331
+ companyId,
261332
+ { outcome: status === "succeeded" ? "succeeded" : "failed" }
260813
261333
  );
260814
261334
  },
260815
261335
  log: (m2) => console.log(m2),
@@ -262894,6 +263414,7 @@ ${nodeFault}` : "");
262894
263414
  if (gitTimer) clearInterval(gitTimer);
262895
263415
  if (coordTimer) clearInterval(coordTimer);
262896
263416
  if (livenessTimer) clearInterval(livenessTimer);
263417
+ clearInterval(connectorSkillTimer);
262897
263418
  if (orphanSweepTimer) clearInterval(orphanSweepTimer);
262898
263419
  for (const t of dropSweepTimers) clearInterval(t);
262899
263420
  if (deadlineClockTimer) clearInterval(deadlineClockTimer);
@@ -263092,7 +263613,7 @@ async function runSession(dispatchId, job, deps) {
263092
263613
  }
263093
263614
  }
263094
263615
  if (job.requiredTools?.length) {
263095
- const missing = await ensureConnectorTools(job.requiredTools);
263616
+ const missing = await ensureConnectorTools(job.requiredTools, { versions: job.requiredToolVersions });
263096
263617
  if (missing.length) log2("[node-cli]", ` connector tools still missing (agent may fail): ${missing.join(", ")}`);
263097
263618
  }
263098
263619
  const prepared = await prepareConnectorsForJob(job);
@@ -263495,7 +264016,7 @@ function detectRuntimes() {
263495
264016
 
263496
264017
  // ../cli/src/daemon/workdir-handler.ts
263497
264018
  var import_node_fs19 = __toESM(require("node:fs"), 1);
263498
- var import_node_path25 = __toESM(require("node:path"), 1);
264019
+ var import_node_path26 = __toESM(require("node:path"), 1);
263499
264020
  init_src6();
263500
264021
  init_src();
263501
264022
  var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
@@ -263514,10 +264035,10 @@ function isSensitiveSegment(segment) {
263514
264035
  }
263515
264036
  function relPathIsSensitive(rel) {
263516
264037
  if (!rel) return false;
263517
- return rel.split(import_node_path25.default.sep).some((seg) => seg.length > 0 && isSensitiveSegment(seg));
264038
+ return rel.split(import_node_path26.default.sep).some((seg) => seg.length > 0 && isSensitiveSegment(seg));
263518
264039
  }
263519
264040
  function withinBase(p2, base) {
263520
- return p2 === base || p2.startsWith(base + import_node_path25.default.sep);
264041
+ return p2 === base || p2.startsWith(base + import_node_path26.default.sep);
263521
264042
  }
263522
264043
  function normalizeRel(raw) {
263523
264044
  const trimmed = (raw ?? "").trim();
@@ -263534,7 +264055,7 @@ async function trustedCanonicalContainer(req, logicalBase, dirKind) {
263534
264055
  } catch {
263535
264056
  return null;
263536
264057
  }
263537
- return isLegacy ? import_node_path25.default.join(trustedRootReal, "oasis-chat-sessions") : import_node_path25.default.join(trustedRootReal, "sessions", dirKind);
264058
+ return isLegacy ? import_node_path26.default.join(trustedRootReal, "oasis-chat-sessions") : import_node_path26.default.join(trustedRootReal, "sessions", dirKind);
263538
264059
  }
263539
264060
  async function resolveWithinWorkdir(req) {
263540
264061
  const dirKind = sessionDirKind(req.runtimeKind);
@@ -263552,11 +264073,11 @@ async function resolveWithinWorkdir(req) {
263552
264073
  } catch {
263553
264074
  return { ok: false, code: "NOT_FOUND" };
263554
264075
  }
263555
- if (import_node_path25.default.dirname(base) !== canonicalContainer) return { ok: false, code: "PATH_ESCAPE" };
264076
+ if (import_node_path26.default.dirname(base) !== canonicalContainer) return { ok: false, code: "PATH_ESCAPE" };
263556
264077
  const rel = normalizeRel(req.path);
263557
- const requested = import_node_path25.default.resolve(base, rel);
264078
+ const requested = import_node_path26.default.resolve(base, rel);
263558
264079
  if (!withinBase(requested, base)) return { ok: false, code: "PATH_ESCAPE" };
263559
- const cleanRel = base === requested ? "" : import_node_path25.default.relative(base, requested);
264080
+ const cleanRel = base === requested ? "" : import_node_path26.default.relative(base, requested);
263560
264081
  if (relPathIsSensitive(cleanRel)) return { ok: false, code: "SENSITIVE" };
263561
264082
  let real;
263562
264083
  try {
@@ -263566,7 +264087,7 @@ async function resolveWithinWorkdir(req) {
263566
264087
  return { ok: false, code: "PATH_ESCAPE" };
263567
264088
  }
263568
264089
  if (!withinBase(real, base)) return { ok: false, code: "PATH_ESCAPE" };
263569
- const realRel = base === real ? "" : import_node_path25.default.relative(base, real);
264090
+ const realRel = base === real ? "" : import_node_path26.default.relative(base, real);
263570
264091
  if (relPathIsSensitive(realRel)) return { ok: false, code: "SENSITIVE" };
263571
264092
  return { ok: true, base, real };
263572
264093
  }
@@ -263602,7 +264123,7 @@ async function handleWorkdirList(req) {
263602
264123
  const slice = truncated ? visible.slice(0, WORKDIR_LIST_MAX_ENTRIES) : visible;
263603
264124
  const entries = [];
263604
264125
  for (const d of slice) {
263605
- const abs = import_node_path25.default.join(anchor, d.name);
264126
+ const abs = import_node_path26.default.join(anchor, d.name);
263606
264127
  try {
263607
264128
  const st = await import_node_fs19.default.promises.lstat(abs);
263608
264129
  if (st.isSymbolicLink()) continue;
@@ -263634,7 +264155,7 @@ async function verifyOpenedFd(fh, base, fallback) {
263634
264155
  const fdReal = await fdCanonicalPath(fh);
263635
264156
  if (fdReal === null) return { anchor: fallback };
263636
264157
  if (!withinBase(fdReal, base)) return { error: { ok: false, code: "PATH_ESCAPE" } };
263637
- const fdRel = base === fdReal ? "" : import_node_path25.default.relative(base, fdReal);
264158
+ const fdRel = base === fdReal ? "" : import_node_path26.default.relative(base, fdReal);
263638
264159
  if (relPathIsSensitive(fdRel)) return { error: { ok: false, code: "SENSITIVE" } };
263639
264160
  return { anchor: `/proc/self/fd/${fh.fd}` };
263640
264161
  }
@@ -263723,7 +264244,7 @@ function looksBinary(bytes2) {
263723
264244
  function contentTypeFor(absPath, bytes2) {
263724
264245
  const sniffed = sniffContentType(bytes2);
263725
264246
  if (sniffed) return sniffed;
263726
- const ext = import_node_path25.default.extname(absPath).toLowerCase();
264247
+ const ext = import_node_path26.default.extname(absPath).toLowerCase();
263727
264248
  if (EXT_CONTENT_TYPE[ext]) return EXT_CONTENT_TYPE[ext];
263728
264249
  return looksBinary(bytes2) ? "application/octet-stream" : "text/plain; charset=utf-8";
263729
264250
  }
@@ -264319,17 +264840,17 @@ var RuntimeRouterAdapter = class {
264319
264840
  // ../cli/src/daemon/reap-claude-projects.ts
264320
264841
  var import_node_fs20 = __toESM(require("node:fs"), 1);
264321
264842
  var import_node_os10 = __toESM(require("node:os"), 1);
264322
- var import_node_path26 = __toESM(require("node:path"), 1);
264843
+ var import_node_path27 = __toESM(require("node:path"), 1);
264323
264844
  function claudeProjectSlug(cwd) {
264324
264845
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
264325
264846
  }
264326
264847
  function claudeProjectsRoot() {
264327
- const configDir = process.env["CLAUDE_CONFIG_DIR"] || import_node_path26.default.join(import_node_os10.default.homedir(), ".claude");
264328
- return import_node_path26.default.join(configDir, "projects");
264848
+ const configDir = process.env["CLAUDE_CONFIG_DIR"] || import_node_path27.default.join(import_node_os10.default.homedir(), ".claude");
264849
+ return import_node_path27.default.join(configDir, "projects");
264329
264850
  }
264330
264851
  function reapClaudeProjects(workdir, runtimeKind) {
264331
264852
  if (runtimeKind !== "claude" && runtimeKind !== "claude-code") return;
264332
- const dir = import_node_path26.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264853
+ const dir = import_node_path27.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264333
264854
  if (!import_node_fs20.default.existsSync(dir)) return;
264334
264855
  try {
264335
264856
  import_node_fs20.default.rmSync(dir, { recursive: true, force: true });
@@ -264338,7 +264859,7 @@ function reapClaudeProjects(workdir, runtimeKind) {
264338
264859
  }
264339
264860
  function measureClaudeProjects(workdir, runtimeKind) {
264340
264861
  if (runtimeKind !== "claude" && runtimeKind !== "claude-code") return 0;
264341
- const dir = import_node_path26.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264862
+ const dir = import_node_path27.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264342
264863
  return dirSizeBytes2(dir);
264343
264864
  }
264344
264865
  function dirSizeBytes2(dir) {
@@ -264350,7 +264871,7 @@ function dirSizeBytes2(dir) {
264350
264871
  return 0;
264351
264872
  }
264352
264873
  for (const e of entries) {
264353
- const p2 = import_node_path26.default.join(dir, e.name);
264874
+ const p2 = import_node_path27.default.join(dir, e.name);
264354
264875
  if (e.isSymbolicLink()) continue;
264355
264876
  if (e.isDirectory()) {
264356
264877
  total += dirSizeBytes2(p2);
@@ -266761,7 +267282,7 @@ function fieldsFromFlags(flags, ownFlags) {
266761
267282
  }
266762
267283
  return Object.keys(fields).length > 0 ? fields : void 0;
266763
267284
  }
266764
- function buildStageOp(cmd, flags, positional, readFile8 = (file) => fs39.readFileSync(file, "utf8")) {
267285
+ function buildStageOp(cmd, flags, positional, readFile9 = (file) => fs39.readFileSync(file, "utf8")) {
266765
267286
  switch (cmd) {
266766
267287
  case "link":
266767
267288
  return {
@@ -266783,7 +267304,7 @@ function buildStageOp(cmd, flags, positional, readFile8 = (file) => fs39.readFil
266783
267304
  ...flags.get("title") !== void 0 ? { title: flags.get("title") } : {},
266784
267305
  ...(flags.get("brief") ?? flags.get("description")) !== void 0 ? { description: flags.get("brief") ?? flags.get("description") } : {},
266785
267306
  ...flags.get("input") !== void 0 ? { inputs: flags.get("input").split(",").map((to) => ({ to: to.trim() })) } : {},
266786
- ...partsFile !== void 0 ? { parts: JSON.parse(readFile8(partsFile)) } : {},
267307
+ ...partsFile !== void 0 ? { parts: JSON.parse(readFile9(partsFile)) } : {},
266787
267308
  ...fields !== void 0 ? { fields } : {}
266788
267309
  };
266789
267310
  }
@@ -270150,7 +270671,7 @@ function shimScript() {
270150
270671
  }
270151
270672
 
270152
270673
  // src/index.ts
270153
- var PKG_VERSION = true ? "2.2.7" : "dev";
270674
+ var PKG_VERSION = true ? "2.2.9" : "dev";
270154
270675
  var LOCAL_BIN = localBin();
270155
270676
  var NPM_PREFIX = npmPrefix();
270156
270677
  var INSTANCE = DEFAULT_INSTANCE;