oasis_test_v2 2.2.7 → 2.2.8

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 +517 -116
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -17406,6 +17406,7 @@ ${supplemental.taskAppend.trim()}
17406
17406
  ...Object.keys(jobEnv).length > 0 ? { env: jobEnv } : {},
17407
17407
  ...provisioned?.wrapperPaths && provisioned.wrapperPaths.length > 0 ? { wrapperPaths: provisioned.wrapperPaths } : {},
17408
17408
  ...provisioned?.requiredTools && provisioned.requiredTools.length > 0 ? { requiredTools: provisioned.requiredTools } : {},
17409
+ ...provisioned?.requiredToolVersions && Object.keys(provisioned.requiredToolVersions).length > 0 ? { requiredToolVersions: provisioned.requiredToolVersions } : {},
17409
17410
  // 凭据本体随 job 下发,供**节点侧本机注入**(见上方 provision 注释)。
17410
17411
  ...provisioned?.connectorCreds && provisioned.connectorCreds.length > 0 ? { connectorCreds: provisioned.connectorCreds } : {},
17411
17412
  ...executionEnv !== void 0 ? { executionEnv } : {},
@@ -195837,7 +195838,15 @@ var init_service3 = __esm({
195837
195838
  ...companyId ? { companyId } : {},
195838
195839
  ...snapshot ? { parentConversationSnapshot: snapshot } : {},
195839
195840
  extraSystemPrompt: EXPERT_DELEGATION_SYSTEM_PROMPT,
195840
- ..._DelegationService.inboundBundle(inbound) ? { workspaceBundle: _DelegationService.inboundBundle(inbound) } : {}
195841
+ ..._DelegationService.inboundBundle(inbound) ? { workspaceBundle: _DelegationService.inboundBundle(inbound) } : {},
195842
+ /* 派发这一侧要拿它们建 `chat_items` 账本(见本文件 `DelegationDispatchRequest` 的注释)。
195843
+ 轮次口径与 `bindChildRunToPlaceholder` / `seedChildRoundRows` **逐字一致**:
195844
+ `record.roundSummaries.length + 1`。三处一旦不同源,item 就会挂到上一轮那条回答行上。 */
195845
+ assistantMessageId: childRoundMessageIds(
195846
+ record8.childSessionId,
195847
+ record8.roundSummaries.length + 1
195848
+ ).placeholderId,
195849
+ chatTurnId: turnId
195841
195850
  });
195842
195851
  outcomeSettled = true;
195843
195852
  await opts?.onDispatched?.().catch((err) => console.warn(`[delegation] \u5F85\u8F6C\u8FBE\u6807\u8BB0\u5DF2\u6D88\u8D39\u5931\u8D25\uFF08${record8.id}\uFF09: ${String(err)}`));
@@ -197101,7 +197110,11 @@ function toChatDispatchRequest(request2) {
197101
197110
  delegatedChildTurn: true,
197102
197111
  // §6.1.3 去程:`--file` 带来的字节就在这一格。**丢了它命令照样回 202**,
197103
197112
  // 而专家的工作区里什么都没有——这正是上一版真实发生过的形态。
197104
- ...request2.workspaceBundle ? { workspaceBundle: request2.workspaceBundle } : {}
197113
+ ...request2.workspaceBundle ? { workspaceBundle: request2.workspaceBundle } : {},
197114
+ // 这两格漏了同样**不报错**:子会话照跑、正文照回,只是 `chat_items` 的行没有 message_id、
197115
+ // turnId 回落成 `chat-run-turn:<runId>`,右栏面板的增量流对不上轮——正是本文件存在的理由。
197116
+ ...request2.assistantMessageId ? { assistantMessageId: request2.assistantMessageId } : {},
197117
+ ...request2.chatTurnId ? { chatTurnId: request2.chatTurnId } : {}
197105
197118
  };
197106
197119
  }
197107
197120
  function applyWorkspaceBundle(target, bundle) {
@@ -197321,27 +197334,77 @@ var init_assembly = __esm({
197321
197334
  });
197322
197335
 
197323
197336
  // ../server/src/domains/delegations/child-live-turn.ts
197324
- function registerDelegatedChildTurn(liveChat, childSessionId, session) {
197337
+ function registerDelegatedChildTurn(liveChat, childSessionId, session, deps = {}) {
197338
+ const log3 = deps.log ?? ((m2) => console.warn(m2));
197325
197339
  const appendInput = session.appendInput;
197326
- if (typeof appendInput !== "function" || session.canAppendInput === false) return;
197340
+ const canAppend = typeof appendInput === "function" && session.canAppendInput !== false;
197341
+ const ledger = new ChatItemLedger({
197342
+ ...deps.items ? { items: deps.items } : {},
197343
+ sessionId: childSessionId,
197344
+ turnId: deps.turnId ?? fallbackTurnId(session.runId),
197345
+ ...session.runId ? { runId: session.runId } : {},
197346
+ ...deps.assistantMessageId ? { messageId: deps.assistantMessageId } : {},
197347
+ ...deps.versionSeed !== void 0 ? { versionSeed: deps.versionSeed } : {},
197348
+ log: log3
197349
+ });
197327
197350
  const ctrl = liveChat.start(childSessionId, {
197328
197351
  runtimeSessionId: session.id,
197329
197352
  ...session.runId ? { runId: session.runId } : {},
197330
197353
  kill: () => {
197331
197354
  void session.kill?.();
197332
197355
  },
197333
- appendInput: (input) => appendInput.call(session, input),
197356
+ // 纪律 ①:插不进去的 runtime 也登记,只是这两格照实报。
197357
+ ...canAppend && appendInput ? { appendInput: (input) => appendInput.call(session, input) } : {},
197334
197358
  // **每次现读**,不快照:一轮跑到收尾时 stdin 会先关,那之后 runtime 自己会回
197335
197359
  // `session-closing`,判断权本就该留在它那儿(同 `/api/chat` 那条路的写法)。
197336
197360
  get canAppendInput() {
197337
- return session.canAppendInput !== false;
197361
+ return typeof session.appendInput === "function" && session.canAppendInput !== false;
197362
+ }
197363
+ }, { items: ledger });
197364
+ if (deps.assistantMessageId) ctrl.setAssistantMessageId(deps.assistantMessageId);
197365
+ const hasChannels = typeof session.onOutput === "function" || typeof session.onTelemetry === "function" || typeof session.onLiveEvent === "function";
197366
+ const normalized4 = typeof session.onNormalizedProviderEvent === "function" ? {
197367
+ onNormalizedProviderEvent: session.onNormalizedProviderEvent.bind(session),
197368
+ finishTurn: (signal) => session.finishNormalizedTurn?.(signal)
197369
+ } : hasChannels ? attachStreamNormalizer(
197370
+ {
197371
+ id: session.id,
197372
+ onOutput: (cb) => session.onOutput?.(cb),
197373
+ onTelemetry: (cb) => session.onTelemetry?.(cb),
197374
+ onLiveEvent: (cb) => session.onLiveEvent?.(cb),
197375
+ ...session.supportsLiveProtocol !== void 0 ? { supportsLiveProtocol: session.supportsLiveProtocol } : {}
197376
+ },
197377
+ {
197378
+ providerName: deps.runtimeKind ?? "runtime",
197379
+ fallbackTurnId: `oasis-turn:${session.runId ?? session.id}`
197380
+ }
197381
+ ) : null;
197382
+ normalized4?.onNormalizedProviderEvent((event) => {
197383
+ try {
197384
+ ctrl.applyNormalizedEvent(event);
197385
+ } catch {
197338
197386
  }
197339
197387
  });
197340
- void session.done.then(() => ctrl.finish("done"), () => ctrl.finish("error"));
197388
+ const settle = async (signal) => {
197389
+ try {
197390
+ normalized4?.finishTurn(signal);
197391
+ } catch {
197392
+ }
197393
+ try {
197394
+ ledger.finish(signal);
197395
+ await ledger.drain();
197396
+ } catch (err) {
197397
+ log3(`[delegation] \u5B50\u4F1A\u8BDD ${childSessionId} \u8D26\u672C\u6536\u5C3E\u5931\u8D25: ${String(err)}`);
197398
+ }
197399
+ ctrl.finish(signal === "completed" ? "done" : "error");
197400
+ };
197401
+ void session.done.then(() => settle("completed"), () => settle("failed"));
197341
197402
  }
197342
197403
  var init_child_live_turn = __esm({
197343
197404
  "../server/src/domains/delegations/child-live-turn.ts"() {
197344
197405
  "use strict";
197406
+ init_src6();
197407
+ init_chat_item_ledger();
197345
197408
  }
197346
197409
  });
197347
197410
 
@@ -197455,10 +197518,12 @@ var init_tool_home = __esm({
197455
197518
  });
197456
197519
 
197457
197520
  // ../connectors/src/_base/install.ts
197458
- function installPlan(cmd, platform2, toolsPrefix) {
197459
- if (cmd === "lark-cli") {
197521
+ function installPlan(cmd, platform2, toolsPrefix, version2) {
197522
+ const npmPkg = NPM_TOOL_PACKAGES[cmd];
197523
+ if (npmPkg) {
197460
197524
  const npm = platform2 === "win32" ? "npm.cmd" : "npm";
197461
- return [{ file: npm, args: ["install", "-g", "--prefix", toolsPrefix, "@larksuite/cli"], ensureDir: toolsPrefix }];
197525
+ const spec = version2 ? `${npmPkg}@${version2}` : npmPkg;
197526
+ return [{ file: npm, args: ["install", "-g", "--prefix", toolsPrefix, spec], ensureDir: toolsPrefix }];
197462
197527
  }
197463
197528
  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
197529
  if (platform2 === "darwin") {
@@ -197503,17 +197568,54 @@ function defaultDeps() {
197503
197568
  mkdirp: (dir) => {
197504
197569
  (0, import_node_fs9.mkdirSync)(dir, { recursive: true });
197505
197570
  },
197506
- log: (msg) => console.warn(msg)
197571
+ log: (msg) => console.warn(msg),
197572
+ installedVersion: async (pkg, prefix) => readInstalledVersion(pkg, prefix)
197507
197573
  };
197508
197574
  }
197509
- async function ensureToolInstalled(cmd, deps = {}) {
197575
+ function readInstalledVersion(pkg, prefix) {
197576
+ const parts = pkg.split("/");
197577
+ for (const mid of [["lib", "node_modules"], ["node_modules"]]) {
197578
+ const file = (0, import_node_path11.join)(prefix, ...mid, ...parts, "package.json");
197579
+ if (!(0, import_node_fs9.existsSync)(file)) continue;
197580
+ try {
197581
+ const v2 = JSON.parse((0, import_node_fs9.readFileSync)(file, "utf8")).version;
197582
+ if (typeof v2 === "string" && v2) return v2;
197583
+ } catch {
197584
+ }
197585
+ }
197586
+ return null;
197587
+ }
197588
+ async function alignToWantedVersion(cmd, wanted, d) {
197589
+ if (alignedVersion.get(cmd) === wanted) return;
197590
+ const pkg = NPM_TOOL_PACKAGES[cmd];
197591
+ if (!pkg) return;
197592
+ const installed = await d.installedVersion(pkg, d.toolsPrefix);
197593
+ if (!installed) return;
197594
+ if (installed === wanted) {
197595
+ alignedVersion.set(cmd, wanted);
197596
+ return;
197597
+ }
197598
+ const npm = d.platform === "win32" ? "npm.cmd" : "npm";
197599
+ d.log(`[connector-install] ${cmd} ${installed} \u2192 ${wanted}\uFF08\u670D\u52A1\u7AEF\u767B\u8BB0\u7248\u672C\uFF09\uFF0C\u5B89\u88C5\u4E2D\u2026`);
197600
+ try {
197601
+ d.mkdirp(d.toolsPrefix);
197602
+ await d.run(npm, ["install", "-g", "--prefix", d.toolsPrefix, `${pkg}@${wanted}`]);
197603
+ alignedVersion.set(cmd, wanted);
197604
+ d.log(`[connector-install] ${cmd} \u5DF2\u5BF9\u9F50\u5230 ${wanted}`);
197605
+ } catch (err) {
197606
+ d.log(`[connector-install] ${cmd} \u5BF9\u9F50\u5230 ${wanted} \u5931\u8D25\uFF0C\u7EE7\u7EED\u7528 ${installed}\uFF1A${String(err)}`);
197607
+ }
197608
+ }
197609
+ async function ensureToolInstalled(cmd, opts = {}) {
197610
+ const { versions, ...deps } = opts;
197510
197611
  const d = { ...defaultDeps(), ...deps };
197511
- if (confirmed.has(cmd)) return true;
197512
- if (await d.isPresent(cmd)) {
197612
+ const wanted = versions?.[cmd];
197613
+ if (confirmed.has(cmd) || await d.isPresent(cmd)) {
197513
197614
  confirmed.add(cmd);
197615
+ if (wanted) await alignToWantedVersion(cmd, wanted, d);
197514
197616
  return true;
197515
197617
  }
197516
- const steps = installPlan(cmd, d.platform, d.toolsPrefix);
197618
+ const steps = installPlan(cmd, d.platform, d.toolsPrefix, wanted);
197517
197619
  if (steps.length === 0) {
197518
197620
  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
197621
  return false;
@@ -197526,6 +197628,7 @@ async function ensureToolInstalled(cmd, deps = {}) {
197526
197628
  await d.run(step.file, step.args);
197527
197629
  if (await d.isPresent(cmd)) {
197528
197630
  confirmed.add(cmd);
197631
+ if (wanted) alignedVersion.set(cmd, wanted);
197529
197632
  d.log(`[connector-install] ${cmd} \u5B89\u88C5\u6210\u529F\uFF08${step.file} ${step.args.join(" ")}\uFF09`);
197530
197633
  return true;
197531
197634
  }
@@ -197536,15 +197639,15 @@ async function ensureToolInstalled(cmd, deps = {}) {
197536
197639
  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
197640
  return false;
197538
197641
  }
197539
- async function ensureConnectorTools(tools, deps = {}) {
197642
+ async function ensureConnectorTools(tools, opts = {}) {
197540
197643
  if (!tools || tools.length === 0) return [];
197541
197644
  const missing = [];
197542
197645
  for (const cmd of tools) {
197543
- if (!await ensureToolInstalled(cmd, deps)) missing.push(cmd);
197646
+ if (!await ensureToolInstalled(cmd, opts)) missing.push(cmd);
197544
197647
  }
197545
197648
  return missing;
197546
197649
  }
197547
- var import_node_child_process12, import_node_util2, import_node_fs9, import_node_path11, execFileAsync, confirmed;
197650
+ var import_node_child_process12, import_node_util2, import_node_fs9, import_node_path11, execFileAsync, NPM_TOOL_PACKAGES, confirmed, alignedVersion;
197548
197651
  var init_install = __esm({
197549
197652
  "../connectors/src/_base/install.ts"() {
197550
197653
  "use strict";
@@ -197554,7 +197657,9 @@ var init_install = __esm({
197554
197657
  import_node_path11 = require("node:path");
197555
197658
  init_tool_home();
197556
197659
  execFileAsync = (0, import_node_util2.promisify)(import_node_child_process12.execFile);
197660
+ NPM_TOOL_PACKAGES = { "lark-cli": "@larksuite/cli" };
197557
197661
  confirmed = /* @__PURE__ */ new Set();
197662
+ alignedVersion = /* @__PURE__ */ new Map();
197558
197663
  }
197559
197664
  });
197560
197665
 
@@ -201625,7 +201730,7 @@ var init_connector_adapter = __esm({
201625
201730
  async spawn(job) {
201626
201731
  const t0 = Date.now();
201627
201732
  if (job.requiredTools?.length) {
201628
- const missing = await ensureConnectorTools(job.requiredTools);
201733
+ const missing = await ensureConnectorTools(job.requiredTools, { versions: job.requiredToolVersions });
201629
201734
  if (missing.length) log("[adapter]", ` connector tools still missing (agent may fail): ${missing.join(", ")}`);
201630
201735
  }
201631
201736
  const prepared = await prepareConnectorsForJob(job);
@@ -203289,13 +203394,22 @@ async function startOasisServer(opts) {
203289
203394
  resolveStore: opts.delegationStoreFor,
203290
203395
  resolveChatSessions: resolveChatSessionsForDelegation,
203291
203396
  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` 的文件头。
203397
+ /* **派发完顺手把子会话这一轮登记进 live 注册表,并给它接上 `chat_items` 账本。**
203398
+ 两件事的成因不同,都在 `child-live-turn.ts` 的文件头:
203399
+ · 不登记 → 下面那行 `appendToChild` 恒回 `no-live-turn`,每一条 `--follow-up` 转达
203400
+ 都落队列,专家要等本轮跑完才看见「手上这段作废」(v2 bug 0088);
203401
+ · 不接账本 → 子会话在 `chat_items` 里只有 storage 层镜像来的一问一答两行纯文字,
203402
+ 右栏面板只能靠详情接口拿 run_id 去轨迹表**现折**,跑的过程中一片空白。 */
203296
203403
  dispatchChat: async (request2) => {
203297
203404
  const session = await dispatchChat(request2);
203298
- registerDelegatedChildTurn(liveChat, request2.chatSessionId, session);
203405
+ const itemStore = opts.resolveChatItems ? await opts.resolveChatItems(request2.companyId).catch(() => void 0) : opts.chatItems;
203406
+ const versionSeed = itemStore ? await itemStore.sessionVersionCursor(request2.chatSessionId).catch(() => 0) : 0;
203407
+ registerDelegatedChildTurn(liveChat, request2.chatSessionId, session, {
203408
+ ...itemStore ? { items: itemStore } : {},
203409
+ ...request2.chatTurnId ? { turnId: request2.chatTurnId } : {},
203410
+ ...request2.assistantMessageId ? { assistantMessageId: request2.assistantMessageId } : {},
203411
+ versionSeed
203412
+ });
203299
203413
  return session;
203300
203414
  },
203301
203415
  appendToChild: (childSessionId, text5, extra) => liveChat.append(childSessionId, text5, extra),
@@ -204070,6 +204184,7 @@ async function startOasisServer(opts) {
204070
204184
  const rawBody = Buffer.concat(chunks).toString("utf8");
204071
204185
  const companyCtx = opts.resolveCompanyContext ? await opts.resolveCompanyContext(actor, req.headers, url.pathname) : void 0;
204072
204186
  const currentCompanyId = companyCtx?.kind === "ok" ? companyCtx.companyId : void 0;
204187
+ const connectorActorsService = async (companyId) => opts.actors ? (await opts.actors.resolveCtx(companyId ?? currentCompanyId)).service : void 0;
204073
204188
  const wodrafts = opts.resolveWorkorderDrafts ? await opts.resolveWorkorderDrafts(currentCompanyId) : opts.workorderDrafts;
204074
204189
  const wodraftPlannerIssues = (opts.resolveWorkorderDraftPlannerIssues && await opts.resolveWorkorderDraftPlannerIssues(currentCompanyId)) ?? opts.workorderDraftPlannerIssues ?? defaultDraftPlannerIssuesStore;
204075
204190
  const engine = await resolveEngine(currentCompanyId);
@@ -206133,12 +206248,12 @@ ${composed}`;
206133
206248
  const actorCtx = await opts.resolveActorContext(body2.actorId).catch(() => null);
206134
206249
  if (actorCtx) {
206135
206250
  const { mkdtempSync: mkdtempSync6, mkdirSync: mkdirSync25, writeFileSync: writeFileSync18 } = await import("node:fs");
206136
- const { join: join39, dirname: dirname36 } = await import("node:path");
206251
+ const { join: join40, dirname: dirname36 } = await import("node:path");
206137
206252
  const { tmpdir: tmpdir12 } = await import("node:os");
206138
- const dir = mkdtempSync6(join39(tmpdir12(), "oasis-chat-"));
206253
+ const dir = mkdtempSync6(join40(tmpdir12(), "oasis-chat-"));
206139
206254
  if (actorCtx.config?.prompt) {
206140
206255
  for (const [rel, content3] of Object.entries(splitIdentityFiles2(actorCtx.config.prompt))) {
206141
- const file = join39(dir, rel);
206256
+ const file = join40(dir, rel);
206142
206257
  mkdirSync25(dirname36(file), { recursive: true });
206143
206258
  writeFileSync18(file, content3);
206144
206259
  }
@@ -206150,12 +206265,12 @@ ${composed}`;
206150
206265
  const s2 = byId.get(id);
206151
206266
  return s2 ? `- **${s2.name}** (\`${s2.id}\`): ${s2.description}` : `- \`${id}\`\uFF08\u672A\u5728\u6280\u80FD\u5E93\u4E2D\uFF0C\u53EF\u80FD\u5DF2\u5378\u8F7D\uFF09`;
206152
206267
  });
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"));
206268
+ 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
206269
  }
206155
206270
  if (opts.materializeSkills) {
206156
206271
  const skillFiles = await opts.materializeSkills(body2.actorId, "claude").catch(() => ({}));
206157
206272
  for (const [rel, content3] of Object.entries(skillFiles)) {
206158
- const file = join39(dir, rel);
206273
+ const file = join40(dir, rel);
206159
206274
  mkdirSync25(dirname36(file), { recursive: true });
206160
206275
  writeFileSync18(file, content3);
206161
206276
  }
@@ -206169,7 +206284,7 @@ ${composed}`;
206169
206284
  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
206285
  return `- **${c.name}** (\`${c.id}\`): ${statusNote} \xB7 ${modeNote}`;
206171
206286
  });
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"));
206287
+ 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
206288
  }
206174
206289
  spawnCwd = dir;
206175
206290
  }
@@ -206254,7 +206369,7 @@ ${composed}`;
206254
206369
  }
206255
206370
  if (req.method === "POST" && /^\/api\/connectors\/[^/]+\/disconnect$/.test(url.pathname)) {
206256
206371
  const connId = decodeURIComponent(url.pathname.split("/")[3] ?? "");
206257
- const svc = opts.actors?.service;
206372
+ const svc = await connectorActorsService();
206258
206373
  if (!svc) {
206259
206374
  res.writeHead(501).end(JSON.stringify({ error: "actors service disabled" }));
206260
206375
  return;
@@ -206280,7 +206395,7 @@ ${composed}`;
206280
206395
  }
206281
206396
  if (req.method === "DELETE" && /^\/api\/connectors\/[^/]+$/.test(url.pathname)) {
206282
206397
  const connId = decodeURIComponent(url.pathname.slice("/api/connectors/".length));
206283
- const svc = opts.actors?.service;
206398
+ const svc = await connectorActorsService();
206284
206399
  if (!svc) {
206285
206400
  res.writeHead(501).end(JSON.stringify({ error: "actors service disabled" }));
206286
206401
  return;
@@ -206299,6 +206414,31 @@ ${composed}`;
206299
206414
  }
206300
206415
  return;
206301
206416
  }
206417
+ if (req.method === "DELETE" && /^\/api\/actors\/[^/]+\/connectors\/[^/]+$/.test(url.pathname)) {
206418
+ const parts = url.pathname.split("/");
206419
+ const actorId = decodeURIComponent(parts[3] ?? "");
206420
+ const connId = decodeURIComponent(parts[5] ?? "");
206421
+ const svc = await connectorActorsService();
206422
+ if (!svc) {
206423
+ res.writeHead(501).end(JSON.stringify({ error: "actors service disabled" }));
206424
+ return;
206425
+ }
206426
+ try {
206427
+ const { variablesDeleted } = await svc.deleteActorConnector(actorId, connId);
206428
+ let channelDisabled = false;
206429
+ if (connId === "feishu" && channelService) {
206430
+ const b2 = await channelService.getBindingForActor(actorId);
206431
+ if (b2 && b2.status !== "revoked") {
206432
+ await channelService.disable(actorId);
206433
+ channelDisabled = true;
206434
+ }
206435
+ }
206436
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, variablesDeleted, channelDisabled }));
206437
+ } catch (e) {
206438
+ res.writeHead(500).end(JSON.stringify({ error: String(e) }));
206439
+ }
206440
+ return;
206441
+ }
206302
206442
  if (url.pathname === "/api/connectors/feishu/setup" && req.method === "GET") {
206303
206443
  res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
206304
206444
  const send = (obj2) => res.write(`data: ${JSON.stringify(obj2)}
@@ -206348,8 +206488,8 @@ ${composed}`;
206348
206488
  if (!errStr && pollData["client_id"]) {
206349
206489
  const clientId = String(pollData["client_id"]);
206350
206490
  const clientSecret = pollData["client_secret"] ? String(pollData["client_secret"]) : void 0;
206351
- if (opts.actors && clientSecret) {
206352
- const svc = opts.actors.service;
206491
+ const svc = clientSecret ? await connectorActorsService() : void 0;
206492
+ if (svc && clientSecret) {
206353
206493
  const credScope = actorIdParam ? { scope: "personal", actorId: actorIdParam } : { scope: "global" };
206354
206494
  await svc.putVariable({ key: "FEISHU_APP_ID", value: clientId, ...credScope, connectorId: "feishu", encrypted: true });
206355
206495
  await svc.putVariable({ key: "FEISHU_APP_SECRET", value: clientSecret, ...credScope, connectorId: "feishu", encrypted: true });
@@ -206412,6 +206552,8 @@ ${composed}`;
206412
206552
  });
206413
206553
  githubAppPending.put(state, {
206414
206554
  ...actorId ? { actorId } : {},
206555
+ // 回调那一步没有鉴权头,解析不出公司——只能在这里记下来带过去。
206556
+ ...currentCompanyId ? { companyId: currentCompanyId } : {},
206415
206557
  employeeSlug: nameSlug,
206416
206558
  createdAtMs: Date.now(),
206417
206559
  // 存下这次选的归属——回调时要拿它跟 GitHub 返回的 owner 比对。
@@ -206432,7 +206574,7 @@ ${composed}`;
206432
206574
  if (url.pathname === "/api/connectors/github/app/orgs" && req.method === "POST") {
206433
206575
  try {
206434
206576
  const { actorId } = JSON.parse(rawBody || "{}");
206435
- const svc = opts.actors?.service;
206577
+ const svc = await connectorActorsService();
206436
206578
  if (!svc) throw new Error("actors service unavailable");
206437
206579
  const token = await svc.revealResolvedVariable("GITHUB_TOKEN", actorId);
206438
206580
  if (!token) {
@@ -206465,7 +206607,7 @@ ${composed}`;
206465
206607
  }
206466
206608
  if (url.pathname === "/api/connectors/github/app/available" && req.method === "POST") {
206467
206609
  try {
206468
- const svc = opts.actors?.service;
206610
+ const svc = await connectorActorsService();
206469
206611
  if (!svc) throw new Error("actors service unavailable");
206470
206612
  const rows = await svc.listVariables();
206471
206613
  const plain = (key, actorId) => rows.find((r) => r.key === key && r.actorId === actorId && !r.encrypted)?.maskedValue ?? "";
@@ -206528,7 +206670,7 @@ ${composed}`;
206528
206670
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "\u7F3A\u5C11 appId" }));
206529
206671
  return;
206530
206672
  }
206531
- const svc = opts.actors?.service;
206673
+ const svc = await connectorActorsService();
206532
206674
  if (!svc) throw new Error("actors service unavailable");
206533
206675
  const rows = await svc.listVariables();
206534
206676
  const owners = rows.filter((r) => r.key === "GITHUB_APP_ID" && !r.encrypted && r.maskedValue === appId);
@@ -206575,7 +206717,7 @@ ${composed}`;
206575
206717
  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
206718
  return;
206577
206719
  }
206578
- const svc = opts.actors?.service;
206720
+ const svc = await connectorActorsService();
206579
206721
  if (!svc) throw new Error("actors service unavailable");
206580
206722
  let meta;
206581
206723
  try {
@@ -206633,7 +206775,7 @@ ${composed}`;
206633
206775
  if (url.pathname === "/api/connectors/github/app/permissions" && req.method === "POST") {
206634
206776
  try {
206635
206777
  const { actorId } = JSON.parse(rawBody || "{}");
206636
- const svc = opts.actors?.service;
206778
+ const svc = await connectorActorsService();
206637
206779
  if (!svc) throw new Error("actors service unavailable");
206638
206780
  const appId = await svc.revealResolvedVariable("GITHUB_APP_ID", actorId);
206639
206781
  const privateKeyPem = await svc.revealResolvedVariable("GITHUB_APP_PRIVATE_KEY", actorId);
@@ -206687,7 +206829,7 @@ ${composed}`;
206687
206829
  fail(problem.message);
206688
206830
  return;
206689
206831
  }
206690
- const svc = opts.actors?.service;
206832
+ const svc = await connectorActorsService(pending.companyId);
206691
206833
  if (!svc) throw new Error("actors service unavailable");
206692
206834
  const scope = pending.actorId ? { scope: "personal", actorId: pending.actorId } : { scope: "global" };
206693
206835
  const put3 = (key, value2, encrypted) => svc.putVariable({ key, value: value2, ...scope, connectorId: "github", encrypted });
@@ -206756,7 +206898,7 @@ ${composed}`;
206756
206898
  reply({ ok: false, error: "pending" });
206757
206899
  return;
206758
206900
  }
206759
- const svc = opts.actors?.service;
206901
+ const svc = await connectorActorsService();
206760
206902
  if (!svc) throw new Error("actors service unavailable");
206761
206903
  const scope = pending.actorId ? { scope: "personal", actorId: pending.actorId } : { scope: "global" };
206762
206904
  const put3 = (key, value2, encrypted) => svc.putVariable({ key, value: value2, ...scope, connectorId: "github", encrypted });
@@ -206845,8 +206987,8 @@ ${composed}`;
206845
206987
  throw new Error(String(data["error_description"] ?? errStr ?? "login failed"));
206846
206988
  }
206847
206989
  if (!data["access_token"]) throw new Error("no access_token in response");
206848
- if (opts.actors) {
206849
- const svc = opts.actors.service;
206990
+ const svc = await connectorActorsService();
206991
+ if (svc) {
206850
206992
  if (actorId) {
206851
206993
  await svc.putVariable({ key: "FEISHU_APP_ID", value: appId, scope: "personal", actorId, connectorId: "feishu", encrypted: true });
206852
206994
  } else {
@@ -206947,8 +207089,8 @@ ${composed}`;
206947
207089
  });
206948
207090
  const p2 = await pr.json();
206949
207091
  if (p2.access_token) {
206950
- if (opts.actors) {
206951
- const svc = opts.actors.service;
207092
+ const svc = await connectorActorsService();
207093
+ if (svc) {
206952
207094
  const connId = "github";
206953
207095
  await svc.upsertConnector({ id: connId, name: "GitHub", mode: "oauth", status: "connected", account: "github" });
206954
207096
  for (const [k2, v2] of [["GIT_AUTHOR_NAME", actorNameParam], ["GIT_AUTHOR_EMAIL", ""], ["GIT_COMMITTER_NAME", actorNameParam], ["GIT_COMMITTER_EMAIL", ""], ["EMAIL", ""]])
@@ -218233,9 +218375,32 @@ var init_service5 = __esm({
218233
218375
  const a = await this.opts.store.getActor(id);
218234
218376
  if (!a) throw new Error(`actor not found: ${id}`);
218235
218377
  await this.upsertActor({ ...a, status: "disabled" }, by);
218378
+ await this.dropRuntimeBindingOfDeletedAgent(a.id, a.kind, by);
218236
218379
  if (a.status === "active") await this.opts.onActorDisabled?.(id, by);
218237
218380
  return a;
218238
218381
  }
218382
+ /**
218383
+ * 删掉 Agent 时把它跟 runtime 的绑定一起撤掉。
218384
+ *
218385
+ * 为什么必须做(2026-09-09 线上实测):删除只写 `status='disabled'`,`actor_bindings` 那行
218386
+ * 原样留着且仍是 `active`。而 `GET /api/actors` 会把已删 Agent 过滤掉、`GET /api/bindings` 不会——
218387
+ * 运行时管理页拿两份数据对着算「运行 Agent」,于是已删的 Agent 继续占着一格,
218388
+ * 名字还查不回来,页面上直接露出 `asst-xxx-cbf34f` 这种裸 id。didi 组织实测:yx_claude 上
218389
+ * 12 条 active 绑定里 7 条的 Agent 早被删了(其中 5 条是助理)。
218390
+ *
218391
+ * 顺带修好另一处:`pickAssistantRuntime` 用「(nodeId,kind) 上的 active 绑定数」当负载做均衡,
218392
+ * 已删 Agent 的残留绑定会把负载算高,把新助理往别的机器上赶。
218393
+ *
218394
+ * 只对 agent 生效:真人被移出组织是「离岗」不是删除,PRD 明确其助理照常在岗、账号回来还能接上。
218395
+ * 幂等:removeBinding 对没有绑定的 actor 是 no-op,所以重复删也安全(存量脏数据也能靠再删一次修好)。
218396
+ */
218397
+ async dropRuntimeBindingOfDeletedAgent(id, kind, by) {
218398
+ if (kind !== "agent") return;
218399
+ const existing = await this.opts.store.getBinding(id);
218400
+ if (!existing) return;
218401
+ await this.opts.store.removeBinding(id);
218402
+ await this.audit({ kind: "binding_change", actorId: id, by, at: this.now(), detail: { removed: true, reason: "agent_deleted" } });
218403
+ }
218239
218404
  /**
218240
218405
  * 原子「若当前 active 则停用」(QA R10):与 disableActor 的区别在于——
218241
218406
  * 只在 actor 当前**确为 active** 时才写,返回 `ok:true + before + seq`;已被并发者/别的路径先 disable
@@ -218259,6 +218424,7 @@ var init_service5 = __esm({
218259
218424
  if (!res.ok) return res;
218260
218425
  this.opts.onRolesChanged?.(id, []);
218261
218426
  await this.audit({ kind: "actor_upsert", actorId: id, by, at: this.now(), detail: { status: "disabled", roles: res.before.roles } });
218427
+ await this.dropRuntimeBindingOfDeletedAgent(id, res.before.kind, by);
218262
218428
  await this.opts.onActorDisabled?.(id, by);
218263
218429
  return { ok: true, before: res.before, seq };
218264
218430
  }
@@ -218859,39 +219025,42 @@ ${input.description}
218859
219025
  const c = cfg ?? await this.opts.store.latestConfig(actorId);
218860
219026
  return new Set(c?.connectorIds ?? []);
218861
219027
  }
218862
- /** 设置某员工×连接器的连接记录(启用/停用)。无则创建,有则翻转 enabled。 */
218863
- async setActorConnectorEnabled(actorId, connectorId, enabled) {
218864
- const conn = { actorId, connectorId, enabled, updatedAt: this.now() };
219028
+ /**
219029
+ * 设置某员工×连接器的连接记录(启用/停用)。无则创建,有则翻转 enabled
219030
+ *
219031
+ * `configuredBy` 只在**授权流程走完**时传(「谁为这名员工把它接上的」)。普通开关不传,
219032
+ * 此时把已有值原样带回——**只增不抹**:翻一次开关不该把授权时记下的人擦掉。
219033
+ * pg 侧另有 `COALESCE` 兜同一条,两层都做是因为内存 store 是整行替换。
219034
+ */
219035
+ async setActorConnectorEnabled(actorId, connectorId, enabled, configuredBy) {
219036
+ const keep = configuredBy ?? (await this.opts.store.listActorConnectorConnections(actorId)).find((c) => c.connectorId === connectorId)?.configuredBy;
219037
+ const conn = {
219038
+ actorId,
219039
+ connectorId,
219040
+ enabled,
219041
+ updatedAt: this.now(),
219042
+ ...keep ? { configuredBy: keep } : {}
219043
+ };
218865
219044
  await this.opts.store.upsertActorConnectorConnection(conn);
218866
219045
  return conn;
218867
219046
  }
218868
- listActorConnectorConnections(actorId) {
218869
- return this.opts.store.listActorConnectorConnections(actorId);
218870
- }
218871
219047
  /**
218872
- * 移除某员工与某连接器的连接(组织页连接器详情的「移除连接」,2026-09-09 原型 1600:5765)。
219048
+ * 删除**一条**「员工 × 连接器」连接:连接记录 + 该员工这个连接器的**个人凭据变量**。
218873
219049
  *
218874
- * 删两样东西,缺一不可:
218875
- * 这名员工**自己**那份连接器凭据(`scope=personal` 且 `connectorId` 命中的变量行)——
218876
- * 不删的话,「移除」之后 `actorConnected` 里还有它,卡片照样在,读作「删不掉」;
218877
- * ② 「员工×连接器」的连接记录——不删的话 `effective` 里还有它(enabled=false),
218878
- * 卡片变成一张「已停用」的僵尸卡,而人要的是它消失。
219050
+ * 与开关的区别(发起人 2026-09-09 定的,两颗按钮并存):开关是「暂时不用」,凭据留着、
219051
+ * 打开就能接着用;删除是「不要了」,凭据清掉、再要用得重走一遍授权。
218879
219052
  *
218880
- * **不碰组织那条 connector 行**(决策 0080 修订五的同一条理由):组织级连接是别人配的资产,
218881
- * 一名员工点「移除连接」不该把全组织的连接拆掉。组织级的断开在「管理 > 连接器」。
218882
- * 于是:组织已连接时,移除个人连接后这名员工会**回落到组织默认**——卡片仍在,但身份那一行
218883
- * 变回组织账号。这是对的,不是没删干净。
218884
- *
218885
- * 幂等:没有个人凭据、没有记录时照样返回成功(`variablesDeleted: 0`)——重复点、并发点都不该报错。
219053
+ * **只动这名员工自己的东西**:组织级变量(scope=global)一个不碰——那是别人也在用的。
219054
+ * 飞书对话通道的下线不在这里做(本服务够不到 channelService),由路由层在删完凭据后补一刀。
218886
219055
  */
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);
219056
+ async deleteActorConnector(actorId, connectorId) {
219057
+ const personal = (await this.opts.store.listVariables(actorId)).filter((v2) => v2.scope === "personal" && v2.actorId === actorId && v2.connectorId === connectorId);
219058
+ for (const v2 of personal) await this.deleteVariable(v2.key, actorId);
218893
219059
  await this.opts.store.deleteActorConnectorConnection(actorId, connectorId);
218894
- return { removed: true, variablesDeleted: mine.length };
219060
+ return { variablesDeleted: personal.length };
219061
+ }
219062
+ listActorConnectorConnections(actorId) {
219063
+ return this.opts.store.listActorConnectorConnections(actorId);
218895
219064
  }
218896
219065
  /**
218897
219066
  * 员工连接器面板所需状态:连接记录 + 组织级已连接集合 + 该员工已授权(有个人 connector 变量)集合。
@@ -220860,14 +221029,9 @@ function actorsDomain(opts) {
220860
221029
  const { service } = await resolveCtx(req.auth.companyId);
220861
221030
  const b2 = req.body;
220862
221031
  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);
221032
+ 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
221033
  return { status: 200, body: conn };
220865
221034
  });
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
221035
  const requireVariableManager = (req) => {
220872
221036
  if (!isHumanActor(req.auth.actor)) {
220873
221037
  throw new ApiError(
@@ -226090,7 +226254,13 @@ function reviewSummary(snap, reviews, events) {
226090
226254
  const name = node2?.title ?? first.nodeId;
226091
226255
  const latest = latestActivityReviews(reviews, events);
226092
226256
  const requirements = snap.requirements.filter((r) => r.nodeId === first.nodeId && r.reviewerActorId === first.reviewerActorId);
226093
- const judgement = judgeWork({ requirements: requirements.length ? requirements : reviews, reviews });
226257
+ for (const requirement of requirements) {
226258
+ const current = reviews.find((r) => r.id === requirement.latestReviewId);
226259
+ if (!current) continue;
226260
+ const index2 = latest.findIndex((r) => r.reviewGroup === requirement.reviewGroup);
226261
+ if (index2 >= 0) latest[index2] = current;
226262
+ }
226263
+ const judgement = judgeWork({ requirements: requirements.length ? requirements : latest, reviews: latest });
226094
226264
  const pending = latest.filter((r) => !r.cancelledAt && !r.verdict && reviewState(r) === "running");
226095
226265
  if (work?.acceptanceState === "accepted" || work?.acceptedAt || work?.acceptanceState !== "rejected" && judgement === "passed") {
226096
226266
  return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
@@ -226126,6 +226296,66 @@ var init_review_activity = __esm({
226126
226296
  }
226127
226297
  });
226128
226298
 
226299
+ // ../server/src/domains/collab/review-requirement-activity.ts
226300
+ function reviewRequirementActivity(snap, events, ref2) {
226301
+ const previous3 = /* @__PURE__ */ new Map();
226302
+ const titles = new Map(snap.nodes.map((n) => [n.id, n.title]));
226303
+ const cards = [];
226304
+ const label = (r) => `${ref2(r.reviewerActorId).name || "\u672A\u547D\u540D\u5BA1\u6838\u4EBA"}${r.source === "closure" ? "\uFF08\u7ED3\u6848\u5BA1\u6838\uFF09" : ""}`;
226305
+ const names = (rows) => [...new Set(rows.map(label))].join("\u3001") || "\u65E0";
226306
+ for (const rec of [...events].sort((a, b2) => a.seq - b2.seq)) {
226307
+ if (rec.status === "pending" || rec.status === "failed") continue;
226308
+ const e = rec.event;
226309
+ if (e.kind === "plan.changed") {
226310
+ for (const node2 of e.addNodes ?? []) previous3.set(node2.id, node2.reviewers ?? []);
226311
+ for (const id of e.removeNodes ?? []) previous3.delete(id);
226312
+ continue;
226313
+ }
226314
+ if (e.kind === "plan.add_node") {
226315
+ previous3.set(e.nodeId, e.reviewers ?? []);
226316
+ continue;
226317
+ }
226318
+ if (e.kind !== "plan.update_review_requirements") continue;
226319
+ const before = previous3.get(e.nodeId);
226320
+ const after = e.reviewers;
226321
+ const beforeKeys = new Set(before?.map(requirementKey));
226322
+ const afterKeys = new Set(after.map(requirementKey));
226323
+ const added = before ? after.filter((r) => !beforeKeys.has(requirementKey(r))) : [];
226324
+ const removed = before?.filter((r) => !afterKeys.has(requirementKey(r))) ?? [];
226325
+ const nodeTitle = titles.get(e.nodeId) || "\u5DF2\u79FB\u9664\u7684\u8282\u70B9";
226326
+ const system = rec.actorId.startsWith("actor:system:");
226327
+ const closureAdded = system && added.length > 0 && !removed.length && added.every((r) => r.source === "closure");
226328
+ cards.push({
226329
+ id: `wo:${rec.seq}`,
226330
+ seq: rec.seq,
226331
+ at: rec.createdAt,
226332
+ updatedAt: rec.createdAt,
226333
+ nodeId: e.nodeId,
226334
+ nodeTitle,
226335
+ executor: system ? { ...ref2(rec.actorId), name: "\u7CFB\u7EDF" } : ref2(rec.actorId),
226336
+ ...rec.handActorId ? { handActor: ref2(rec.handActorId) } : {},
226337
+ phase: "done",
226338
+ 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`,
226339
+ detail: [
226340
+ ...added.length ? [`\u65B0\u589E\uFF1A${names(added)}`] : [],
226341
+ ...removed.length ? [`\u79FB\u9664\uFF1A${names(removed)}`] : [],
226342
+ `\u66F4\u65B0\u540E\uFF1A${names(after)}`
226343
+ ].join("\n"),
226344
+ artifacts: [],
226345
+ actions: []
226346
+ });
226347
+ previous3.set(e.nodeId, after);
226348
+ }
226349
+ return cards;
226350
+ }
226351
+ var requirementKey;
226352
+ var init_review_requirement_activity = __esm({
226353
+ "../server/src/domains/collab/review-requirement-activity.ts"() {
226354
+ "use strict";
226355
+ requirementKey = (r) => JSON.stringify([r.reviewerActorId, r.reviewGroup, r.source]);
226356
+ }
226357
+ });
226358
+
226129
226359
  // ../server/src/domains/collab/workorder-manager.ts
226130
226360
  function resolveWorkorderManager(snap) {
226131
226361
  const nodes = snap.nodes;
@@ -226806,6 +227036,8 @@ function buildWorkorderActivity(input) {
226806
227036
  .../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(traced ? runIdOfCard(d) : void 0)
226807
227037
  };
226808
227038
  });
227039
+ cards.push(...reviewRequirementActivity(snap, events, ref2));
227040
+ cards.sort((a, b2) => a.updatedAt.localeCompare(b2.updatedAt) || a.seq - b2.seq || a.id.localeCompare(b2.id));
226809
227041
  return { workorderId, cards, truncated };
226810
227042
  }
226811
227043
  var ACTIVITY_EVENT_KINDS, ACTIVITY_EVENT_LIMIT, ACTIVITY_COPY, REWORK_ANNOTATION_PREFIX, ENGINE_RETRY_EXHAUSTED, BUTTON;
@@ -226815,9 +227047,12 @@ var init_activity2 = __esm({
226815
227047
  init_src();
226816
227048
  init_src4();
226817
227049
  init_review_activity();
227050
+ init_review_requirement_activity();
226818
227051
  init_workorder_manager();
226819
227052
  ACTIVITY_EVENT_KINDS = [
226820
227053
  "plan.changed",
227054
+ "plan.add_node",
227055
+ "plan.update_review_requirements",
226821
227056
  "plan.update_spec",
226822
227057
  "plan.node_retry",
226823
227058
  "work.create",
@@ -228045,9 +228280,18 @@ function toAuditEntry(op) {
228045
228280
  summary: `${KIND_LABEL2[op.kind] ?? op.kind} \xB7 ${op.artifactId}`
228046
228281
  };
228047
228282
  }
228283
+ function nodeIdOfWorkId(workId) {
228284
+ if (!workId || !workId.startsWith("wk:")) return null;
228285
+ const body2 = workId.slice(3);
228286
+ const cut = body2.lastIndexOf(":");
228287
+ if (cut <= 0) return null;
228288
+ if (!/^\d+$/.test(body2.slice(cut + 1))) return null;
228289
+ const nodeId = body2.slice(0, cut);
228290
+ return nodeId.startsWith("artifact:") ? nodeId : null;
228291
+ }
228048
228292
  function eventToAuditEntry(rec) {
228049
228293
  const ev = rec.event;
228050
- const artifactId = rec.nodeId ?? rec.workId ?? "";
228294
+ const artifactId = rec.nodeId ?? nodeIdOfWorkId(rec.workId) ?? "";
228051
228295
  return {
228052
228296
  seq: rec.seq,
228053
228297
  at: rec.createdAt,
@@ -228057,7 +228301,11 @@ function eventToAuditEntry(rec) {
228057
228301
  summary: `${KIND_LABEL2[ev.kind] ?? ev.kind}${artifactId ? ` \xB7 ${artifactId}` : ""}`
228058
228302
  };
228059
228303
  }
228060
- function enrichAudit(entry, model, titleOf) {
228304
+ function enrichAudit(entry, model, titleOf, workorderId) {
228305
+ if (!entry.artifactId) {
228306
+ const workspaceTitle2 = workorderId ? titleOf.get(workorderId) : void 0;
228307
+ return { ...entry, ...workspaceTitle2 ? { workspaceTitle: workspaceTitle2 } : {}, visibility: "ok" };
228308
+ }
228061
228309
  const a = model.artifacts.get(entry.artifactId);
228062
228310
  if (!a) return { ...entry, visibility: "deleted" };
228063
228311
  const workspaceTitle = titleOf.get(a.workspace);
@@ -228075,8 +228323,8 @@ async function buildAudit(source, q2 = {}, model) {
228075
228323
  const items = [];
228076
228324
  let lastCursor = null;
228077
228325
  let truncated = false;
228078
- const push2 = (entry, cursor) => {
228079
- items.push(model && titleOf ? enrichAudit(entry, model, titleOf) : entry);
228326
+ const push2 = (entry, cursor, workorderId) => {
228327
+ items.push(model && titleOf ? enrichAudit(entry, model, titleOf, workorderId) : entry);
228080
228328
  lastCursor = cursor;
228081
228329
  };
228082
228330
  const match = (actor, kind, at) => {
@@ -228087,30 +228335,33 @@ async function buildAudit(source, q2 = {}, model) {
228087
228335
  return true;
228088
228336
  };
228089
228337
  if (q2.latest) {
228090
- let collected = [];
228338
+ const collected = [];
228091
228339
  if (source.engineStore) {
228092
228340
  const records = await source.engineStore.transaction((tx) => tx.listEvents(void 0, 0));
228093
228341
  for (const rec of records) {
228094
228342
  const ev = rec.event;
228095
228343
  if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
228096
- collected.push(eventToAuditEntry(rec));
228344
+ collected.push({ entry: eventToAuditEntry(rec), workorderId: rec.workorderId });
228097
228345
  }
228098
228346
  } else if (source.oplog) {
228099
228347
  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
228348
  for await (const { op } of src) {
228101
228349
  if (!match(op.actor, op.kind, op.timestamp)) continue;
228102
- collected.push(toAuditEntry(op));
228350
+ collected.push({ entry: toAuditEntry(op) });
228103
228351
  }
228104
228352
  }
228105
228353
  const tail = collected.slice(-limit).reverse();
228106
- return { items: model && titleOf ? tail.map((e) => enrichAudit(e, model, titleOf)) : tail, cursor: null };
228354
+ return {
228355
+ items: model && titleOf ? tail.map(({ entry, workorderId }) => enrichAudit(entry, model, titleOf, workorderId)) : tail.map(({ entry }) => entry),
228356
+ cursor: null
228357
+ };
228107
228358
  }
228108
228359
  if (source.engineStore) {
228109
228360
  const records = await source.engineStore.transaction((tx) => tx.listEvents(void 0, fromSeq));
228110
228361
  for (const rec of records) {
228111
228362
  const ev = rec.event;
228112
228363
  if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
228113
- push2(eventToAuditEntry(rec), String(rec.seq));
228364
+ push2(eventToAuditEntry(rec), String(rec.seq), rec.workorderId);
228114
228365
  if (items.length >= limit) {
228115
228366
  truncated = true;
228116
228367
  break;
@@ -228156,6 +228407,30 @@ var init_audit = __esm({
228156
228407
  "workorder.paused": "\u6682\u505C\u6D3E\u53D1",
228157
228408
  "workorder.resumed": "\u6062\u590D\u6D3E\u53D1",
228158
228409
  "workorder.sealed": "\u5C01\u5B58",
228410
+ // 其余新引擎事件 kind——缺标签时 summary 直接露英文 kind(生产实测:`plan.changed` 是量最大的
228411
+ // 一类审计条目,界面上就写着「plan.changed」)。这张表按 EngineEvent 的 kind 全集补齐。
228412
+ "plan.changed": "\u6539\u4EFB\u52A1\u56FE",
228413
+ "plan.add_edge": "\u63A5\u4F9D\u8D56",
228414
+ "plan.delete_edge": "\u65AD\u4F9D\u8D56",
228415
+ "plan.update_review_requirements": "\u6539\u8BC4\u5BA1\u8981\u6C42",
228416
+ "plan.update_fields": "\u6539\u5B57\u6BB5",
228417
+ "work.started": "\u5F00\u5DE5",
228418
+ "work.redispatch": "\u91CD\u6D3E",
228419
+ "work.snapshot_recorded": "\u5B58\u5FEB\u7167",
228420
+ "work.handoff_recorded": "\u8BB0\u4EA4\u63A5",
228421
+ "work.reject": "\u6253\u56DE",
228422
+ "work.content_edited": "\u6539\u5185\u5BB9",
228423
+ "work.conclude": "\u5B9A\u7A3F",
228424
+ "node.latest_proposed.rebased": "\u6362\u57FA\u7EBF",
228425
+ "review.started": "\u5F00\u59CB\u8BC4\u5BA1",
228426
+ "review.kill": "\u64A4\u8BC4\u5BA1",
228427
+ "review.timeout": "\u8BC4\u5BA1\u8D85\u65F6",
228428
+ "issue.resolution_proposed": "\u63D0\u89E3\u51B3\u65B9\u6848",
228429
+ "issue.resolution_rejected": "\u9A73\u56DE\u89E3\u51B3\u65B9\u6848",
228430
+ "workorder.created": "\u5EFA\u5DE5\u5355",
228431
+ "workorder.root_set": "\u5B9A\u6839\u8282\u70B9",
228432
+ "workorder.meta_changed": "\u6539\u5DE5\u5355\u4FE1\u606F",
228433
+ "workorder.ping": "\u5524\u9192",
228159
228434
  // 旧 op kind(oplog 回退路径)
228160
228435
  spawn_artifact: "\u5EFA\u4EA7\u7269",
228161
228436
  propose_revision: "\u63D0\u4FEE\u8BA2",
@@ -237750,6 +238025,11 @@ var init_daemon_adapter = __esm({
237750
238025
  clearTimeout(entry.ackTimer);
237751
238026
  entry.ackTimer = void 0;
237752
238027
  }
238028
+ const reapTimer = this.sessionReapTimers.get(dispatchId);
238029
+ if (reapTimer) {
238030
+ clearTimeout(reapTimer);
238031
+ this.sessionReapTimers.delete(dispatchId);
238032
+ }
237753
238033
  entry.exited = info;
237754
238034
  for (const w2 of entry.appendWaiters.splice(0)) {
237755
238035
  clearTimeout(w2.timer);
@@ -237845,6 +238125,10 @@ var init_daemon_adapter = __esm({
237845
238125
  for (const [dispatchId, entry] of this.pending) {
237846
238126
  if (entry.nodeId !== daemonId || entry.exited || present.has(dispatchId)) continue;
237847
238127
  if (this.hub.hasSession?.(dispatchId)) continue;
238128
+ if (this.sessionReapTimers.has(dispatchId)) {
238129
+ 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`);
238130
+ continue;
238131
+ }
237848
238132
  absent.push(dispatchId);
237849
238133
  }
237850
238134
  for (const dispatchId of absent) {
@@ -237877,8 +238161,7 @@ var init_daemon_adapter = __esm({
237877
238161
  */
237878
238162
  onSessionDown(dispatchId) {
237879
238163
  if (this.sessionReapTimers.has(dispatchId)) return;
237880
- const entry = this.pending.get(dispatchId);
237881
- if (!entry || entry.exited) return;
238164
+ if (this.settledDispatchIds.has(dispatchId)) return;
237882
238165
  const timer = setTimeout(() => {
237883
238166
  this.sessionReapTimers.delete(dispatchId);
237884
238167
  const e = this.pending.get(dispatchId);
@@ -239214,6 +239497,7 @@ ${ctx.nodeFault}
239214
239497
  env: { ...prov?.env ?? {}, OASIS_STAGE: "1", ...workspace ? { OASIS_WORKSPACE: workspace } : {} },
239215
239498
  ...prov?.wrapperPaths && prov.wrapperPaths.length > 0 ? { wrapperPaths: prov.wrapperPaths } : {},
239216
239499
  ...prov?.requiredTools && prov.requiredTools.length > 0 ? { requiredTools: prov.requiredTools } : {},
239500
+ ...prov?.requiredToolVersions && Object.keys(prov.requiredToolVersions).length > 0 ? { requiredToolVersions: prov.requiredToolVersions } : {},
239217
239501
  ...prov?.connectorCreds && prov.connectorCreds.length > 0 ? { connectorCreds: prov.connectorCreds } : {}
239218
239502
  };
239219
239503
  this.deps.log?.(`[coordinator] ${logMsg}`);
@@ -239774,6 +240058,7 @@ var init_postgres_registry = __esm({
239774
240058
  updated_at timestamptz NOT NULL,
239775
240059
  PRIMARY KEY (actor_id, connector_id)
239776
240060
  )`);
240061
+ await pool.query(`ALTER TABLE "${s2}".actor_connector_connections ADD COLUMN IF NOT EXISTS configured_by text`);
239777
240062
  await pool.query(`
239778
240063
  CREATE TABLE IF NOT EXISTS "${s2}".skill_catalog (
239779
240064
  id text PRIMARY KEY, -- slug
@@ -240161,10 +240446,11 @@ var init_postgres_registry = __esm({
240161
240446
  }
240162
240447
  async upsertActorConnectorConnection(c) {
240163
240448
  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]
240449
+ `INSERT INTO ${this.s}.actor_connector_connections (actor_id, connector_id, enabled, updated_at, configured_by)
240450
+ VALUES ($1, $2, $3, $4, $5)
240451
+ ON CONFLICT (actor_id, connector_id) DO UPDATE SET enabled = $3, updated_at = $4,
240452
+ configured_by = COALESCE($5, ${this.s}.actor_connector_connections.configured_by)`,
240453
+ [c.actorId, c.connectorId, c.enabled, c.updatedAt, c.configuredBy ?? null]
240168
240454
  );
240169
240455
  }
240170
240456
  async listActorConnectorConnections(actorId) {
@@ -240176,7 +240462,8 @@ var init_postgres_registry = __esm({
240176
240462
  actorId: row.actor_id,
240177
240463
  connectorId: row.connector_id,
240178
240464
  enabled: row.enabled,
240179
- updatedAt: new Date(row.updated_at).toISOString()
240465
+ updatedAt: new Date(row.updated_at).toISOString(),
240466
+ ...row.configured_by ? { configuredBy: row.configured_by } : {}
240180
240467
  }));
240181
240468
  }
240182
240469
  async listAllActorConnectorConnections() {
@@ -240187,7 +240474,8 @@ var init_postgres_registry = __esm({
240187
240474
  actorId: row.actor_id,
240188
240475
  connectorId: row.connector_id,
240189
240476
  enabled: row.enabled,
240190
- updatedAt: new Date(row.updated_at).toISOString()
240477
+ updatedAt: new Date(row.updated_at).toISOString(),
240478
+ ...row.configured_by ? { configuredBy: row.configured_by } : {}
240191
240479
  }));
240192
240480
  }
240193
240481
  async deleteActorConnectorConnection(actorId, connectorId) {
@@ -255201,6 +255489,98 @@ var init_chat_builtin_skills = __esm({
255201
255489
  }
255202
255490
  });
255203
255491
 
255492
+ // ../server/src/governance/connector-tool-versions.ts
255493
+ function connectorToolVersionsFile(dataDir) {
255494
+ return (0, import_node_path25.join)(dataDir, "connector-tools", "versions.json");
255495
+ }
255496
+ function registryBase() {
255497
+ const raw = process.env["npm_config_registry"] || process.env["NPM_CONFIG_REGISTRY"] || "https://registry.npmjs.org";
255498
+ return raw.replace(/\/+$/, "");
255499
+ }
255500
+ async function fetchLatestNpmVersion(pkg) {
255501
+ try {
255502
+ const url = `${registryBase()}/${pkg.split("/").map(encodeURIComponent).join("%2F")}/latest`;
255503
+ const res = await fetch(url, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(NPM_PROBE_TIMEOUT_MS) });
255504
+ if (!res.ok) return null;
255505
+ const v2 = (await res.json()).version;
255506
+ return typeof v2 === "string" && v2 ? v2 : null;
255507
+ } catch {
255508
+ return null;
255509
+ }
255510
+ }
255511
+ async function readRegisteredToolVersions(dataDir) {
255512
+ try {
255513
+ const raw = JSON.parse(await (0, import_promises14.readFile)(connectorToolVersionsFile(dataDir), "utf8"));
255514
+ if (!raw || typeof raw !== "object") return {};
255515
+ const out = {};
255516
+ for (const [k2, v2] of Object.entries(raw)) {
255517
+ if (typeof v2 === "string" && v2) out[k2] = v2;
255518
+ }
255519
+ return out;
255520
+ } catch {
255521
+ return {};
255522
+ }
255523
+ }
255524
+ async function writeRegisteredToolVersions(dataDir, versions) {
255525
+ const file = connectorToolVersionsFile(dataDir);
255526
+ await (0, import_promises14.mkdir)((0, import_node_path25.join)(dataDir, "connector-tools"), { recursive: true });
255527
+ const tmp = `${file}.${process.pid}.tmp`;
255528
+ await (0, import_promises14.writeFile)(tmp, `${JSON.stringify(versions, null, 2)}
255529
+ `, "utf8");
255530
+ await (0, import_promises14.rename)(tmp, file);
255531
+ }
255532
+ async function refreshConnectorToolVersions(opts) {
255533
+ const fetchLatest = opts.fetchLatest ?? fetchLatestNpmVersion;
255534
+ const log3 = opts.log ?? (() => {
255535
+ });
255536
+ const versions = await readRegisteredToolVersions(opts.dataDir);
255537
+ const results = [];
255538
+ let changed = false;
255539
+ for (const [tool, pkg] of Object.entries(NPM_TOOL_PACKAGES)) {
255540
+ const before = versions[tool];
255541
+ const latest = await fetchLatest(pkg);
255542
+ if (!latest) {
255543
+ results.push({ tool, pkg, ...before ? { from: before, to: before } : {}, outcome: "unreachable" });
255544
+ log3(`[connector-tools] ${pkg} \u95EE\u4E0D\u5230\u6700\u65B0\u7248\uFF0C\u6CBF\u7528\u767B\u8BB0\u7248\u672C ${before ?? "\uFF08\u65E0\uFF09"}`);
255545
+ continue;
255546
+ }
255547
+ if (before === latest) {
255548
+ results.push({ tool, pkg, from: before, to: latest, outcome: "unchanged" });
255549
+ continue;
255550
+ }
255551
+ versions[tool] = latest;
255552
+ changed = true;
255553
+ results.push({ tool, pkg, ...before ? { from: before } : {}, to: latest, outcome: "updated" });
255554
+ log3(`[connector-tools] ${pkg} \u767B\u8BB0\u7248\u672C ${before ?? "\uFF08\u65E0\uFF09"} \u2192 ${latest}`);
255555
+ }
255556
+ if (changed) {
255557
+ try {
255558
+ await writeRegisteredToolVersions(opts.dataDir, versions);
255559
+ } catch (err) {
255560
+ log3(`[connector-tools] \u767B\u8BB0\u7248\u672C\u843D\u76D8\u5931\u8D25\uFF08\u672C\u8FDB\u7A0B\u5185\u4ECD\u751F\u6548\uFF09\uFF1A${String(err)}`);
255561
+ }
255562
+ }
255563
+ return { versions, results };
255564
+ }
255565
+ function pickToolVersions(registered, tools) {
255566
+ const out = {};
255567
+ for (const t of tools) {
255568
+ const v2 = registered[t];
255569
+ if (v2) out[t] = v2;
255570
+ }
255571
+ return out;
255572
+ }
255573
+ var import_promises14, import_node_path25, NPM_PROBE_TIMEOUT_MS;
255574
+ var init_connector_tool_versions = __esm({
255575
+ "../server/src/governance/connector-tool-versions.ts"() {
255576
+ "use strict";
255577
+ import_promises14 = require("node:fs/promises");
255578
+ import_node_path25 = require("node:path");
255579
+ init_src8();
255580
+ NPM_PROBE_TIMEOUT_MS = 3e3;
255581
+ }
255582
+ });
255583
+
255204
255584
  // ../server/src/design/board-watch.ts
255205
255585
  function decideBoardLetter(c, nowMs, quietMs) {
255206
255586
  if (c.hasOpenWatchLetter) return false;
@@ -255996,6 +256376,7 @@ var init_src11 = __esm({
255996
256376
  init_builtin_skills();
255997
256377
  init_chat_builtin_skills();
255998
256378
  init_connector_skills();
256379
+ init_connector_tool_versions();
255999
256380
  init_board_watch();
256000
256381
  init_ephemeral_project();
256001
256382
  init_run_settlement();
@@ -256858,6 +257239,7 @@ function materializeBuiltins(builtins, runtimeKind, opts) {
256858
257239
  var CONNECTOR_SKILL_SOURCES = [
256859
257240
  { connectorId: "feishu", baseUrl: FEISHU_WELL_KNOWN_SKILLS_BASE }
256860
257241
  ];
257242
+ var registeredToolVersions = {};
256861
257243
  async function readAllConnectorSkills(dataDir) {
256862
257244
  const out = [];
256863
257245
  for (const src of CONNECTOR_SKILL_SOURCES) {
@@ -256889,11 +257271,18 @@ async function refreshConnectorSkillsNow(dataDir, apply) {
256889
257271
  }
256890
257272
  }
256891
257273
  if (out.length > 0) apply(out);
257274
+ try {
257275
+ const { versions } = await refreshConnectorToolVersions({ dataDir, log: (m2) => console.log(m2) });
257276
+ registeredToolVersions = versions;
257277
+ } catch (err) {
257278
+ console.warn(`[serve] connector CLI \u767B\u8BB0\u7248\u672C\u5237\u65B0\u5931\u8D25\uFF0C\u6CBF\u7528\u4E0A\u4E00\u4EFD\uFF1A${String(err)}`);
257279
+ }
256892
257280
  return results;
256893
257281
  }
256894
257282
  function refreshConnectorSkillsInBackground(dataDir, apply) {
256895
257283
  void refreshConnectorSkillsNow(dataDir, apply);
256896
257284
  }
257285
+ var CONNECTOR_SKILL_REFRESH_MS = 24 * 60 * 6e4;
256897
257286
  var SESSION_TOKEN_GRACE_MS = 15 * 6e4;
256898
257287
  function makeResumeRetryProxy(first, spawnFallback) {
256899
257288
  const relay = () => {
@@ -257087,6 +257476,8 @@ async function buildActorProvision(service, actorId, scope, onBrokerRefused) {
257087
257476
  env,
257088
257477
  wrapperPaths: [oasisWrapperScript(), ...provision.wrapperPaths],
257089
257478
  requiredTools: provision.requiredTools,
257479
+ // 只挑本次真要用的那几个:job 里不塞无关工具的版本号。
257480
+ requiredToolVersions: pickToolVersions(registeredToolVersions, provision.requiredTools),
257090
257481
  connectorCreds: provision.connectorCreds,
257091
257482
  cleanup: provision.cleanup
257092
257483
  };
@@ -258275,10 +258666,17 @@ async function startServe(opts) {
258275
258666
  });
258276
258667
  console.log(`[serve] loaded ${builtinSkills.length} builtin skill(s): ${builtinSkills.map((s2) => s2.id).join(", ") || "(none)"}`);
258277
258668
  connectorSkills = await readAllConnectorSkills(opts.dir);
258669
+ registeredToolVersions = await readRegisteredToolVersions(opts.dir);
258278
258670
  console.log(`[serve] connector skills\uFF08\u843D\u76D8\u526F\u672C\uFF09\uFF1A${connectorSkills.length} \u4E2A`);
258279
258671
  refreshConnectorSkillsInBackground(opts.dir, (s2) => {
258280
258672
  connectorSkills = s2;
258281
258673
  });
258674
+ const connectorSkillTimer = setInterval(() => {
258675
+ refreshConnectorSkillsInBackground(opts.dir, (s2) => {
258676
+ connectorSkills = s2;
258677
+ });
258678
+ }, CONNECTOR_SKILL_REFRESH_MS);
258679
+ connectorSkillTimer.unref?.();
258282
258680
  if (defaultCompanyMigration.createdCompany) {
258283
258681
  const seededAt = (/* @__PURE__ */ new Date()).toISOString();
258284
258682
  for (const devActor of ["actor:human:yx"]) {
@@ -259148,6 +259546,7 @@ async function startServe(opts) {
259148
259546
  env: provision.env,
259149
259547
  wrapperPaths: provision.wrapperPaths,
259150
259548
  ...provision.requiredTools.length > 0 ? { requiredTools: provision.requiredTools } : {},
259549
+ ...Object.keys(provision.requiredToolVersions).length > 0 ? { requiredToolVersions: provision.requiredToolVersions } : {},
259151
259550
  ...provision.connectorCreds.length > 0 ? { connectorCreds: provision.connectorCreds } : {}
259152
259551
  });
259153
259552
  const chunks = [];
@@ -260133,7 +260532,7 @@ async function startServe(opts) {
260133
260532
  console.warn(`[trace] chat run ${traceRunId} \u7EED\u8D26\u5F02\u5E38: ${String(err)}`);
260134
260533
  });
260135
260534
  };
260136
- const provision = isolatedKnowledgeTurn ? { env: {}, wrapperPaths: [], requiredTools: [], connectorCreds: [], cleanup: async () => void 0 } : await buildActorProvision(actorService, actorId);
260535
+ const provision = isolatedKnowledgeTurn ? { env: {}, wrapperPaths: [], requiredTools: [], requiredToolVersions: {}, connectorCreds: [], cleanup: async () => void 0 } : await buildActorProvision(actorService, actorId);
260137
260536
  const actorCtx = await buildActorContext(actorService, actorId).catch(() => null);
260138
260537
  const chatResolvedModel = sessionModelOverride ?? actorCtx?.config?.model ?? void 0;
260139
260538
  const chatModel = chatResolvedModel ?? await registryStore.getRuntimeConfig(`runtime:${binding.nodeId}:${binding.runtimeKind}`).then((c) => c?.model).catch(() => void 0) ?? void 0;
@@ -260273,6 +260672,7 @@ async function startServe(opts) {
260273
260672
  },
260274
260673
  wrapperPaths: provision.wrapperPaths,
260275
260674
  ...provision.requiredTools.length > 0 ? { requiredTools: provision.requiredTools } : {},
260675
+ ...Object.keys(provision.requiredToolVersions).length > 0 ? { requiredToolVersions: provision.requiredToolVersions } : {},
260276
260676
  ...provision.connectorCreds.length > 0 ? { connectorCreds: provision.connectorCreds } : {},
260277
260677
  ...requiredConnectors.length > 0 ? { requiredConnectorSlugs: requiredConnectors } : {}
260278
260678
  });
@@ -262894,6 +263294,7 @@ ${nodeFault}` : "");
262894
263294
  if (gitTimer) clearInterval(gitTimer);
262895
263295
  if (coordTimer) clearInterval(coordTimer);
262896
263296
  if (livenessTimer) clearInterval(livenessTimer);
263297
+ clearInterval(connectorSkillTimer);
262897
263298
  if (orphanSweepTimer) clearInterval(orphanSweepTimer);
262898
263299
  for (const t of dropSweepTimers) clearInterval(t);
262899
263300
  if (deadlineClockTimer) clearInterval(deadlineClockTimer);
@@ -263092,7 +263493,7 @@ async function runSession(dispatchId, job, deps) {
263092
263493
  }
263093
263494
  }
263094
263495
  if (job.requiredTools?.length) {
263095
- const missing = await ensureConnectorTools(job.requiredTools);
263496
+ const missing = await ensureConnectorTools(job.requiredTools, { versions: job.requiredToolVersions });
263096
263497
  if (missing.length) log2("[node-cli]", ` connector tools still missing (agent may fail): ${missing.join(", ")}`);
263097
263498
  }
263098
263499
  const prepared = await prepareConnectorsForJob(job);
@@ -263495,7 +263896,7 @@ function detectRuntimes() {
263495
263896
 
263496
263897
  // ../cli/src/daemon/workdir-handler.ts
263497
263898
  var import_node_fs19 = __toESM(require("node:fs"), 1);
263498
- var import_node_path25 = __toESM(require("node:path"), 1);
263899
+ var import_node_path26 = __toESM(require("node:path"), 1);
263499
263900
  init_src6();
263500
263901
  init_src();
263501
263902
  var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
@@ -263514,10 +263915,10 @@ function isSensitiveSegment(segment) {
263514
263915
  }
263515
263916
  function relPathIsSensitive(rel) {
263516
263917
  if (!rel) return false;
263517
- return rel.split(import_node_path25.default.sep).some((seg) => seg.length > 0 && isSensitiveSegment(seg));
263918
+ return rel.split(import_node_path26.default.sep).some((seg) => seg.length > 0 && isSensitiveSegment(seg));
263518
263919
  }
263519
263920
  function withinBase(p2, base) {
263520
- return p2 === base || p2.startsWith(base + import_node_path25.default.sep);
263921
+ return p2 === base || p2.startsWith(base + import_node_path26.default.sep);
263521
263922
  }
263522
263923
  function normalizeRel(raw) {
263523
263924
  const trimmed = (raw ?? "").trim();
@@ -263534,7 +263935,7 @@ async function trustedCanonicalContainer(req, logicalBase, dirKind) {
263534
263935
  } catch {
263535
263936
  return null;
263536
263937
  }
263537
- return isLegacy ? import_node_path25.default.join(trustedRootReal, "oasis-chat-sessions") : import_node_path25.default.join(trustedRootReal, "sessions", dirKind);
263938
+ return isLegacy ? import_node_path26.default.join(trustedRootReal, "oasis-chat-sessions") : import_node_path26.default.join(trustedRootReal, "sessions", dirKind);
263538
263939
  }
263539
263940
  async function resolveWithinWorkdir(req) {
263540
263941
  const dirKind = sessionDirKind(req.runtimeKind);
@@ -263552,11 +263953,11 @@ async function resolveWithinWorkdir(req) {
263552
263953
  } catch {
263553
263954
  return { ok: false, code: "NOT_FOUND" };
263554
263955
  }
263555
- if (import_node_path25.default.dirname(base) !== canonicalContainer) return { ok: false, code: "PATH_ESCAPE" };
263956
+ if (import_node_path26.default.dirname(base) !== canonicalContainer) return { ok: false, code: "PATH_ESCAPE" };
263556
263957
  const rel = normalizeRel(req.path);
263557
- const requested = import_node_path25.default.resolve(base, rel);
263958
+ const requested = import_node_path26.default.resolve(base, rel);
263558
263959
  if (!withinBase(requested, base)) return { ok: false, code: "PATH_ESCAPE" };
263559
- const cleanRel = base === requested ? "" : import_node_path25.default.relative(base, requested);
263960
+ const cleanRel = base === requested ? "" : import_node_path26.default.relative(base, requested);
263560
263961
  if (relPathIsSensitive(cleanRel)) return { ok: false, code: "SENSITIVE" };
263561
263962
  let real;
263562
263963
  try {
@@ -263566,7 +263967,7 @@ async function resolveWithinWorkdir(req) {
263566
263967
  return { ok: false, code: "PATH_ESCAPE" };
263567
263968
  }
263568
263969
  if (!withinBase(real, base)) return { ok: false, code: "PATH_ESCAPE" };
263569
- const realRel = base === real ? "" : import_node_path25.default.relative(base, real);
263970
+ const realRel = base === real ? "" : import_node_path26.default.relative(base, real);
263570
263971
  if (relPathIsSensitive(realRel)) return { ok: false, code: "SENSITIVE" };
263571
263972
  return { ok: true, base, real };
263572
263973
  }
@@ -263602,7 +264003,7 @@ async function handleWorkdirList(req) {
263602
264003
  const slice = truncated ? visible.slice(0, WORKDIR_LIST_MAX_ENTRIES) : visible;
263603
264004
  const entries = [];
263604
264005
  for (const d of slice) {
263605
- const abs = import_node_path25.default.join(anchor, d.name);
264006
+ const abs = import_node_path26.default.join(anchor, d.name);
263606
264007
  try {
263607
264008
  const st = await import_node_fs19.default.promises.lstat(abs);
263608
264009
  if (st.isSymbolicLink()) continue;
@@ -263634,7 +264035,7 @@ async function verifyOpenedFd(fh, base, fallback) {
263634
264035
  const fdReal = await fdCanonicalPath(fh);
263635
264036
  if (fdReal === null) return { anchor: fallback };
263636
264037
  if (!withinBase(fdReal, base)) return { error: { ok: false, code: "PATH_ESCAPE" } };
263637
- const fdRel = base === fdReal ? "" : import_node_path25.default.relative(base, fdReal);
264038
+ const fdRel = base === fdReal ? "" : import_node_path26.default.relative(base, fdReal);
263638
264039
  if (relPathIsSensitive(fdRel)) return { error: { ok: false, code: "SENSITIVE" } };
263639
264040
  return { anchor: `/proc/self/fd/${fh.fd}` };
263640
264041
  }
@@ -263723,7 +264124,7 @@ function looksBinary(bytes2) {
263723
264124
  function contentTypeFor(absPath, bytes2) {
263724
264125
  const sniffed = sniffContentType(bytes2);
263725
264126
  if (sniffed) return sniffed;
263726
- const ext = import_node_path25.default.extname(absPath).toLowerCase();
264127
+ const ext = import_node_path26.default.extname(absPath).toLowerCase();
263727
264128
  if (EXT_CONTENT_TYPE[ext]) return EXT_CONTENT_TYPE[ext];
263728
264129
  return looksBinary(bytes2) ? "application/octet-stream" : "text/plain; charset=utf-8";
263729
264130
  }
@@ -264319,17 +264720,17 @@ var RuntimeRouterAdapter = class {
264319
264720
  // ../cli/src/daemon/reap-claude-projects.ts
264320
264721
  var import_node_fs20 = __toESM(require("node:fs"), 1);
264321
264722
  var import_node_os10 = __toESM(require("node:os"), 1);
264322
- var import_node_path26 = __toESM(require("node:path"), 1);
264723
+ var import_node_path27 = __toESM(require("node:path"), 1);
264323
264724
  function claudeProjectSlug(cwd) {
264324
264725
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
264325
264726
  }
264326
264727
  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");
264728
+ const configDir = process.env["CLAUDE_CONFIG_DIR"] || import_node_path27.default.join(import_node_os10.default.homedir(), ".claude");
264729
+ return import_node_path27.default.join(configDir, "projects");
264329
264730
  }
264330
264731
  function reapClaudeProjects(workdir, runtimeKind) {
264331
264732
  if (runtimeKind !== "claude" && runtimeKind !== "claude-code") return;
264332
- const dir = import_node_path26.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264733
+ const dir = import_node_path27.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264333
264734
  if (!import_node_fs20.default.existsSync(dir)) return;
264334
264735
  try {
264335
264736
  import_node_fs20.default.rmSync(dir, { recursive: true, force: true });
@@ -264338,7 +264739,7 @@ function reapClaudeProjects(workdir, runtimeKind) {
264338
264739
  }
264339
264740
  function measureClaudeProjects(workdir, runtimeKind) {
264340
264741
  if (runtimeKind !== "claude" && runtimeKind !== "claude-code") return 0;
264341
- const dir = import_node_path26.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264742
+ const dir = import_node_path27.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
264342
264743
  return dirSizeBytes2(dir);
264343
264744
  }
264344
264745
  function dirSizeBytes2(dir) {
@@ -264350,7 +264751,7 @@ function dirSizeBytes2(dir) {
264350
264751
  return 0;
264351
264752
  }
264352
264753
  for (const e of entries) {
264353
- const p2 = import_node_path26.default.join(dir, e.name);
264754
+ const p2 = import_node_path27.default.join(dir, e.name);
264354
264755
  if (e.isSymbolicLink()) continue;
264355
264756
  if (e.isDirectory()) {
264356
264757
  total += dirSizeBytes2(p2);
@@ -266761,7 +267162,7 @@ function fieldsFromFlags(flags, ownFlags) {
266761
267162
  }
266762
267163
  return Object.keys(fields).length > 0 ? fields : void 0;
266763
267164
  }
266764
- function buildStageOp(cmd, flags, positional, readFile8 = (file) => fs39.readFileSync(file, "utf8")) {
267165
+ function buildStageOp(cmd, flags, positional, readFile9 = (file) => fs39.readFileSync(file, "utf8")) {
266765
267166
  switch (cmd) {
266766
267167
  case "link":
266767
267168
  return {
@@ -266783,7 +267184,7 @@ function buildStageOp(cmd, flags, positional, readFile8 = (file) => fs39.readFil
266783
267184
  ...flags.get("title") !== void 0 ? { title: flags.get("title") } : {},
266784
267185
  ...(flags.get("brief") ?? flags.get("description")) !== void 0 ? { description: flags.get("brief") ?? flags.get("description") } : {},
266785
267186
  ...flags.get("input") !== void 0 ? { inputs: flags.get("input").split(",").map((to) => ({ to: to.trim() })) } : {},
266786
- ...partsFile !== void 0 ? { parts: JSON.parse(readFile8(partsFile)) } : {},
267187
+ ...partsFile !== void 0 ? { parts: JSON.parse(readFile9(partsFile)) } : {},
266787
267188
  ...fields !== void 0 ? { fields } : {}
266788
267189
  };
266789
267190
  }
@@ -270150,7 +270551,7 @@ function shimScript() {
270150
270551
  }
270151
270552
 
270152
270553
  // src/index.ts
270153
- var PKG_VERSION = true ? "2.2.7" : "dev";
270554
+ var PKG_VERSION = true ? "2.2.8" : "dev";
270154
270555
  var LOCAL_BIN = localBin();
270155
270556
  var NPM_PREFIX = npmPrefix();
270156
270557
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.7",
3
+ "version": "2.2.8",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"
@@ -26,6 +26,6 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "oasisRelease": {
29
- "sourceHead": "b470dcedc3aa56cd5921435d5d01b17a5cab8ed3"
29
+ "sourceHead": "6677c1551a7015b5a37a4fc4c3d7f7e7aaf395e6"
30
30
  }
31
31
  }