oasis_test_v2 2.2.15 → 2.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -25452,7 +25452,9 @@ var init_codex = __esm({
25452
25452
  const reasoningEffort = this.opts.reasoningEffort;
25453
25453
  return runProtocolSession(job, {
25454
25454
  bin: this.opts.codexBin ?? "codex",
25455
- args: ["app-server", "--stdio"],
25455
+ // exec 那条路:关掉登录 shell,否则 `~/.profile` 会把 `$HOME/.local/bin` 前置到
25456
+ // PATH 最前,压过注入的连接器 wrapper(理由与实证见 exec 分支里那段长注释)。
25457
+ args: ["app-server", "--stdio", "-c", "allow_login_shell=false"],
25456
25458
  extraArgs: this.opts.extraArgs,
25457
25459
  blockedFlags: CODEX_APP_SERVER_BLOCKED_FLAGS,
25458
25460
  workRoot: this.opts.workRoot,
@@ -25633,6 +25635,24 @@ ${task}` : task;
25633
25635
  // 打开 reasoning 摘要通道:默认 auto,让 codex 产出 reasoning item(→ thought → 前端思考块)。
25634
25636
  ...this.opts.reasoningSummary !== "none" ? ["-c", `model_reasoning_summary=${this.opts.reasoningSummary ?? "auto"}`] : [],
25635
25637
  ...this.opts.reasoningEffort ? ["-c", `model_reasoning_effort=${this.opts.reasoningEffort}`] : [],
25638
+ /* ★ 关掉登录 shell(2026-09-11)。
25639
+ codex 的 shell 工具默认把每条命令丢进 `/bin/bash -lc`——本机 codex 自己的
25640
+ thread_history 里逐字记着 `"command":"/bin/bash -lc \"pwd && rg …"`。
25641
+ 登录 shell 会重跑 `~/.profile`,而 Ubuntu 的 profile 里是
25642
+ `PATH="$HOME/.local/bin:$PATH"`(**前置**);于是任何装在 `~/.local/bin` 的同名 CLI
25643
+ 都会抢到注入的连接器 wrapper 前面。实测后果:飞书连接器凭据、连接记录全对,
25644
+ dev-agent(codex)跑 `lark-cli whoami` 却回 `not_configured`——wrapper 被绕过、
25645
+ 而隔离键 `LARKSUITE_CLI_CONFIG_DIR` 照旧生效指着空目录,两头落空。
25646
+ 同机同用户的 claude-code agent 一切正常,差别只有这一个 `-l`。
25647
+
25648
+ `allow_login_shell` 是 codex 的正式配置键(本机实测:给它传整数会报
25649
+ `invalid type: integer, expected a boolean in \`allow_login_shell\``,
25650
+ 而不存在的键是被静默忽略的),exec 与 app-server 两条路都吃 `-c` 覆盖。
25651
+
25652
+ ⚠ 未验证的一格:这个键**是否真的**就是那个 `-l` 的开关,只能等下一次
25653
+ codex 派单后在节点上 `ps` 看是 `bash -lc` 还是 `bash -c`。设错了是 no-op,不会更坏。 */
25654
+ "-c",
25655
+ "allow_login_shell=false",
25636
25656
  // ADR「agent CLI 启动参数员工级可配置化」:员工配置 job.extraArgs(在前)+ opts.extraArgs(静态默认,在后),
25637
25657
  // 过 CODEX_BLOCKED_FLAGS 拦截。追加在**位置 prompt 之前**(codex exec 的 [PROMPT] 必须是末位参数)。
25638
25658
  ...prepareExtraArgs(
@@ -28663,7 +28683,7 @@ function projectsIntoContent(kind, role, origin, telemetryTextInContent) {
28663
28683
  return true;
28664
28684
  }
28665
28685
  }
28666
- var import_node_crypto15, CHAT_ITEMS_DOUBLE_WRITE_ENV, ChatItemLedger;
28686
+ var import_node_crypto15, CHAT_ITEMS_DOUBLE_WRITE_ENV, SEGMENT_END_NOTE, ChatItemLedger;
28667
28687
  var init_chat_item_ledger = __esm({
28668
28688
  "../server/src/chat-item-ledger.ts"() {
28669
28689
  "use strict";
@@ -28671,6 +28691,7 @@ var init_chat_item_ledger = __esm({
28671
28691
  init_chat_item();
28672
28692
  import_node_crypto15 = require("node:crypto");
28673
28693
  CHAT_ITEMS_DOUBLE_WRITE_ENV = "OASIS_CHAT_ITEMS_DOUBLE_WRITE";
28694
+ SEGMENT_END_NOTE = "\u672C\u6BB5\u65E0\u8F93\u51FA\uFF08\u4EBA\u5728\u9996\u4E2A\u6B63\u6587\u4E4B\u524D\u63D2\u4E86\u8BDD\uFF09";
28674
28695
  ChatItemLedger = class {
28675
28696
  constructor(deps) {
28676
28697
  this.deps = deps;
@@ -28699,6 +28720,8 @@ var init_chat_item_ledger = __esm({
28699
28720
  log;
28700
28721
  persistEnabled;
28701
28722
  telemetryTextInContent;
28723
+ /** 本段的「无输出」注解已经提前落过(`closeSegmentBeforeUserInput`),`beginSegment` 别再落一条。 */
28724
+ segmentEndEmitted = false;
28702
28725
  /** **不变量 1**:`providerItemKey → itemId`,本账本唯一持有。切段/收尾清空。 */
28703
28726
  itemKeyMap = /* @__PURE__ */ new Map();
28704
28727
  /** 内存账本:itemId → 行。插入顺序即 seq 顺序。 */
@@ -28916,19 +28939,11 @@ var init_chat_item_ledger = __esm({
28916
28939
  * 映射一并清空——新段是新行,复用旧 itemId 会把新文本写进上一条消息。
28917
28940
  */
28918
28941
  beginSegment() {
28919
- this.closeStreamingRows("completed");
28920
- if (this.segmentItemCount === 0 && this.messageId) {
28921
- this.insert({
28922
- kind: "control",
28923
- role: "assistant",
28924
- status: "completed",
28925
- origin: "server",
28926
- text: "",
28927
- attrs: {},
28928
- inContent: false,
28929
- persist: true,
28930
- payload: { code: "segment_end", note: "\u672C\u6BB5\u65E0\u8F93\u51FA\uFF08\u4EBA\u5728\u9996\u4E2A\u6B63\u6587\u4E4B\u524D\u63D2\u4E86\u8BDD\uFF09" }
28931
- });
28942
+ if (this.segmentEndEmitted) {
28943
+ this.segmentEndEmitted = false;
28944
+ } else {
28945
+ this.closeStreamingRows("completed");
28946
+ if (this.segmentItemCount === 0 && this.messageId) this.insertSegmentEnd();
28932
28947
  }
28933
28948
  this.itemKeyMap.clear();
28934
28949
  this.lastTurnId = null;
@@ -28936,6 +28951,41 @@ var init_chat_item_ledger = __esm({
28936
28951
  this.segment += 1;
28937
28952
  this.segmentItemCount = 0;
28938
28953
  }
28954
+ /**
28955
+ * 人插话时,先把「这一段到此为止」落定;**必须在那条 user 行之前调用**。
28956
+ *
28957
+ * 展示序(`ord`)由落库那一刻的表级序列派,所以**谁先落库谁在上面**。此前这条注解跟在
28958
+ * `beginSegment()` 里、而 `beginSegment` 由 `onUserInput` 回调在 user 行之后触发,于是
28959
+ * 「本段无输出」排到了人说的那句话**下面**——发起人 2026-09-11 的现场(会话 `eca44da4`:
28960
+ * user 行 ord 8503、注解 ord 8504)。他要的顺序是:开启这一轮的那条消息 → agent 头像块
28961
+ * (里面就是这条注解)→ 人插的那句话 → agent 接着说。
28962
+ *
28963
+ * 返回落下的那一行(`null` = 本段有输出、或还没有 assistant 行可归属,不需要注解),
28964
+ * 调用方据它把这条推上 v3 流——不推的话它只在下一次快照才出现。
28965
+ */
28966
+ closeSegmentBeforeUserInput() {
28967
+ this.closeStreamingRows("completed");
28968
+ if (this.segmentEndEmitted) return null;
28969
+ if (this.segmentItemCount !== 0 || !this.messageId) return null;
28970
+ const id = this.insertSegmentEnd();
28971
+ this.segmentEndEmitted = true;
28972
+ const row = this.rows.get(id);
28973
+ return { itemId: id, version: row.wireVersion, startedVersion: row.startedVersion, ord: row.ord };
28974
+ }
28975
+ /** 零输出段兜底(S1 评审 B1):那条 assistant 行库里已经在了,一条 item 都没有会被对账抓成假红。 */
28976
+ insertSegmentEnd() {
28977
+ return this.insert({
28978
+ kind: "control",
28979
+ role: "assistant",
28980
+ status: "completed",
28981
+ origin: "server",
28982
+ text: "",
28983
+ attrs: {},
28984
+ inContent: false,
28985
+ persist: true,
28986
+ payload: { code: "segment_end", note: SEGMENT_END_NOTE }
28987
+ });
28988
+ }
28939
28989
  /** 行落库了,把本段还挂着 NULL 的 assistant item 认领回去,并让后续 item 直接带上它。 */
28940
28990
  bindMessage(messageId) {
28941
28991
  this.messageId = messageId;
@@ -51348,7 +51398,7 @@ var require_websocket = __commonJS({
51348
51398
  var http2 = require("http");
51349
51399
  var net = require("net");
51350
51400
  var tls = require("tls");
51351
- var { randomBytes: randomBytes13, createHash: createHash37 } = require("crypto");
51401
+ var { randomBytes: randomBytes13, createHash: createHash38 } = require("crypto");
51352
51402
  var { Duplex, Readable } = require("stream");
51353
51403
  var { URL: URL2 } = require("url");
51354
51404
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -52016,7 +52066,7 @@ var require_websocket = __commonJS({
52016
52066
  abortHandshake(websocket, socket, "Invalid Upgrade header");
52017
52067
  return;
52018
52068
  }
52019
- const digest = createHash37("sha1").update(key + GUID).digest("base64");
52069
+ const digest = createHash38("sha1").update(key + GUID).digest("base64");
52020
52070
  if (res.headers["sec-websocket-accept"] !== digest) {
52021
52071
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
52022
52072
  return;
@@ -52385,7 +52435,7 @@ var require_websocket_server = __commonJS({
52385
52435
  var EventEmitter = require("events");
52386
52436
  var http2 = require("http");
52387
52437
  var { Duplex } = require("stream");
52388
- var { createHash: createHash37 } = require("crypto");
52438
+ var { createHash: createHash38 } = require("crypto");
52389
52439
  var extension3 = require_extension();
52390
52440
  var PerMessageDeflate2 = require_permessage_deflate();
52391
52441
  var subprotocol2 = require_subprotocol();
@@ -52692,7 +52742,7 @@ var require_websocket_server = __commonJS({
52692
52742
  );
52693
52743
  }
52694
52744
  if (this._state > RUNNING) return abortHandshake(socket, 503);
52695
- const digest = createHash37("sha1").update(key + GUID).digest("base64");
52745
+ const digest = createHash38("sha1").update(key + GUID).digest("base64");
52696
52746
  const headers = [
52697
52747
  "HTTP/1.1 101 Switching Protocols",
52698
52748
  "Upgrade: websocket",
@@ -159847,6 +159897,7 @@ var DEFAULT_BUFFER_MAX, DEFAULT_GRACE_MS, DEFAULT_LIVE_COALESCE_MS, DEFAULT_LIVE
159847
159897
  var init_live_chat = __esm({
159848
159898
  "../server/src/live-chat.ts"() {
159849
159899
  "use strict";
159900
+ init_chat_item_ledger();
159850
159901
  DEFAULT_BUFFER_MAX = 4e3;
159851
159902
  DEFAULT_GRACE_MS = 6e4;
159852
159903
  DEFAULT_LIVE_COALESCE_MS = 24;
@@ -160593,6 +160644,24 @@ var init_live_chat = __esm({
160593
160644
  text: shown,
160594
160645
  ...opts?.attachments?.length ? { attachments: opts.attachments } : {}
160595
160646
  };
160647
+ const segmentEnd = (() => {
160648
+ try {
160649
+ return turn.items?.closeSegmentBeforeUserInput() ?? null;
160650
+ } catch {
160651
+ return null;
160652
+ }
160653
+ })();
160654
+ if (segmentEnd) {
160655
+ this.publishServerItem(turn, {
160656
+ itemId: segmentEnd.itemId,
160657
+ itemType: "control",
160658
+ startedVersion: segmentEnd.startedVersion,
160659
+ version: segmentEnd.version,
160660
+ ord: segmentEnd.ord,
160661
+ status: "completed",
160662
+ payload: { code: "segment_end", note: SEGMENT_END_NOTE }
160663
+ });
160664
+ }
160596
160665
  try {
160597
160666
  turn.items?.recordUserInput(shown, opts?.attachments, opts?.clientKeys);
160598
160667
  } catch {
@@ -197867,41 +197936,6 @@ function stageConnectorWrapper(connector) {
197867
197936
  stagedBinDirs.set(cacheKey, binDir);
197868
197937
  return link;
197869
197938
  }
197870
- function clearLoginShellShadow(connector) {
197871
- const commandName = connector.commandName;
197872
- if (!commandName) return [];
197873
- const home2 = process.env["HOME"] ?? (0, import_node_os3.homedir)();
197874
- if (!home2) return [];
197875
- const moved = [];
197876
- for (const parts of LOGIN_SHELL_FRONT_DIRS) {
197877
- const dir = (0, import_node_path9.join)(home2, ...parts);
197878
- if (!(0, import_node_fs8.existsSync)(dir)) continue;
197879
- const target = (0, import_node_path9.join)(dir, commandName);
197880
- try {
197881
- (0, import_node_fs8.lstatSync)(target);
197882
- } catch {
197883
- continue;
197884
- }
197885
- if (isOasisWrapperPath(target)) continue;
197886
- const backup = (0, import_node_path9.join)(dir, `${commandName}${DISPLACED_SUFFIX}`);
197887
- const dest = (0, import_node_fs8.existsSync)(backup) || safeLstat(backup) ? `${backup}.${Date.now()}` : backup;
197888
- try {
197889
- (0, import_node_fs8.renameSync)(target, dest);
197890
- moved.push({ from: target, to: dest });
197891
- } catch {
197892
- continue;
197893
- }
197894
- }
197895
- return moved;
197896
- }
197897
- function safeLstat(p2) {
197898
- try {
197899
- (0, import_node_fs8.lstatSync)(p2);
197900
- return true;
197901
- } catch {
197902
- return false;
197903
- }
197904
- }
197905
197939
  function isStagedWrapperUsable(link) {
197906
197940
  try {
197907
197941
  (0, import_node_fs8.lstatSync)(link);
@@ -197910,7 +197944,7 @@ function isStagedWrapperUsable(link) {
197910
197944
  }
197911
197945
  return (0, import_node_fs8.existsSync)(link);
197912
197946
  }
197913
- var import_node_fs8, import_node_os3, import_node_path9, stagedBinDirs, LOGIN_SHELL_FRONT_DIRS, DISPLACED_SUFFIX;
197947
+ var import_node_fs8, import_node_os3, import_node_path9, stagedBinDirs;
197914
197948
  var init_stage = __esm({
197915
197949
  "../connectors/src/_base/stage.ts"() {
197916
197950
  "use strict";
@@ -197919,8 +197953,6 @@ var init_stage = __esm({
197919
197953
  import_node_path9 = require("node:path");
197920
197954
  init_wrapper_assets();
197921
197955
  stagedBinDirs = /* @__PURE__ */ new Map();
197922
- LOGIN_SHELL_FRONT_DIRS = [[".local", "bin"], ["bin"]];
197923
- DISPLACED_SUFFIX = ".oasis-displaced";
197924
197956
  }
197925
197957
  });
197926
197958
 
@@ -201952,19 +201984,12 @@ async function prepareConnectorsForJob(job, deps) {
201952
201984
  localWrappers.push(staged);
201953
201985
  status.wrapperReady = true;
201954
201986
  }
201955
- const moved = clearLoginShellShadow(connector);
201956
- for (const m2 of moved) {
201957
- log(
201958
- "[node-connectors]",
201959
- `${slug6}: \u632A\u5F00\u767B\u5F55 shell \u906E\u6321 ${m2.from} \u2192 ${m2.to}\uFF08$HOME/.local/bin \u4F1A\u88AB ~/.profile \u63D2\u5230 PATH \u6700\u524D\uFF0C\u538B\u8FC7\u6CE8\u5165\u7684 wrapper\uFF1B\u6539\u540D\u4E0D\u5220\u9664\uFF0Cwrapper \u89E3\u6790\u94FE\u6700\u540E\u4E00\u6863\u4ECD\u80FD\u6361\u56DE\u5B83\uFF09`
201960
- );
201961
- }
201962
201987
  const shadow = loginShellShadow(connector.commandName);
201963
201988
  if (shadow) {
201964
201989
  status.shadowedBy = shadow;
201965
201990
  log(
201966
201991
  "[node-connectors]",
201967
- `\u26A0 ${slug6}: creds + wrapper \u5DF2\u843D\u5728\u672C\u673A\uFF0C\u4F46 **${shadow} \u4ECD\u5728\u767B\u5F55 shell \u91CC\u6321\u7740 wrapper**\uFF0C\u800C\u4E14\u632A\u4E0D\u52A8\u5B83\u3002agent \u4E00\u65E6\u7528 \`bash -lc\` \u8C03 ${connector.commandName}\uFF0C\u8D70\u5230\u7684\u662F\u90A3\u4E2A\u5BBF\u4E3B\u526F\u672C\u3001\u914D\u7F6E\u76EE\u5F55\u53C8\u88AB\u9694\u79BB\u6210\u7A7A\u7684\uFF0C\u7ED3\u679C\u5C31\u662F\u300C\u672A\u914D\u7F6E\u300D\u3002`
201992
+ `\u26A0 ${slug6}: creds + wrapper \u5DF2\u843D\u5728\u672C\u673A\uFF0C\u4F46 **${shadow} \u4F1A\u5728\u767B\u5F55 shell \u91CC\u6321\u4F4F wrapper**\u2014\u2014Ubuntu \u7684 ~/.profile \u628A $HOME/.local/bin \u524D\u7F6E\u5230 PATH \u6700\u524D\u3002agent \u4E00\u65E6\u7528 \`bash -lc\` \u8C03 ${connector.commandName}\uFF0C\u8D70\u5230\u7684\u662F\u90A3\u4E2A\u5BBF\u4E3B\u526F\u672C\u3001\u914D\u7F6E\u76EE\u5F55\u53C8\u88AB\u9694\u79BB\u6210\u7A7A\u7684\uFF0C\u7ED3\u679C\u5C31\u662F\u300C\u672A\u914D\u7F6E\u300D\u3002`
201968
201993
  );
201969
201994
  } else {
201970
201995
  log("[node-connectors]", `${slug6}: creds + wrapper \u5DF2\u843D\u5728\u672C\u673A`);
@@ -205876,13 +205901,22 @@ async function startOasisServer(opts) {
205876
205901
  if (url.pathname === "/api/chat/attachments" && req.method === "POST" && opts.uploadChatAttachment) {
205877
205902
  try {
205878
205903
  const body2 = JSON.parse(rawBody);
205879
- if (typeof body2.contentBase64 !== "string" || !body2.contentBase64) throw new Error("\u7F3A\u5C11 contentBase64");
205880
- const MAX_BASE64_LEN = Math.ceil(CHAT_ATTACHMENT_MAX_BYTES * 4 / 3) + 4096;
205904
+ if (typeof body2.contentBase64 !== "string") throw new Error("\u7F3A\u5C11 contentBase64");
205905
+ const MAX_BASE64_LEN = 4 * Math.ceil(CHAT_ATTACHMENT_MAX_BYTES / 3);
205881
205906
  if (body2.contentBase64.length > MAX_BASE64_LEN) {
205882
205907
  res.writeHead(413, { "content-type": "application/json" });
205883
205908
  res.end(JSON.stringify({ error: { code: "ATTACHMENT_TOO_LARGE", message: `\u9644\u4EF6\u8D85\u8FC7 ${CHAT_ATTACHMENT_MAX_BYTES / 1024 / 1024}MB \u4E0A\u9650` } }));
205884
205909
  return;
205885
205910
  }
205911
+ if (body2.contentBase64.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(body2.contentBase64)) {
205912
+ throw new Error("\u9644\u4EF6\u7F16\u7801\u65E0\u6548\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u6587\u4EF6");
205913
+ }
205914
+ if (Buffer.from(body2.contentBase64, "base64").byteLength > CHAT_ATTACHMENT_MAX_BYTES) {
205915
+ res.writeHead(413, { "content-type": "application/json" }).end(JSON.stringify({
205916
+ error: { code: "ATTACHMENT_TOO_LARGE", message: `\u9644\u4EF6\u8D85\u8FC7 ${CHAT_ATTACHMENT_MAX_BYTES / 1024 / 1024}MB \u4E0A\u9650` }
205917
+ }));
205918
+ return;
205919
+ }
205886
205920
  const out = await opts.uploadChatAttachment({
205887
205921
  contentBase64: body2.contentBase64,
205888
205922
  ...typeof body2.contentType === "string" ? { contentType: body2.contentType } : {},
@@ -211505,7 +211539,7 @@ var init_service4 = __esm({
211505
211539
  }
211506
211540
  };
211507
211541
  ProjectsService = class {
211508
- constructor(projects, artifacts, kernel, oplog, actorNames, blobs, actorDirectory, resolveActorContext, loadActorResolver) {
211542
+ constructor(projects, artifacts, kernel, oplog, actorNames, blobs, actorDirectory, resolveActorContext, loadActorResolver, listChatFiles) {
211509
211543
  this.projects = projects;
211510
211544
  this.artifacts = artifacts;
211511
211545
  this.kernel = kernel;
@@ -211515,6 +211549,7 @@ var init_service4 = __esm({
211515
211549
  this.actorDirectory = actorDirectory;
211516
211550
  this.resolveActorContext = resolveActorContext;
211517
211551
  this.loadActorResolver = loadActorResolver;
211552
+ this.listChatFiles = listChatFiles;
211518
211553
  }
211519
211554
  projectCreatedHandler;
211520
211555
  onProjectNameChanged;
@@ -212102,6 +212137,18 @@ var init_service4 = __esm({
212102
212137
  });
212103
212138
  }
212104
212139
  const files = await this.collectProjectFiles(projectId2);
212140
+ const existingFiles = /* @__PURE__ */ new Set([
212141
+ ...items.filter((item) => item.blobRef).map((item) => `${item.name}\0${item.blobRef}`),
212142
+ ...files.map((file) => `${file.name}\0${file.blobId}`)
212143
+ ]);
212144
+ if (this.listChatFiles && !isUncategorizedProjectId(projectId2)) {
212145
+ for (const file of await this.listChatFiles(projectId2)) {
212146
+ const key = `${file.name}\0${file.blobId}`;
212147
+ if (existingFiles.has(key)) continue;
212148
+ existingFiles.add(key);
212149
+ files.push(file);
212150
+ }
212151
+ }
212105
212152
  for (const file of files) {
212106
212153
  const path41 = typeof file.path === "string" && file.path.trim() ? file.path : void 0;
212107
212154
  if (path41) emitFolder(dirname24(path41), file.uploadedAt);
@@ -212778,7 +212825,15 @@ var init_service4 = __esm({
212778
212825
  * 亲手提交过至少一版」。这是仓库里唯一一条真实存在的「产物 ↔ 人」关系;
212779
212826
  * 「谁 own 这个节点」不用(节点 owner 是**派给谁**,派了没干完的也会被算成产出,那是假数据)。
212780
212827
  *
212781
- * 同一产物被同一人提交多版只出现一次,`revisionCount` 记他提过几版、`updatedAt` 取他最近一次提交。
212828
+ * **空轮不算产出**(ADR 0539 补记):内核把**每一轮执行**都投影成一版 revision,这一轮一个产出文件
212829
+ * 都没有时 bridge 如实给 `contentKind="empty"`(运行中占位 / 被限流打死的空跑 / 只留结论没产正文,
212830
+ * 见 `bridge-readmodel.workToRevision` 与 ADR-0134)。那种版本这里一律跳过——它不是「他交了什么」,
212831
+ * 展示层拿到只会得到一行没有大小、点开写着「还没有可预览的正文」的空行。
212832
+ * 项目页「相关文件」早就不列这类轮次(`collectEngineFilesForProject` 的
212833
+ * `latestAcceptId` / `cancelledAt` / `arts.length === 0` 三道闸),本栏此前漏了这条口径。
212834
+ *
212835
+ * 于是 `revisionCount` 记的是**他交付过几版**、`latestRevisionId`/`updatedAt` 取**他最近一次真的
212836
+ * 交了东西的那一版**;一版都没交过内容的产物整条不出现。
212782
212837
  *
212783
212838
  * 数据同 `artifactDetail`:从 `kernel.model` 现算投影(新引擎 oplog 已退役、派生 store 恒空),
212784
212839
  * 不读 `artifact_revisions` 表——读那张表在新引擎上永远是空列表。
@@ -212796,6 +212851,7 @@ var init_service4 = __esm({
212796
212851
  const byArtifact = /* @__PURE__ */ new Map();
212797
212852
  for (const revision of projection.revisions) {
212798
212853
  if (revision.actorId !== actorId) continue;
212854
+ if (isEmptyContent(this.kernel.model.revisions.get(revision.revisionId)?.contentKind)) continue;
212799
212855
  const artifact = artifactById.get(revision.artifactId);
212800
212856
  if (!artifact) continue;
212801
212857
  const prev = byArtifact.get(revision.artifactId);
@@ -212838,8 +212894,10 @@ var init_service4 = __esm({
212838
212894
  * 同一个 {@link contentByteSize}**,不另写一套判据):装箱单摊成每个物理文件一行,
212839
212895
  * 其余(inline-blob / external-pin / 装箱单解析失败)整份产物一行。
212840
212896
  *
212841
- * 取哪一版:**他自己最近提交的那一版**(`latestRevisionId`),不是产物的 current head——
212842
- * 这一栏回答的是「他交了什么」,拿别人后来覆盖的版本冒充他的产出就是假数据。
212897
+ * 取哪一版:**他自己最近交付过内容的那一版**(`latestRevisionId`,空轮已在
212898
+ * {@link listActorArtifacts} 里滤掉),不是产物的 current head——这一栏回答的是「他交了什么」,
212899
+ * 拿别人后来覆盖的版本冒充他的产出就是假数据。该版本 id 随行发给前端(`revisionId`),
212900
+ * 预览栏据它定位同一版:行与预览必须指着同一个东西,否则会出现「行上有 20 KB、点开说没有正文」。
212843
212901
  */
212844
212902
  async listActorFiles(actorId) {
212845
212903
  const artifacts = await this.listActorArtifacts(actorId);
@@ -212851,6 +212909,7 @@ var init_service4 = __esm({
212851
212909
  const base = {
212852
212910
  artifactId: item.artifactId,
212853
212911
  projectId: item.projectId,
212912
+ revisionId: item.latestRevisionId,
212854
212913
  type: item.type,
212855
212914
  updatedAt: item.updatedAt,
212856
212915
  ...contentKind ? { contentKind } : {},
@@ -220169,6 +220228,20 @@ ${input.description}
220169
220228
  };
220170
220229
  });
220171
220230
  }
220231
+ /**
220232
+ * 这一行变量是谁建的(子 PRD《密钥管理》§1 的权限判据)。**只读元数据、不解密**——
220233
+ * `listVariables()` 会把每一行都解一遍出掩码,写路径上没必要付那个钱。
220234
+ *
220235
+ * 返回 undefined 有两种含义,调用方按「放行」处理:行不存在(新建),
220236
+ * 或行是 2026-09-10 之前落的(那时还没有 created_by 这一格)。
220237
+ */
220238
+ async variableCreatedBy(key, selector = {}) {
220239
+ const rows = await this.opts.store.listVariables();
220240
+ const hit = rows.find(
220241
+ (r) => r.key === key && (r.actorId ?? "") === (selector.actorId ?? "") && (r.projectId ?? "") === (selector.projectId ?? "")
220242
+ );
220243
+ return hit?.createdBy;
220244
+ }
220172
220245
  async revealVariable(key, actorId) {
220173
220246
  const ct = await this.opts.store.getVariableCiphertext(key, actorId);
220174
220247
  if (ct === null) return null;
@@ -221954,6 +222027,17 @@ function actorsDomain(opts) {
221954
222027
  );
221955
222028
  }
221956
222029
  };
222030
+ const requireSecretOwner = async (req, service, key, selector = {}) => {
222031
+ const owner = await service.variableCreatedBy(key, selector);
222032
+ if (!owner || owner === req.auth.actor) return;
222033
+ const manager = opts.isCompanyManager ? await opts.isCompanyManager(req.auth.companyId ?? "", req.auth.actor).catch(() => false) : false;
222034
+ if (manager) return;
222035
+ throw new ApiError(
222036
+ 403,
222037
+ "SECRET_NOT_OWNED",
222038
+ "\u8FD9\u4EFD\u5BC6\u94A5\u7531\u522B\u4EBA\u521B\u5EFA\uFF0C\u53EA\u6709\u521B\u5EFA\u8005\u672C\u4EBA\u6216\u7EC4\u7EC7\u7BA1\u7406\u8005\u53EF\u4EE5\u4FEE\u6539 / \u5220\u9664"
222039
+ );
222040
+ };
221957
222041
  const auditVariableChange = async (req, kind, variableKey, targetScope) => {
221958
222042
  await opts.credentialAudit?.({
221959
222043
  kind,
@@ -222008,6 +222092,7 @@ function actorsDomain(opts) {
222008
222092
  );
222009
222093
  }
222010
222094
  if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
222095
+ await requireSecretOwner(req, service, b2.key, scope === "project" ? { projectId: b2.projectId } : {});
222011
222096
  await service.putVariable({
222012
222097
  key: b2.key,
222013
222098
  value: b2.value,
@@ -222035,6 +222120,7 @@ function actorsDomain(opts) {
222035
222120
  requireVariableManager(req);
222036
222121
  const { service } = await resolveCtx(req.auth.companyId);
222037
222122
  const projectId2 = req.query.get("projectId") ?? void 0;
222123
+ await requireSecretOwner(req, service, req.params.key, projectId2 ? { projectId: projectId2 } : {});
222038
222124
  await service.deleteVariable(
222039
222125
  req.params.key,
222040
222126
  void 0,
@@ -222082,6 +222168,7 @@ function actorsDomain(opts) {
222082
222168
  );
222083
222169
  }
222084
222170
  }
222171
+ await requireSecretOwner(req, service, b2.key, { actorId });
222085
222172
  await service.putVariable({
222086
222173
  key: b2.key,
222087
222174
  value: b2.value,
@@ -222102,6 +222189,7 @@ function actorsDomain(opts) {
222102
222189
  router.delete("/api/actors/:id/variables/:key", async (req) => {
222103
222190
  requireVariableManager(req);
222104
222191
  const { service } = await resolveCtx(req.auth.companyId);
222192
+ await requireSecretOwner(req, service, req.params.key, { actorId: req.params.id });
222105
222193
  await service.deleteVariable(req.params.key, req.params.id);
222106
222194
  await auditVariableChange(req, "credential_delete", req.params.key, `personal:${req.params.id}`);
222107
222195
  return { status: 200, body: { ok: true } };
@@ -222910,7 +222998,7 @@ function createActorsDomain(opts) {
222910
222998
  return {
222911
222999
  service: defaultCtx.service,
222912
223000
  resolveCtx,
222913
- register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {}, ...opts.skillMarket ? { skillMarket: opts.skillMarket } : {}, ...opts.resolveNodeTenancy ? { resolveNodeTenancy: opts.resolveNodeTenancy } : {} })
223001
+ register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.isCompanyManager ? { isCompanyManager: opts.isCompanyManager } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {}, ...opts.skillMarket ? { skillMarket: opts.skillMarket } : {}, ...opts.resolveNodeTenancy ? { resolveNodeTenancy: opts.resolveNodeTenancy } : {} })
222914
223002
  };
222915
223003
  }
222916
223004
  var import_node_crypto49;
@@ -223267,6 +223355,7 @@ ${errs.join("\n")}`);
223267
223355
  id: item.id,
223268
223356
  artifact_id: item.artifactId,
223269
223357
  project_id: item.projectId,
223358
+ revision_id: item.revisionId,
223270
223359
  type: item.type,
223271
223360
  name: item.name,
223272
223361
  file_path: item.filePath,
@@ -223942,6 +224031,15 @@ var init_routes5 = __esm({
223942
224031
  }
223943
224032
  });
223944
224033
 
224034
+ // ../server/src/domains/collab/activity-notice.ts
224035
+ var NOTICE_TITLE;
224036
+ var init_activity_notice = __esm({
224037
+ "../server/src/domains/collab/activity-notice.ts"() {
224038
+ "use strict";
224039
+ NOTICE_TITLE = "\u53D8\u66F4\u901A\u77E5";
224040
+ }
224041
+ });
224042
+
223945
224043
  // ../server/src/domains/collab/review-activity.ts
223946
224044
  function reviewCardId(targetWorkId, reviewerActorId) {
223947
224045
  return `${REVIEW_CARD_PREFIX}${encodeURIComponent(targetWorkId)}:${encodeURIComponent(reviewerActorId)}`;
@@ -224090,35 +224188,18 @@ var init_review_activity = __esm({
224090
224188
 
224091
224189
  // ../server/src/domains/collab/review-requirement-activity.ts
224092
224190
  function reviewRequirementActivity(snap, events, ref2) {
224093
- const previous3 = /* @__PURE__ */ new Map();
224094
224191
  const titles = new Map(snap.nodes.map((n) => [n.id, n.title]));
224095
224192
  const cards = [];
224096
224193
  const label = (r) => `${ref2(r.reviewerActorId).name || "\u672A\u547D\u540D\u5BA1\u6838\u4EBA"}${r.source === "closure" ? "\uFF08\u7ED3\u6848\u5BA1\u6838\uFF09" : ""}`;
224097
- const names = (rows) => [...new Set(rows.map(label))].join("\u3001") || "\u65E0";
224194
+ const names = (rows) => [...new Set(rows.map(label))].join("\u3001");
224195
+ const submitter = (id) => ref2(id).name?.trim() || "\u672A\u547D\u540D\u6210\u5458";
224196
+ const sentence = (id, nodeTitle, rows) => rows.length ? `${submitter(id)} \u66F4\u6539\u300A${nodeTitle}\u300B\u8282\u70B9\u7684\u5BA1\u6838\u4EBA\u4E3A ${names(rows)}\u3002` : `${submitter(id)} \u6E05\u7A7A\u4E86\u300A${nodeTitle}\u300B\u8282\u70B9\u7684\u5BA1\u6838\u4EBA\u3002`;
224098
224197
  for (const rec of [...events].sort((a, b2) => a.seq - b2.seq)) {
224099
224198
  if (rec.status === "pending" || rec.status === "failed") continue;
224100
224199
  const e = rec.event;
224101
- if (e.kind === "plan.changed") {
224102
- for (const node2 of e.addNodes ?? []) previous3.set(node2.id, node2.reviewers ?? []);
224103
- for (const id of e.removeNodes ?? []) previous3.delete(id);
224104
- continue;
224105
- }
224106
- if (e.kind === "plan.add_node") {
224107
- previous3.set(e.nodeId, e.reviewers ?? []);
224108
- continue;
224109
- }
224110
224200
  if (e.kind !== "plan.update_review_requirements") continue;
224111
- const before = previous3.get(e.nodeId);
224112
- const after = e.reviewers;
224113
- const beforeKeys = new Set(before?.map(requirementKey));
224114
- const afterKeys = new Set(after.map(requirementKey));
224115
- const added = before ? after.filter((r) => !beforeKeys.has(requirementKey(r))) : [];
224116
- const removed = before?.filter((r) => !afterKeys.has(requirementKey(r))) ?? [];
224201
+ if (!isAuthoredChange(rec)) continue;
224117
224202
  const nodeTitle = titles.get(e.nodeId) || "\u5DF2\u79FB\u9664\u7684\u8282\u70B9";
224118
- if (!isAuthoredChange(rec)) {
224119
- previous3.set(e.nodeId, after);
224120
- continue;
224121
- }
224122
224203
  cards.push({
224123
224204
  id: `wo:${rec.seq}`,
224124
224205
  seq: rec.seq,
@@ -224129,24 +224210,19 @@ function reviewRequirementActivity(snap, events, ref2) {
224129
224210
  executor: ref2(rec.actorId),
224130
224211
  ...rec.handActorId ? { handActor: ref2(rec.handActorId) } : {},
224131
224212
  phase: "done",
224132
- status: `\u66F4\u65B0\u4E86\u300A${nodeTitle}\u300B\u7684\u5BA1\u6838\u5217\u8868\u3002`,
224133
- detail: [
224134
- ...added.length ? [`\u65B0\u589E\uFF1A${names(added)}`] : [],
224135
- ...removed.length ? [`\u79FB\u9664\uFF1A${names(removed)}`] : [],
224136
- `\u66F4\u65B0\u540E\uFF1A${names(after)}`
224137
- ].join("\n"),
224213
+ noticeTitle: NOTICE_TITLE,
224214
+ status: sentence(rec.actorId, nodeTitle, e.reviewers),
224138
224215
  artifacts: [],
224139
224216
  actions: []
224140
224217
  });
224141
- previous3.set(e.nodeId, after);
224142
224218
  }
224143
224219
  return cards;
224144
224220
  }
224145
- var requirementKey, isAuthoredChange;
224221
+ var isAuthoredChange;
224146
224222
  var init_review_requirement_activity = __esm({
224147
224223
  "../server/src/domains/collab/review-requirement-activity.ts"() {
224148
224224
  "use strict";
224149
- requirementKey = (r) => JSON.stringify([r.reviewerActorId, r.reviewGroup, r.source]);
224225
+ init_activity_notice();
224150
224226
  isAuthoredChange = (rec) => rec.origin !== "apply" && rec.origin !== "effect" && !rec.actorId.startsWith("actor:system:");
224151
224227
  }
224152
224228
  });
@@ -224242,7 +224318,6 @@ function buildWorkorderActivity(input) {
224242
224318
  return tail || nodeId;
224243
224319
  };
224244
224320
  const cardOfWork = /* @__PURE__ */ new Map();
224245
- const latestCardOfNode = /* @__PURE__ */ new Map();
224246
224321
  const executionCards = [];
224247
224322
  const offTimeline = (w2) => isDispatchLayerFailure(w2.outcomeDetail) || w2.outcome === "completed" && statusOf2(w2) !== "success" && !artifactsByWork.get(w2.id)?.length;
224248
224323
  const statusOf2 = (w2) => w2.status ?? (w2.outcome === "failed" ? "failed" : w2.outcome === "completed" ? "success" : "running");
@@ -224271,35 +224346,45 @@ function buildWorkorderActivity(input) {
224271
224346
  works.sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || a.id.localeCompare(b2.id));
224272
224347
  const interrupts = [...interruptsOfNode.get(nodeId) ?? []].sort();
224273
224348
  const node2 = nodeById.get(nodeId);
224349
+ const nodeCancelledAt = node2?.cancelledAt ?? null;
224274
224350
  const name = nodeName(nodeId);
224275
- let card2 = null;
224351
+ let fold2 = null;
224276
224352
  for (const [idx, w2] of works.entries()) {
224277
- if (!card2) {
224278
- card2 = {
224279
- /* id 取组内**首个** work(React key 与滚动锚点因此稳定);`at` 是这一轮开工的时刻。 */
224280
- id: `work:${w2.id}`,
224281
- traceWorkIds: [],
224282
- seq: 0,
224283
- // 事件循环里按首个 work.create seq
224284
- at: w2.createdAt,
224285
- updatedAt: w2.createdAt,
224286
- nodeId,
224287
- executorId: w2.assigneeActorId,
224288
- phase: "running",
224289
- status: ACTIVITY_COPY.working(name),
224290
- artifacts: [],
224291
- actions: []
224292
- };
224293
- executionCards.push(card2);
224294
- latestCardOfNode.set(nodeId, card2);
224353
+ const next = works[idx + 1];
224354
+ const artifacts = artifactsByWork.get(w2.id) ?? [];
224355
+ const st = statusOf2(w2);
224356
+ const interrupt = interrupts.find((t) => t >= w2.createdAt && (!next || t < next.createdAt));
224357
+ const removedHere = !!nodeCancelledAt && nodeCancelledAt >= w2.createdAt && (!next || nodeCancelledAt < next.createdAt);
224358
+ const reassigned = !!next && next.assigneeActorId !== w2.assigneeActorId;
224359
+ const killedByPlan = !!(w2.deadAt || w2.cancelledAt) && !!next && (reassigned || (next.nodeVersion ?? 0) > (w2.nodeVersion ?? 0));
224360
+ const failedRound = !w2.conclusion && !interrupt && !removedHere && !killedByPlan && (!!w2.deadAt || !!w2.cancelledAt || st === "failed" || st === "retry");
224361
+ if (failedRound && fold2) {
224362
+ fold2.traceWorkIds.push(w2.id);
224363
+ cardOfWork.set(w2.id, fold2);
224364
+ fold2.updatedAt = lastStampOf(w2);
224365
+ fold2.stuckReason = w2.outcomeDetail?.reason === "timeout" ? "timeout" : "failed";
224366
+ fold2.foldedRounds = (fold2.foldedRounds ?? 1) + 1;
224367
+ fold2.status = ACTIVITY_COPY.foldedFailures(nameOf(fold2.executorId), name, fold2.foldedRounds);
224368
+ continue;
224295
224369
  }
224296
- card2.traceWorkIds.push(w2.id);
224370
+ fold2 = null;
224371
+ const card2 = {
224372
+ /* 卡 id = 这一轮的 workId(连败折叠时取第一轮,React key 与滚动锚点因此稳定)。 */
224373
+ id: `work:${w2.id}`,
224374
+ traceWorkIds: [w2.id],
224375
+ seq: 0,
224376
+ // 事件循环里按这一轮 work.create 的 seq 填
224377
+ at: w2.createdAt,
224378
+ updatedAt: lastStampOf(w2),
224379
+ nodeId,
224380
+ executorId: w2.assigneeActorId,
224381
+ phase: "running",
224382
+ status: ACTIVITY_COPY.working(name),
224383
+ artifacts,
224384
+ actions: []
224385
+ };
224386
+ executionCards.push(card2);
224297
224387
  cardOfWork.set(w2.id, card2);
224298
- card2.executorId = w2.assigneeActorId;
224299
- card2.updatedAt = lastStampOf(w2);
224300
- card2.artifacts = artifactsByWork.get(w2.id) ?? card2.artifacts;
224301
- delete card2.openWorkId;
224302
- delete card2.stuckReason;
224303
224388
  if (w2.conclusion) {
224304
224389
  card2.phase = "done";
224305
224390
  if (rootBriefNodeIds.has(nodeId)) {
@@ -224315,32 +224400,21 @@ function buildWorkorderActivity(input) {
224315
224400
  card2.status = awaits ? ACTIVITY_COPY.doneAwaitingReview(name) : ACTIVITY_COPY.done(name);
224316
224401
  }
224317
224402
  }
224318
- card2 = null;
224319
- continue;
224320
- }
224321
- const next = works[idx + 1];
224322
- const interrupt = interrupts.find((t) => t >= w2.createdAt && (!next || t < next.createdAt));
224323
- if (interrupt) {
224403
+ } else if (interrupt) {
224324
224404
  card2.phase = "stuck";
224325
224405
  card2.status = ACTIVITY_COPY.interruptedByIssue(name);
224326
224406
  if (interrupt > card2.updatedAt) card2.updatedAt = interrupt;
224327
- card2 = null;
224328
- continue;
224329
- }
224330
- const stuckKind = w2.outcomeDetail?.reason === "timeout" ? "timeout" : "failed";
224331
- const st = statusOf2(w2);
224332
- if (w2.deadAt || w2.cancelledAt) {
224407
+ } else if (removedHere || killedByPlan) {
224333
224408
  card2.phase = "stuck";
224334
- card2.stuckReason = stuckKind;
224335
- card2.status = ACTIVITY_COPY.killed(name);
224336
- } else if (st === "failed") {
224409
+ card2.status = removedHere ? ACTIVITY_COPY.interruptedByNodeRemoved(name) : reassigned ? ACTIVITY_COPY.interruptedByReassign(name, nameOf(next.assigneeActorId)) : ACTIVITY_COPY.interruptedByPlanChange(name);
224410
+ if (!removedHere && !reassigned) card2.planChangeUnnamed = true;
224411
+ if (removedHere && nodeCancelledAt > card2.updatedAt) card2.updatedAt = nodeCancelledAt;
224412
+ } else if (failedRound) {
224413
+ const stuckKind = w2.outcomeDetail?.reason === "timeout" ? "timeout" : "failed";
224337
224414
  card2.phase = "stuck";
224338
224415
  card2.stuckReason = stuckKind;
224339
- card2.status = stuckKind === "timeout" ? ACTIVITY_COPY.timedOut(nameOf(card2.executorId)) : ACTIVITY_COPY.failed(nameOf(card2.executorId));
224340
- } else if (st === "retry") {
224341
- card2.phase = "stuck";
224342
- card2.stuckReason = stuckKind;
224343
- card2.status = ACTIVITY_COPY.awaitingRedispatch(nameOf(card2.executorId), name);
224416
+ card2.status = w2.deadAt || w2.cancelledAt ? ACTIVITY_COPY.killed(name) : st === "retry" ? ACTIVITY_COPY.awaitingRedispatch(nameOf(card2.executorId), name) : stuckKind === "timeout" ? ACTIVITY_COPY.timedOut(nameOf(card2.executorId)) : ACTIVITY_COPY.failed(nameOf(card2.executorId));
224417
+ fold2 = card2;
224344
224418
  } else if (w2.endedAt) {
224345
224419
  card2.phase = "running";
224346
224420
  card2.status = ACTIVITY_COPY.awaitingConclude(name);
@@ -224361,17 +224435,28 @@ function buildWorkorderActivity(input) {
224361
224435
  if (rec.handActorId) d.handActorId = rec.handActorId;
224362
224436
  return d;
224363
224437
  };
224364
- const woCard = (rec, executorId, status, detail, phase = "done") => {
224438
+ const cardOpenAt = (nodeId, at) => {
224439
+ const list2 = worksOfNode.get(nodeId);
224440
+ if (!list2) return void 0;
224441
+ let hit;
224442
+ for (const w2 of list2) {
224443
+ if (w2.createdAt > at) break;
224444
+ hit = w2;
224445
+ }
224446
+ return hit ? cardOfWork.get(hit.id) : void 0;
224447
+ };
224448
+ const woCard = (rec, status, opts = {}) => {
224365
224449
  standalone.push({
224366
224450
  id: `wo:${rec.seq}`,
224367
224451
  seq: rec.seq,
224368
224452
  at: rec.createdAt,
224369
224453
  updatedAt: rec.createdAt,
224370
- executorId,
224454
+ executorId: rec.actorId,
224371
224455
  ...rec.handActorId ? { handActorId: rec.handActorId } : {},
224372
- phase,
224456
+ phase: "done",
224457
+ ...opts.notice ? { noticeTitle: NOTICE_TITLE } : {},
224373
224458
  status,
224374
- ...detail ? { detail } : {},
224459
+ ...opts.detail ? { detail: opts.detail } : {},
224375
224460
  artifacts: [],
224376
224461
  actions: []
224377
224462
  });
@@ -224384,20 +224469,29 @@ function buildWorkorderActivity(input) {
224384
224469
  case "plan.changed": {
224385
224470
  break;
224386
224471
  }
224472
+ /* 暂停 / 恢复是**人对这单做的事**,不是某一轮执行——走「变更通知」那套卡面(帧 `2874:1189`)。
224473
+ 此前它们沿用执行卡的标题模板,卡面第一行被拼成「{谁} 已完成」(`ACTIVITY_PHASE_LABEL.done`),
224474
+ 第二行才是「{谁}已暂停任务。」——标题和正文说的不是同一件事,而「已完成」在这一拍根本
224475
+ 没有所指(暂停不是完成)。 */
224387
224476
  case "workorder.paused":
224388
- woCard(rec, rec.actorId, ACTIVITY_COPY.paused(nameOf(rec.actorId)), str4(ev.reason));
224477
+ woCard(rec, ACTIVITY_COPY.paused(nameOf(rec.actorId)), { notice: true, detail: str4(ev.reason) });
224389
224478
  break;
224390
224479
  case "workorder.resumed":
224391
- woCard(rec, rec.actorId, ACTIVITY_COPY.resumedWorkorder(nameOf(rec.actorId)));
224480
+ woCard(rec, ACTIVITY_COPY.resumedWorkorder(nameOf(rec.actorId)), { notice: true });
224392
224481
  break;
224393
224482
  case "workorder.meta_changed": {
224394
224483
  const patch = ev.patch ?? {};
224395
224484
  if (!("goal" in patch) && !("description" in patch)) break;
224396
- woCard(rec, rec.actorId, ACTIVITY_COPY.goalChanged(nameOf(rec.actorId), managerName()));
224485
+ woCard(rec, ACTIVITY_COPY.goalChanged(nameOf(rec.actorId), managerName()));
224397
224486
  break;
224398
224487
  }
224399
224488
  case "plan.update_spec": {
224400
- woCard(rec, rec.actorId, ACTIVITY_COPY.specConfirmed, void 0, "running");
224489
+ const nodeId = String(ev.nodeId ?? "");
224490
+ const d = cardOpenAt(nodeId, rec.createdAt);
224491
+ if (d?.planChangeUnnamed) {
224492
+ d.status = ACTIVITY_COPY.interruptedBySpecChange(nameOf(rec.actorId), nodeName(nodeId));
224493
+ delete d.planChangeUnnamed;
224494
+ }
224401
224495
  break;
224402
224496
  }
224403
224497
  /* ── 执行(work)──────────────────────────────────────────────
@@ -224677,10 +224771,10 @@ function buildWorkorderActivity(input) {
224677
224771
  case "plan.node_retry": {
224678
224772
  const nodeId = String(ev.nodeId ?? "");
224679
224773
  const by = str4(ev.by) ?? rec.actorId;
224680
- const target = latestCardOfNode.get(nodeId);
224681
- if (!target || target.phase !== "stuck") break;
224774
+ const target = cardOpenAt(nodeId, rec.createdAt);
224775
+ if (!target || target.phase !== "stuck" || !target.stuckReason) break;
224682
224776
  touch(target, rec);
224683
- target.status = target.stuckReason === "timeout" ? ACTIVITY_COPY.retriedAfterTimeout(nameOf(target.executorId), nameOf(by)) : ACTIVITY_COPY.retriedAfterFailure(nameOf(target.executorId), nameOf(by));
224777
+ target.status = target.foldedRounds && target.foldedRounds > 1 ? ACTIVITY_COPY.foldedFailuresRetried(nameOf(target.executorId), nodeName(nodeId), target.foldedRounds, nameOf(by)) : target.stuckReason === "timeout" ? ACTIVITY_COPY.retriedAfterTimeout(nameOf(target.executorId), nameOf(by)) : ACTIVITY_COPY.retriedAfterFailure(nameOf(target.executorId), nameOf(by));
224684
224778
  break;
224685
224779
  }
224686
224780
  default:
@@ -224844,6 +224938,7 @@ function buildWorkorderActivity(input) {
224844
224938
  ...d.reviewWorkId ? { reviewWorkId: d.reviewWorkId, reviewers: d.reviewerIds?.map(ref2) } : {},
224845
224939
  ...d.handActorId ? { handActor: ref2(d.handActorId) } : {},
224846
224940
  phase: d.phase,
224941
+ ...d.noticeTitle ? { noticeTitle: d.noticeTitle } : {},
224847
224942
  status: d.status,
224848
224943
  ...d.detail ? { detail: d.detail } : {},
224849
224944
  ...d.reviewNotes?.length ? { reviewNotes: d.reviewNotes } : {},
@@ -224865,13 +224960,13 @@ var init_activity2 = __esm({
224865
224960
  "use strict";
224866
224961
  init_src();
224867
224962
  init_src4();
224963
+ init_activity_notice();
224868
224964
  init_review_activity();
224869
224965
  init_review_requirement_activity();
224870
224966
  init_workorder_manager();
224871
224967
  init_escalation_recipient();
224872
224968
  ACTIVITY_EVENT_KINDS = [
224873
224969
  "plan.changed",
224874
- "plan.add_node",
224875
224970
  "plan.update_review_requirements",
224876
224971
  "plan.update_spec",
224877
224972
  "plan.node_retry",
@@ -224945,7 +225040,39 @@ var init_activity2 = __esm({
224945
225040
  *
224946
225041
  * 不重复缺口卡那句「需要 @{协调者} 协助解决」:该找谁由缺口卡自己说,执行卡只说这一轮到此为止。
224947
225042
  */
225043
+ /**
225044
+ * **连败折叠**:同一节点上连续没跑成的 N 轮折成一张。不折叠的话 `ws:wo-6d588f8e` 的《隔离边界实现》
225045
+ * 会在时间线上连出 12 张,其中 9 张都是「执行失败。」——真正要给人看的是「这个节点一直没跑成」
225046
+ * 这一件事,逐轮的原因在轨迹抽屉里(`traceWorkIds` 收齐全部 N 轮,一轮不少)。
225047
+ */
225048
+ foldedFailures: (executor, node2, n) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u8DD1\u6210\u300A${node2}\u300B\u3002`,
225049
+ /** 折叠卡上补「谁按的重试」——不把「连续 N 次」换成单轮那句。 */
225050
+ foldedFailuresRetried: (executor, node2, n, by) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C${by}\u5DF2\u4ECB\u5165\u91CD\u8BD5\u3002`,
224948
225051
  interruptedByIssue: (node2) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u56E0\u5361\u70B9\u4E2D\u65AD\u3002`,
225052
+ /**
225053
+ * 这一轮**被计划变更打断**(P15)。与卡点中断同级:计划一动,这一轮就不再是「还在跑」,
225054
+ * 而是被引擎当场顶掉了——`plan.delete_node` 直接 `killLatestWork`;`plan.update_spec`(spec 真变)
225055
+ * 与 `plan.assign_actor`(换人)把 `node.version` 加一,scan 随即重派,`work.create` 自带
225056
+ * supersede 把在跑的这一轮翻成 dead。
225057
+ *
225058
+ * 此前这两条都不结束卡:删节点那档只落到 {@link killed}「本轮执行已终止」,不说是计划调整;
225059
+ * 改 spec / 改派那档**根本没有结局**——新一轮直接改写同一张卡,「中断」在页面上不存在,
225060
+ * 卡上署名还会换成新人(与 §2.1「一张卡只讲一个人的事」相抵)。
225061
+ */
225062
+ interruptedByPlanChange: (node2) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u56E0\u8BA1\u5212\u8C03\u6574\u4E2D\u65AD\u3002`,
225063
+ /**
225064
+ * 计划变更里最常见的那一种:**有人改了任务说明**,在跑的这一轮因此被顶掉。
225065
+ *
225066
+ * 这句取代了原来那张独立的任务级卡「已确认任务说明更新,正在判断该修改的影响。」——两张卡讲的
225067
+ * 本来就是同一件事:计划动了、这一轮做不下去了。旧卡还有两个毛病:`phase` 写死 `running`,
225068
+ * 没有任何一拍能让它收口(`ws:wo-217a0909` 12:13 那两张就是这么永久停在「进行中」的);文案是
225069
+ * 一句没有主语也没有宾语的固定串,两次内容不同的修改渲染成两行一模一样的字。
225070
+ */
225071
+ interruptedBySpecChange: (by, node2) => `${by} \u66F4\u65B0\u4E86\u4EFB\u52A1\u8BF4\u660E\uFF0C\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u5230\u6B64\u4E2D\u65AD\u3002`,
225072
+ /** 换人那一档点破接手的人——卡到此为止,下一轮是新执行人的新卡。 */
225073
+ interruptedByReassign: (node2, next) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u56E0\u8BA1\u5212\u8C03\u6574\u4E2D\u65AD\uFF0C\u5DF2\u6539\u7531 ${next} \u63A5\u624B\u3002`,
225074
+ /** 节点被移出计划:这一轮再没有下文,也不会有新卡。 */
225075
+ interruptedByNodeRemoved: (node2) => `\u300A${node2}\u300B\u5DF2\u4ECE\u8BA1\u5212\u4E2D\u79FB\u9664\uFF0C\u672C\u8F6E\u6267\u884C\u4E2D\u65AD\u3002`,
224949
225076
  /**
224950
225077
  * 引擎判卷 success、`endedAt` 也写了,却始终没有 `work.conclude`:**交了稿没收口**。
224951
225078
  * 如实说这一档,不冒充「正在处理」——后者把一轮早就结束的执行画成还在跑。
@@ -224982,7 +225109,6 @@ var init_activity2 = __esm({
224982
225109
  paused: (actor) => `${actor}\u5DF2\u6682\u505C\u4EFB\u52A1\u3002`,
224983
225110
  resumedWorkorder: (actor) => `${actor}\u5DF2\u6062\u590D\u4EFB\u52A1\u3002`,
224984
225111
  goalChanged: (actor, manager) => `${actor}\u4FEE\u6539\u4E86\u4EFB\u52A1\u76EE\u6807\uFF0C\u9700\u8981 ${manager}\u91CD\u65B0\u8BC4\u4F30\u3002`,
224985
- specConfirmed: "\u5DF2\u786E\u8BA4\u4EFB\u52A1\u8BF4\u660E\u66F4\u65B0\uFF0C\u6B63\u5728\u5224\u65AD\u8BE5\u4FEE\u6539\u7684\u5F71\u54CD\u3002",
224986
225112
  handleEscalation: (target) => `${target} \u7684\u6267\u884C\u5F02\u5E38\u6301\u7EED\u672A\u89E3\u51B3\u3002`,
224987
225113
  handledEscalation: (target) => `\u5DF2\u5904\u7406 ${target} \u7684\u6267\u884C\u5F02\u5E38\u3002`,
224988
225114
  /**
@@ -225991,13 +226117,7 @@ function buildWaitingInbox(reviews, works, policy, nowIso, me, resolveActor) {
225991
226117
  const isSelf = r.actorId === me;
225992
226118
  const actorLabel = displayName(resolveActor, r.actorId);
225993
226119
  if (r.status === "dead" || r.status === "exhausted") {
225994
- out.push(isSelf ? card(kind, r, "urgent", start, `\u4F60\u7684${KIND_LABEL[kind]}\u5DF2\u4E2D\u6B62\uFF0C\u9700\u8981\u4F60\u4ECB\u5165\u91CD\u5F00`) : card(
225995
- kind,
225996
- r,
225997
- "urgent",
225998
- start,
225999
- `\u8FD9\u4E00\u6B65\u7684${KIND_LABEL[kind]}\u5DF2\u4E2D\u6B62\uFF08${actorLabel} \u8FDE\u7EED\u5931\u8D25\u8D85\u9650\uFF09\u2014\u2014\u672C\u8F6E${KIND_LABEL[kind]}\u6536\u4E0D\u9F50\u3001\u8282\u70B9\u505C\u5728\u8FD9\u91CC\uFF0C\u9700\u8981\u4F60\u91CD\u5F00\u8FD9\u4E00\u8F6E`
226000
- ));
226120
+ out.push(isSelf ? card(kind, r, "urgent", start, `\u4F60\u7684${KIND_LABEL[kind]}\u5DF2\u4E2D\u6B62\uFF0C\u9700\u8981\u4F60\u4ECB\u5165\u91CD\u5F00`) : card(kind, r, "urgent", start, kind === "review-waiting" ? `\u8FD9\u4E00\u6B65\u7684\u8BC4\u5BA1\u5DF2\u4E2D\u6B62\uFF08${actorLabel} \u8FDE\u7EED\u5931\u8D25\u8D85\u9650\uFF09\u2014\u2014\u672C\u8F6E\u8BC4\u5BA1\u6536\u4E0D\u9F50\u3001\u8282\u70B9\u505C\u5728\u8FD9\u91CC\uFF0C\u9700\u8981\u4F60\u91CD\u5F00\u8FD9\u4E00\u8F6E` : `\u8FD9\u4E00\u6B65\u7684\u5904\u7406\u5DF2\u4E2D\u6B62\uFF08${actorLabel} \u8FDE\u7EED\u5931\u8D25\u8D85\u9650\uFF09\u2014\u2014\u8282\u70B9\u505C\u5728\u8FD9\u91CC\uFF0C\u9700\u8981\u6709\u4EBA\u91CD\u5F00`));
226001
226121
  continue;
226002
226122
  }
226003
226123
  if (r.status !== "running") continue;
@@ -226049,14 +226169,20 @@ function collectWaitingRows(snapshots2, me, policy = CODE_DEFAULT_POLICY) {
226049
226169
  const requirementHeads = new Map(snap.requirements.map((q2) => [reqKey(q2), q2.latestReviewId]));
226050
226170
  const requirementHead = (r) => requirementHeads.get(reqKey(r));
226051
226171
  const nodesWithRequirements = new Set(snap.requirements.map((q2) => q2.nodeId));
226052
- const managerOfWo = snap.nodes.find((n) => n.type === "brief")?.fields?.["manager"];
226172
+ const briefNode = snap.nodes.find((n) => n.type === "brief");
226173
+ const managerOfWo = briefNode?.fields?.["manager"];
226053
226174
  const isManager = typeof managerOfWo === "string" && managerOfWo === me;
226175
+ const launcher = briefNode?.assigneeActorId ?? null;
226176
+ const openIssueNodes = new Set(
226177
+ snap.issues.filter((i) => i.resolvedAt === null && i.aboutNodeId !== null).map((i) => i.aboutNodeId)
226178
+ );
226179
+ const nodeHasOpenIssue = (nodeId) => openIssueNodes.has(nodeId);
226054
226180
  const latestReview = /* @__PURE__ */ new Map();
226055
226181
  for (const r of snap.reviews) {
226056
226182
  if (cancelled.has(r.nodeId)) continue;
226057
226183
  if (acceptedWork.get(r.nodeId) === r.targetWorkId) continue;
226058
226184
  const mine = r.reviewerActorId === me;
226059
- const oversees = nodeOwner.get(r.nodeId) === me || isManager;
226185
+ const oversees = nodeOwner.get(r.nodeId) === me || isManager || launcher === me;
226060
226186
  if (!mine && !oversees) continue;
226061
226187
  latestBy(latestReview, r);
226062
226188
  }
@@ -226075,10 +226201,12 @@ function collectWaitingRows(snapshots2, me, policy = CODE_DEFAULT_POLICY) {
226075
226201
  const humanPending = !stopped && classify(r.reviewerActorId) === "human";
226076
226202
  if (!mine && !stopped && !humanPending) continue;
226077
226203
  const audience = [r.reviewerActorId];
226078
- const owner = nodeOwner.get(r.nodeId);
226079
- if (stopped || humanPending) {
226080
- if (owner && !audience.includes(owner)) audience.push(owner);
226081
- if (typeof managerOfWo === "string" && !audience.includes(managerOfWo)) audience.push(managerOfWo);
226204
+ const addBystanders = humanPending || stopped && !nodeHasOpenIssue(r.nodeId);
226205
+ if (addBystanders) {
226206
+ for (const candidate of [launcher, managerOfWo, nodeOwner.get(r.nodeId)]) {
226207
+ if (typeof candidate !== "string" || classify(candidate) !== "human") continue;
226208
+ if (!audience.includes(candidate)) audience.push(candidate);
226209
+ }
226082
226210
  }
226083
226211
  reviews.push({
226084
226212
  id: r.id,
@@ -226095,17 +226223,25 @@ function collectWaitingRows(snapshots2, me, policy = CODE_DEFAULT_POLICY) {
226095
226223
  const latestMain = /* @__PURE__ */ new Map();
226096
226224
  const latestReply = /* @__PURE__ */ new Map();
226097
226225
  for (const w2 of snap.works) {
226098
- if (w2.assigneeActorId !== me || cancelled.has(w2.nodeId)) continue;
226226
+ if (cancelled.has(w2.nodeId)) continue;
226099
226227
  latestBy(isReplyWork(w2) ? latestReply : latestMain, w2);
226100
226228
  }
226101
226229
  for (const w2 of [...latestMain.values(), ...latestReply.values()]) {
226102
226230
  const st = workState(w2, hasOutput(w2, outputCountOf(w2.id)));
226103
226231
  if (st !== "running" && st !== "dead") continue;
226232
+ const audience = [w2.assigneeActorId];
226233
+ if (st === "dead" && !nodeHasOpenIssue(w2.nodeId)) {
226234
+ for (const candidate of [launcher, managerOfWo, nodeOwner.get(w2.nodeId)]) {
226235
+ if (typeof candidate !== "string" || classify(candidate) !== "human") continue;
226236
+ if (!audience.includes(candidate)) audience.push(candidate);
226237
+ }
226238
+ }
226104
226239
  works.push({
226105
226240
  id: w2.id,
226106
226241
  workorderId: snap.workorder.id,
226107
226242
  artifactId: w2.nodeId,
226108
226243
  actorId: w2.assigneeActorId,
226244
+ notifyActorIds: audience,
226109
226245
  ...nodeTitle.get(w2.nodeId) ? { title: nodeTitle.get(w2.nodeId) } : {},
226110
226246
  createdAt: w2.createdAt,
226111
226247
  startedAt: w2.startedAt,
@@ -228074,6 +228210,245 @@ var init_collab = __esm({
228074
228210
  }
228075
228211
  });
228076
228212
 
228213
+ // ../server/src/domains/chat-sessions/files-view.ts
228214
+ function reasonFromWorkdirCode(code2) {
228215
+ switch (code2) {
228216
+ case "NODE_OFFLINE":
228217
+ return "NODE_OFFLINE";
228218
+ case "TIMEOUT":
228219
+ return "TIMEOUT";
228220
+ default:
228221
+ return "ERROR";
228222
+ }
228223
+ }
228224
+ function joinRel(dir, name) {
228225
+ return dir ? `${dir}/${name}` : name;
228226
+ }
228227
+ async function collectWorkdirFiles(bridge, loc, now = Date.now) {
228228
+ const deadline = now() + WORKDIR_WALK_BUDGET_MS;
228229
+ const items = [];
228230
+ let listCalls = 0;
228231
+ let truncated = false;
228232
+ const listDir = (path41) => {
228233
+ listCalls += 1;
228234
+ return bridge.list(loc.nodeId, {
228235
+ workdirKey: loc.workdirKey,
228236
+ runtimeKind: loc.runtimeKind,
228237
+ rtId: loc.rtId,
228238
+ path: path41
228239
+ });
228240
+ };
228241
+ const root = await listDir("");
228242
+ if (!root.ok) {
228243
+ if (root.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
228244
+ return { ok: false, reason: reasonFromWorkdirCode(root.code) };
228245
+ }
228246
+ let level = [{ path: "", outcome: root }];
228247
+ for (let depth = 0; depth <= WORKDIR_WALK_MAX_DEPTH; depth += 1) {
228248
+ const nextDirs = [];
228249
+ for (const { path: dir, outcome } of level) {
228250
+ if (!outcome.ok) {
228251
+ truncated = true;
228252
+ continue;
228253
+ }
228254
+ if (outcome.truncated) truncated = true;
228255
+ for (const entry of outcome.entries) {
228256
+ if (entry.type === "dir") {
228257
+ nextDirs.push(joinRel(dir, entry.name));
228258
+ continue;
228259
+ }
228260
+ if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
228261
+ truncated = true;
228262
+ continue;
228263
+ }
228264
+ const rel = joinRel(dir, entry.name);
228265
+ items.push({
228266
+ id: `workdir:${rel}`,
228267
+ name: entry.name,
228268
+ kind: "file",
228269
+ // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
228270
+ // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
228271
+ content_type: null,
228272
+ size: entry.size,
228273
+ updated_at: entry.mtime,
228274
+ source: "workdir",
228275
+ blob_ref: null,
228276
+ path: rel,
228277
+ message_id: null,
228278
+ // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
228279
+ // 这里是**按能力不渲染**的判据,不是置灰假钮。
228280
+ editable: false
228281
+ });
228282
+ }
228283
+ }
228284
+ if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
228285
+ if (nextDirs.length > 0) truncated = true;
228286
+ break;
228287
+ }
228288
+ const budget = WORKDIR_WALK_MAX_LIST_CALLS - listCalls;
228289
+ if (budget <= 0 || now() >= deadline) {
228290
+ truncated = true;
228291
+ break;
228292
+ }
228293
+ const take = nextDirs.slice(0, budget);
228294
+ if (take.length < nextDirs.length) truncated = true;
228295
+ const outcomes = await Promise.all(take.map((p2) => listDir(p2)));
228296
+ level = take.map((p2, i) => ({ path: p2, outcome: outcomes[i] }));
228297
+ }
228298
+ return { ok: true, items, truncated };
228299
+ }
228300
+ async function collectMessageFiles(messages, blobs) {
228301
+ const rows = [];
228302
+ for (const message of messages) {
228303
+ if (!Array.isArray(message.attachments)) continue;
228304
+ message.attachments.forEach((attachment, index2) => {
228305
+ if (!attachment || typeof attachment.name !== "string" || !attachment.name) return;
228306
+ const blobRef = typeof attachment.blobRef === "string" && attachment.blobRef ? attachment.blobRef : null;
228307
+ rows.push({
228308
+ item: {
228309
+ id: `msg:${message.id}:${index2}`,
228310
+ name: attachment.name,
228311
+ kind: "file",
228312
+ updated_at: attachment.updatedAt ?? message.createdAt,
228313
+ source: "message",
228314
+ blob_ref: blobRef,
228315
+ path: null,
228316
+ message_id: message.id,
228317
+ // 没有 blobRef 的是**历史纯文本附件**(契约注释:legacy text attachments may only
228318
+ // contain `name`)——没有可写回的载体,一律不给编辑入口。
228319
+ editable: blobRef !== null && isEditableFileName(attachment.name)
228320
+ },
228321
+ blobRef,
228322
+ declaredType: typeof attachment.contentType === "string" && attachment.contentType ? attachment.contentType : null
228323
+ });
228324
+ });
228325
+ }
228326
+ const metaCache = /* @__PURE__ */ new Map();
228327
+ const metaOf = (ref2) => {
228328
+ if (!blobs) return Promise.resolve(null);
228329
+ let hit = metaCache.get(ref2);
228330
+ if (!hit) {
228331
+ hit = blobs.meta(ref2).catch(() => null);
228332
+ metaCache.set(ref2, hit);
228333
+ }
228334
+ return hit;
228335
+ };
228336
+ return Promise.all(rows.map(async ({ item, blobRef, declaredType }) => {
228337
+ const meta = blobRef ? await metaOf(blobRef) : null;
228338
+ return {
228339
+ ...item,
228340
+ content_type: declaredType ?? meta?.contentType ?? null,
228341
+ size: meta?.size ?? null
228342
+ };
228343
+ }));
228344
+ }
228345
+ function mergeChatSessionFiles(messageItems, workdirItems, materializedCopies = /* @__PURE__ */ new Set()) {
228346
+ const out = [];
228347
+ const seenMessageKeys = /* @__PURE__ */ new Set();
228348
+ for (const item of [...messageItems].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at))) {
228349
+ const key = `${item.name}\0${item.blob_ref ?? ""}`;
228350
+ if (seenMessageKeys.has(key)) continue;
228351
+ seenMessageKeys.add(key);
228352
+ out.push(item);
228353
+ }
228354
+ const seenWorkdirPaths = /* @__PURE__ */ new Set();
228355
+ for (const item of workdirItems) {
228356
+ if (item.path && materializedCopies.has(item.path)) continue;
228357
+ if (seenWorkdirPaths.has(item.id)) continue;
228358
+ seenWorkdirPaths.add(item.id);
228359
+ out.push(item);
228360
+ }
228361
+ out.sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.name.localeCompare(b2.name));
228362
+ return out;
228363
+ }
228364
+ async function findMaterializedAttachmentCopies(messageItems, workdirItems, read) {
228365
+ const copies = /* @__PURE__ */ new Set();
228366
+ const attachments = messageItems.filter((item) => item.blob_ref && /^[a-f0-9]{64}$/.test(item.blob_ref));
228367
+ let budget = CHAT_ATTACHMENT_MAX_BYTES;
228368
+ const deadline = Date.now() + WORKDIR_WALK_BUDGET_MS;
228369
+ let reads = 0;
228370
+ for (const file of workdirItems) {
228371
+ const rootFile = file.path === file.name;
228372
+ const inputFile = file.path?.startsWith("inputs/attachments/") && !file.path.slice("inputs/attachments/".length).includes("/");
228373
+ if (!file.path || !rootFile && !inputFile) continue;
228374
+ const candidates = attachments.filter((attachment) => {
228375
+ const safe = safeAttachmentName(attachment.name);
228376
+ const numbered = file.name.endsWith(`-${safe}`) && /^\d+$/.test(file.name.slice(0, -(safe.length + 1)));
228377
+ return (rootFile ? file.name === attachment.name : file.name === safe || numbered) && attachment.size !== null && attachment.size === file.size;
228378
+ });
228379
+ if (Date.now() >= deadline) break;
228380
+ if (!candidates.length || file.size === null || file.size > budget || reads >= 10) continue;
228381
+ reads += 1;
228382
+ budget -= file.size;
228383
+ let timer;
228384
+ try {
228385
+ const result = await Promise.race([
228386
+ read(file.path, file.size),
228387
+ new Promise((resolve10) => {
228388
+ timer = setTimeout(() => resolve10(null), Math.max(0, deadline - Date.now()));
228389
+ })
228390
+ ]);
228391
+ if (!result?.ok) continue;
228392
+ const bytes2 = Buffer.from(result.contentBase64, "base64");
228393
+ if (bytes2.length !== file.size) continue;
228394
+ const hash2 = (0, import_node_crypto51.createHash)("sha256").update(bytes2).digest("hex");
228395
+ if (candidates.some((attachment) => attachment.blob_ref === hash2)) copies.add(file.path);
228396
+ } catch {
228397
+ } finally {
228398
+ if (timer) clearTimeout(timer);
228399
+ }
228400
+ }
228401
+ return copies;
228402
+ }
228403
+ var import_node_crypto51, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS;
228404
+ var init_files_view = __esm({
228405
+ "../server/src/domains/chat-sessions/files-view.ts"() {
228406
+ "use strict";
228407
+ import_node_crypto51 = require("node:crypto");
228408
+ init_chat_attachment_files();
228409
+ init_src();
228410
+ WORKDIR_WALK_MAX_DEPTH = 3;
228411
+ WORKDIR_WALK_MAX_ENTRIES = 200;
228412
+ WORKDIR_WALK_MAX_LIST_CALLS = 20;
228413
+ WORKDIR_WALK_BUDGET_MS = 1e4;
228414
+ }
228415
+ });
228416
+
228417
+ // ../server/src/domains/projects/chat-files.ts
228418
+ async function collectProjectChatFiles(chat, projectId2, meta) {
228419
+ if (!chat.listAttachmentMessages) return [];
228420
+ const messages = await chat.listAttachmentMessages(projectId2);
228421
+ const sessions = new Map(await Promise.all([...new Set(messages.map((m2) => m2.sessionId))].map(async (id) => [id, await chat.getSession(id)])));
228422
+ const visible = messages.filter((m2) => {
228423
+ const session = sessions.get(m2.sessionId);
228424
+ return session?.projectId === projectId2 && session.visibility !== "internal" && !session.analyzedRunId;
228425
+ });
228426
+ const byMessage = new Map(visible.map((m2) => [m2.messageId, m2]));
228427
+ const rows = await collectMessageFiles(visible.map((m2) => ({ ...m2, id: m2.messageId })), {
228428
+ meta
228429
+ });
228430
+ return mergeChatSessionFiles(rows, []).filter((row) => row.blob_ref !== null).map((row) => {
228431
+ const message = byMessage.get(row.message_id);
228432
+ const session = sessions.get(message.sessionId);
228433
+ return {
228434
+ projectId: projectId2,
228435
+ fileId: `chat:${message.sessionId}:${row.id}`,
228436
+ name: row.name,
228437
+ blobId: row.blob_ref,
228438
+ contentType: row.content_type ?? void 0,
228439
+ size: row.size,
228440
+ uploadedBy: session.humanActorId,
228441
+ uploadedAt: row.updated_at
228442
+ };
228443
+ });
228444
+ }
228445
+ var init_chat_files2 = __esm({
228446
+ "../server/src/domains/projects/chat-files.ts"() {
228447
+ "use strict";
228448
+ init_files_view();
228449
+ }
228450
+ });
228451
+
228077
228452
  // ../server/src/domains/projects/index.ts
228078
228453
  function createProjectsDomain(opts) {
228079
228454
  const build = (companyId, deps) => {
@@ -228089,7 +228464,8 @@ function createProjectsDomain(opts) {
228089
228464
  opts.listMembers ? async () => buildResolver(
228090
228465
  deps?.actorDirectory ?? opts.actorDirectory,
228091
228466
  await memberNameResolver(opts.listMembers, companyId)
228092
- ) : void 0
228467
+ ) : void 0,
228468
+ opts.listChatFiles ? (projectId2) => opts.listChatFiles(companyId, projectId2) : void 0
228093
228469
  );
228094
228470
  if (opts.onProjectNameChangedFor) {
228095
228471
  const handler = opts.onProjectNameChangedFor(companyId);
@@ -228141,6 +228517,7 @@ var init_projects = __esm({
228141
228517
  init_routes5();
228142
228518
  init_service4();
228143
228519
  init_collab();
228520
+ init_chat_files2();
228144
228521
  init_routes5();
228145
228522
  init_service4();
228146
228523
  init_projection();
@@ -228156,11 +228533,11 @@ async function isEmployedElsewhere(store, companyId, accountId) {
228156
228533
  const memberships = await store.listMembershipsForAccount(accountId);
228157
228534
  return memberships.some((m2) => m2.companyId !== companyId);
228158
228535
  }
228159
- var import_node_crypto51, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
228536
+ var import_node_crypto52, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
228160
228537
  var init_service6 = __esm({
228161
228538
  "../server/src/domains/companies/service.ts"() {
228162
228539
  "use strict";
228163
- import_node_crypto51 = require("node:crypto");
228540
+ import_node_crypto52 = require("node:crypto");
228164
228541
  init_console_url();
228165
228542
  ROLES = ["owner", "admin", "member"];
228166
228543
  INVITABLE_ROLES = ["admin", "member"];
@@ -228252,7 +228629,7 @@ var init_service6 = __esm({
228252
228629
  const existing = await this.store.getAccountByEmail(normalized4);
228253
228630
  if (existing) return existing;
228254
228631
  const account = {
228255
- id: `actor:human:${(0, import_node_crypto51.randomUUID)()}`,
228632
+ id: `actor:human:${(0, import_node_crypto52.randomUUID)()}`,
228256
228633
  email: normalized4,
228257
228634
  name: name ?? normalized4,
228258
228635
  status: "active"
@@ -228461,7 +228838,7 @@ var init_service6 = __esm({
228461
228838
  }
228462
228839
  const nowMs = Date.parse(this.now());
228463
228840
  const invitation = {
228464
- id: `invitation:${(0, import_node_crypto51.randomUUID)()}`,
228841
+ id: `invitation:${(0, import_node_crypto52.randomUUID)()}`,
228465
228842
  companyId,
228466
228843
  email: mail,
228467
228844
  role,
@@ -228592,7 +228969,7 @@ var init_service6 = __esm({
228592
228969
  const nowMs = Date.parse(this.now());
228593
228970
  const link = {
228594
228971
  companyId,
228595
- token: (0, import_node_crypto51.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
228972
+ token: (0, import_node_crypto52.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
228596
228973
  role: INVITE_LINK_ROLE,
228597
228974
  createdAt: new Date(nowMs).toISOString(),
228598
228975
  expiresAt: new Date(nowMs + INVITE_LINK_TTL_MS).toISOString(),
@@ -228649,7 +229026,7 @@ var init_service6 = __esm({
228649
229026
  const reusable = existing.find((inv) => !this.isExpired(inv));
228650
229027
  if (reusable) return reusable;
228651
229028
  const invitation = {
228652
- id: `invitation:${(0, import_node_crypto51.randomUUID)()}`,
229029
+ id: `invitation:${(0, import_node_crypto52.randomUUID)()}`,
228653
229030
  companyId: link.companyId,
228654
229031
  email: mail,
228655
229032
  role: link.role,
@@ -228887,11 +229264,11 @@ var init_service6 = __esm({
228887
229264
  });
228888
229265
 
228889
229266
  // ../server/src/domains/companies/offboarding.ts
228890
- var import_node_crypto52, MemberOffboardingService;
229267
+ var import_node_crypto53, MemberOffboardingService;
228891
229268
  var init_offboarding = __esm({
228892
229269
  "../server/src/domains/companies/offboarding.ts"() {
228893
229270
  "use strict";
228894
- import_node_crypto52 = require("node:crypto");
229271
+ import_node_crypto53 = require("node:crypto");
228895
229272
  init_service6();
228896
229273
  MemberOffboardingService = class {
228897
229274
  store;
@@ -228963,7 +229340,7 @@ var init_offboarding = __esm({
228963
229340
  const { ownedAgents, inFlight } = await this.snapshot(companyId, accountId);
228964
229341
  const at = this.now();
228965
229342
  const started = await this.store.startMemberOffboarding({
228966
- operationId: `off:${(0, import_node_crypto52.randomUUID)()}`,
229343
+ operationId: `off:${(0, import_node_crypto53.randomUUID)()}`,
228967
229344
  companyId,
228968
229345
  accountId,
228969
229346
  initiatedBy,
@@ -229473,18 +229850,18 @@ var init_routes6 = __esm({
229473
229850
 
229474
229851
  // ../server/src/domains/companies/agent-credentials.ts
229475
229852
  function hashAgentToken(token, secret) {
229476
- return HASH_PREFIX + (0, import_node_crypto53.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
229853
+ return HASH_PREFIX + (0, import_node_crypto54.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
229477
229854
  }
229478
229855
  function safeEqual(a, b2) {
229479
229856
  const ba = Buffer.from(a);
229480
229857
  const bb = Buffer.from(b2);
229481
- return ba.length === bb.length && (0, import_node_crypto53.timingSafeEqual)(ba, bb);
229858
+ return ba.length === bb.length && (0, import_node_crypto54.timingSafeEqual)(ba, bb);
229482
229859
  }
229483
- var import_node_crypto53, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
229860
+ var import_node_crypto54, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
229484
229861
  var init_agent_credentials = __esm({
229485
229862
  "../server/src/domains/companies/agent-credentials.ts"() {
229486
229863
  "use strict";
229487
- import_node_crypto53 = require("node:crypto");
229864
+ import_node_crypto54 = require("node:crypto");
229488
229865
  init_service6();
229489
229866
  HASH_PREFIX = "ac1$";
229490
229867
  TOKEN_PREFIX = "oak_";
@@ -229509,7 +229886,7 @@ var init_agent_credentials = __esm({
229509
229886
  if (employee.status !== "active") {
229510
229887
  throw new CompanyError(409, "AGENT_LEFT", `\u6570\u5B57\u5458\u5DE5\u5DF2\u79BB\u5F00\uFF0C\u4E0D\u80FD\u7B7E\u53D1\u51ED\u8BC1\uFF1A${agentId}`);
229511
229888
  }
229512
- const token = `${TOKEN_PREFIX}${(0, import_node_crypto53.randomBytes)(32).toString("base64url")}`;
229889
+ const token = `${TOKEN_PREFIX}${(0, import_node_crypto54.randomBytes)(32).toString("base64url")}`;
229513
229890
  const credential = {
229514
229891
  tokenHash: hashAgentToken(token, this.secret),
229515
229892
  companyId,
@@ -229929,7 +230306,7 @@ function gatewayInstanceSlug(gatewayUrl) {
229929
230306
  }
229930
230307
  function connectInstanceName(gatewayUrl, token, companyId) {
229931
230308
  const scope = companyId?.trim();
229932
- const suffix = scope ? `c${(0, import_node_crypto54.createHash)("sha256").update(`${gatewayUrl}\0${scope}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
230309
+ const suffix = scope ? `c${(0, import_node_crypto55.createHash)("sha256").update(`${gatewayUrl}\0${scope}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
229933
230310
  const base = gatewayInstanceSlug(gatewayUrl);
229934
230311
  return base.slice(0, Math.max(1, 64 - suffix.length - 1)) + "-" + suffix;
229935
230312
  }
@@ -230258,12 +230635,12 @@ function buildNpxCmd(p2) {
230258
230635
  const prefix = daemonNpmPrefix();
230259
230636
  return `npm install -g --prefer-online --prefix ${prefix} ${DAEMON_NPM_PACKAGE}@latest && ${prefix}/bin/oasis daemon start --instance ${shellQuote(instanceName)} --server-ws ${shellQuote(p2.gatewayUrl)}${companyPart}${namePart} --pkg ${shellQuote(DAEMON_NPM_PACKAGE)} --upgrade --enroll-token ${shellQuote(p2.token)}`;
230260
230637
  }
230261
- var import_node_crypto54;
230638
+ var import_node_crypto55;
230262
230639
  var init_connect_script = __esm({
230263
230640
  "../server/src/domains/nodes/connect-script.ts"() {
230264
230641
  "use strict";
230265
230642
  init_src();
230266
- import_node_crypto54 = require("node:crypto");
230643
+ import_node_crypto55 = require("node:crypto");
230267
230644
  }
230268
230645
  });
230269
230646
 
@@ -230697,7 +231074,7 @@ function nodesDomain(deps) {
230697
231074
  }
230698
231075
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
230699
231076
  } else {
230700
- nodeId = `node-${(0, import_node_crypto55.randomUUID)().slice(0, 8)}`;
231077
+ nodeId = `node-${(0, import_node_crypto56.randomUUID)().slice(0, 8)}`;
230701
231078
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
230702
231079
  }
230703
231080
  const existingNode = await deps.nodeStore.getNode(nodeId);
@@ -230859,13 +231236,13 @@ function normalizeGatewayUrl(raw) {
230859
231236
  }
230860
231237
  }
230861
231238
  function newNodeId() {
230862
- return "node-" + (0, import_node_crypto55.randomUUID)().replace(/-/g, "").slice(0, 12);
231239
+ return "node-" + (0, import_node_crypto56.randomUUID)().replace(/-/g, "").slice(0, 12);
230863
231240
  }
230864
- var import_node_crypto55, import_node_fs15, import_node_url6, import_node_path21;
231241
+ var import_node_crypto56, import_node_fs15, import_node_url6, import_node_path21;
230865
231242
  var init_routes7 = __esm({
230866
231243
  "../server/src/domains/nodes/routes.ts"() {
230867
231244
  "use strict";
230868
- import_node_crypto55 = require("node:crypto");
231245
+ import_node_crypto56 = require("node:crypto");
230869
231246
  import_node_fs15 = require("node:fs");
230870
231247
  import_node_url6 = require("node:url");
230871
231248
  import_node_path21 = require("node:path");
@@ -231346,7 +231723,7 @@ function createBundle(root, target, input) {
231346
231723
  }
231347
231724
  }
231348
231725
  const bytes2 = fs25.statSync(absBundle).size;
231349
- const digest = (0, import_node_crypto56.createHash)("sha256").update(fs25.readFileSync(absBundle)).digest("hex");
231726
+ const digest = (0, import_node_crypto57.createHash)("sha256").update(fs25.readFileSync(absBundle)).digest("hex");
231350
231727
  const relBase = `inputs/git-bundles/${safeRepo}-${commit.slice(0, 12)}`;
231351
231728
  return {
231352
231729
  absoluteBundlePath: absBundle,
@@ -231442,12 +231819,12 @@ function classifyGitError(fallback, err, where) {
231442
231819
  function oneLine(msg) {
231443
231820
  return msg.split("\n").map((s2) => s2.trim()).filter(Boolean).slice(0, 3).join(" ");
231444
231821
  }
231445
- var import_node_child_process17, import_node_crypto56, fs25, os7, path25, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
231822
+ var import_node_child_process17, import_node_crypto57, fs25, os7, path25, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
231446
231823
  var init_readonly_bundle = __esm({
231447
231824
  "../server/src/git/readonly-bundle.ts"() {
231448
231825
  "use strict";
231449
231826
  import_node_child_process17 = require("node:child_process");
231450
- import_node_crypto56 = require("node:crypto");
231827
+ import_node_crypto57 = require("node:crypto");
231451
231828
  fs25 = __toESM(require("node:fs"), 1);
231452
231829
  os7 = __toESM(require("node:os"), 1);
231453
231830
  path25 = __toESM(require("node:path"), 1);
@@ -231467,7 +231844,7 @@ var init_readonly_bundle = __esm({
231467
231844
 
231468
231845
  // ../server/src/domains/collab/create-seeded-workorder.ts
231469
231846
  function makeSeededWorkorderCreator(deps) {
231470
- const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto57.randomUUID)().slice(0, 8)}`);
231847
+ const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto58.randomUUID)().slice(0, 8)}`);
231471
231848
  return async (input) => {
231472
231849
  const company = input.companyId && deps.resolveCompany ? await deps.resolveCompany(input.companyId) : void 0;
231473
231850
  const kernel = company?.kernel ?? deps.kernel;
@@ -231560,7 +231937,7 @@ function makeSeededWorkorderCreator(deps) {
231560
231937
  };
231561
231938
  }
231562
231939
  function workspaceForKey(key) {
231563
- return `ws:wo-${(0, import_node_crypto57.createHash)("sha256").update(key).digest("hex").slice(0, 8)}`;
231940
+ return `ws:wo-${(0, import_node_crypto58.createHash)("sha256").update(key).digest("hex").slice(0, 8)}`;
231564
231941
  }
231565
231942
  function existingWorkorder(kernel, workspace) {
231566
231943
  const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === workspace);
@@ -231573,11 +231950,11 @@ function existingWorkorder(kernel, workspace) {
231573
231950
  spawned: arts.map((a) => ({ id: a.id, type: a.type, owner: a.owner }))
231574
231951
  };
231575
231952
  }
231576
- var import_node_crypto57, enc2;
231953
+ var import_node_crypto58, enc2;
231577
231954
  var init_create_seeded_workorder = __esm({
231578
231955
  "../server/src/domains/collab/create-seeded-workorder.ts"() {
231579
231956
  "use strict";
231580
- import_node_crypto57 = require("node:crypto");
231957
+ import_node_crypto58 = require("node:crypto");
231581
231958
  init_uncategorized_project();
231582
231959
  init_planner();
231583
231960
  enc2 = (s2) => new TextEncoder().encode(s2);
@@ -232974,12 +233351,12 @@ function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, ope
232974
233351
  if (attempt.status === "no-output") return handoff?.unresolvedIssues.length ? "blocked" : "no_change";
232975
233352
  return "failed";
232976
233353
  }
232977
- var import_node_crypto58, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
233354
+ var import_node_crypto59, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
232978
233355
  var init_service8 = __esm({
232979
233356
  "../server/src/domains/execution-continuity/service.ts"() {
232980
233357
  "use strict";
232981
233358
  init_src();
232982
- import_node_crypto58 = require("node:crypto");
233359
+ import_node_crypto59 = require("node:crypto");
232983
233360
  init_src5();
232984
233361
  BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
232985
233362
  HANDOFF_OUTCOMES2 = /* @__PURE__ */ new Set(["succeeded", "no-output", "failed", "cancelled", "timeout", "orphaned"]);
@@ -233210,7 +233587,7 @@ var init_service8 = __esm({
233210
233587
  if (typeof expectedHash !== "string" || !this.readBundleFile) return null;
233211
233588
  const injectedResume = this.readBundleFile(attempt.id, "execution/RESUME.md");
233212
233589
  if (!injectedResume) return null;
233213
- const actualHash = (0, import_node_crypto58.createHash)("sha256").update(injectedResume).digest("hex");
233590
+ const actualHash = (0, import_node_crypto59.createHash)("sha256").update(injectedResume).digest("hex");
233214
233591
  if (actualHash !== expectedHash) return null;
233215
233592
  const recoverySnapshot = parseResumePackSnapshot(injectedResume);
233216
233593
  if (!recoverySnapshot || recoverySnapshot.scenario !== "redispatch" || !recoverySnapshot.previousAttempt || recoverySnapshot.node.nodeId !== attempt.nodeId || recoverySnapshot.jobKey !== attempt.jobKey || recoverySnapshot.part !== attempt.part) return null;
@@ -233801,171 +234178,6 @@ var init_execution_continuity2 = __esm({
233801
234178
  }
233802
234179
  });
233803
234180
 
233804
- // ../server/src/domains/chat-sessions/files-view.ts
233805
- function reasonFromWorkdirCode(code2) {
233806
- switch (code2) {
233807
- case "NODE_OFFLINE":
233808
- return "NODE_OFFLINE";
233809
- case "TIMEOUT":
233810
- return "TIMEOUT";
233811
- default:
233812
- return "ERROR";
233813
- }
233814
- }
233815
- function joinRel(dir, name) {
233816
- return dir ? `${dir}/${name}` : name;
233817
- }
233818
- async function collectWorkdirFiles(bridge, loc, now = Date.now) {
233819
- const deadline = now() + WORKDIR_WALK_BUDGET_MS;
233820
- const items = [];
233821
- let listCalls = 0;
233822
- let truncated = false;
233823
- const listDir = (path41) => {
233824
- listCalls += 1;
233825
- return bridge.list(loc.nodeId, {
233826
- workdirKey: loc.workdirKey,
233827
- runtimeKind: loc.runtimeKind,
233828
- rtId: loc.rtId,
233829
- path: path41
233830
- });
233831
- };
233832
- const root = await listDir("");
233833
- if (!root.ok) {
233834
- if (root.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
233835
- return { ok: false, reason: reasonFromWorkdirCode(root.code) };
233836
- }
233837
- let level = [{ path: "", outcome: root }];
233838
- for (let depth = 0; depth <= WORKDIR_WALK_MAX_DEPTH; depth += 1) {
233839
- const nextDirs = [];
233840
- for (const { path: dir, outcome } of level) {
233841
- if (!outcome.ok) {
233842
- truncated = true;
233843
- continue;
233844
- }
233845
- if (outcome.truncated) truncated = true;
233846
- for (const entry of outcome.entries) {
233847
- if (entry.type === "dir") {
233848
- nextDirs.push(joinRel(dir, entry.name));
233849
- continue;
233850
- }
233851
- if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
233852
- truncated = true;
233853
- continue;
233854
- }
233855
- const rel = joinRel(dir, entry.name);
233856
- items.push({
233857
- id: `workdir:${rel}`,
233858
- name: entry.name,
233859
- kind: "file",
233860
- // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
233861
- // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
233862
- content_type: null,
233863
- size: entry.size,
233864
- updated_at: entry.mtime,
233865
- source: "workdir",
233866
- blob_ref: null,
233867
- path: rel,
233868
- message_id: null,
233869
- // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
233870
- // 这里是**按能力不渲染**的判据,不是置灰假钮。
233871
- editable: false
233872
- });
233873
- }
233874
- }
233875
- if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
233876
- if (nextDirs.length > 0) truncated = true;
233877
- break;
233878
- }
233879
- const budget = WORKDIR_WALK_MAX_LIST_CALLS - listCalls;
233880
- if (budget <= 0 || now() >= deadline) {
233881
- truncated = true;
233882
- break;
233883
- }
233884
- const take = nextDirs.slice(0, budget);
233885
- if (take.length < nextDirs.length) truncated = true;
233886
- const outcomes = await Promise.all(take.map((p2) => listDir(p2)));
233887
- level = take.map((p2, i) => ({ path: p2, outcome: outcomes[i] }));
233888
- }
233889
- return { ok: true, items, truncated };
233890
- }
233891
- async function collectMessageFiles(messages, blobs) {
233892
- const rows = [];
233893
- for (const message of messages) {
233894
- if (!Array.isArray(message.attachments)) continue;
233895
- message.attachments.forEach((attachment, index2) => {
233896
- if (!attachment || typeof attachment.name !== "string" || !attachment.name) return;
233897
- const blobRef = typeof attachment.blobRef === "string" && attachment.blobRef ? attachment.blobRef : null;
233898
- rows.push({
233899
- item: {
233900
- id: `msg:${message.id}:${index2}`,
233901
- name: attachment.name,
233902
- kind: "file",
233903
- updated_at: attachment.updatedAt ?? message.createdAt,
233904
- source: "message",
233905
- blob_ref: blobRef,
233906
- path: null,
233907
- message_id: message.id,
233908
- // 没有 blobRef 的是**历史纯文本附件**(契约注释:legacy text attachments may only
233909
- // contain `name`)——没有可写回的载体,一律不给编辑入口。
233910
- editable: blobRef !== null && isEditableFileName(attachment.name)
233911
- },
233912
- blobRef,
233913
- declaredType: typeof attachment.contentType === "string" && attachment.contentType ? attachment.contentType : null
233914
- });
233915
- });
233916
- }
233917
- const metaCache = /* @__PURE__ */ new Map();
233918
- const metaOf = (ref2) => {
233919
- if (!blobs) return Promise.resolve(null);
233920
- let hit = metaCache.get(ref2);
233921
- if (!hit) {
233922
- hit = blobs.meta(ref2).catch(() => null);
233923
- metaCache.set(ref2, hit);
233924
- }
233925
- return hit;
233926
- };
233927
- return Promise.all(rows.map(async ({ item, blobRef, declaredType }) => {
233928
- const meta = blobRef ? await metaOf(blobRef) : null;
233929
- return {
233930
- ...item,
233931
- content_type: declaredType ?? meta?.contentType ?? null,
233932
- size: meta?.size ?? null
233933
- };
233934
- }));
233935
- }
233936
- function mergeChatSessionFiles(messageItems, workdirItems) {
233937
- const out = [];
233938
- const seenMessageKeys = /* @__PURE__ */ new Set();
233939
- const messageNames = /* @__PURE__ */ new Set();
233940
- for (const item of messageItems) {
233941
- const key = `${item.name}\0${item.blob_ref ?? ""}`;
233942
- if (seenMessageKeys.has(key)) continue;
233943
- seenMessageKeys.add(key);
233944
- messageNames.add(item.name);
233945
- out.push(item);
233946
- }
233947
- const seenWorkdirPaths = /* @__PURE__ */ new Set();
233948
- for (const item of workdirItems) {
233949
- if (item.path === item.name && messageNames.has(item.name)) continue;
233950
- if (seenWorkdirPaths.has(item.id)) continue;
233951
- seenWorkdirPaths.add(item.id);
233952
- out.push(item);
233953
- }
233954
- out.sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.name.localeCompare(b2.name));
233955
- return out;
233956
- }
233957
- var WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS;
233958
- var init_files_view = __esm({
233959
- "../server/src/domains/chat-sessions/files-view.ts"() {
233960
- "use strict";
233961
- init_src();
233962
- WORKDIR_WALK_MAX_DEPTH = 3;
233963
- WORKDIR_WALK_MAX_ENTRIES = 200;
233964
- WORKDIR_WALK_MAX_LIST_CALLS = 20;
233965
- WORKDIR_WALK_BUDGET_MS = 1e4;
233966
- }
233967
- });
233968
-
233969
234181
  // ../server/src/domains/collab/gap-thread.ts
233970
234182
  function gapThreadSessionId(executorActorId) {
233971
234183
  return `${GAP_THREAD_SESSION_PREFIX}${executorActorId}`;
@@ -234470,7 +234682,7 @@ function createChatSessionsDomain(opts) {
234470
234682
  if (!text5.trim()) throw new ApiError(400, "BAD_REQUEST", "text required");
234471
234683
  const now = Date.now();
234472
234684
  sweepDrafts(now);
234473
- const id = `cd_${(0, import_node_crypto59.randomUUID)()}`;
234685
+ const id = `cd_${(0, import_node_crypto60.randomUUID)()}`;
234474
234686
  drafts.set(id, { text: text5, actor: req.auth.actor, expiresAt: now + DRAFT_TTL_MS });
234475
234687
  return { status: 201, body: { id, expiresInMs: DRAFT_TTL_MS } };
234476
234688
  });
@@ -234522,7 +234734,7 @@ function createChatSessionsDomain(opts) {
234522
234734
  const runtimeId = await currentRuntimeId(body2.aiActorId) ?? "unknown";
234523
234735
  const now = (/* @__PURE__ */ new Date()).toISOString();
234524
234736
  const session = {
234525
- id: (0, import_node_crypto59.randomUUID)(),
234737
+ id: (0, import_node_crypto60.randomUUID)(),
234526
234738
  humanActorId: req.auth.actor,
234527
234739
  // 注:这里**刻意不写 companyId**。新会话要不要带公司归属,按 ws:wo-6d588f8e 的 ADR
234528
234740
  // (每公司 schema、最终移除表内 company 列)统一定,不在本节点自行发明。
@@ -234628,7 +234840,7 @@ function createChatSessionsDomain(opts) {
234628
234840
  if (body2.role !== "user" && body2.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
234629
234841
  const role = body2.role;
234630
234842
  const msg = await store2.appendMessage({
234631
- id: (0, import_node_crypto59.randomUUID)(),
234843
+ id: (0, import_node_crypto60.randomUUID)(),
234632
234844
  sessionId: req.params.id,
234633
234845
  role,
234634
234846
  content: role === "user" ? stripInjectedChatContext(body2.content) : body2.content,
@@ -234669,7 +234881,7 @@ function createChatSessionsDomain(opts) {
234669
234881
  const known = key ? cur.find((x2) => x2.clientKey === key) : void 0;
234670
234882
  if (!known && cur.length >= QUEUE_MAX) throw new ApiError(409, "QUEUE_FULL", `\u961F\u5217\u5DF2\u6EE1\uFF08\u4E0A\u9650 ${QUEUE_MAX} \u6761\uFF09`);
234671
234883
  const item = await store.enqueuePendingMessage({
234672
- id: (0, import_node_crypto59.randomUUID)(),
234884
+ id: (0, import_node_crypto60.randomUUID)(),
234673
234885
  chatSessionId: sid,
234674
234886
  text: text5,
234675
234887
  ...attachments.length ? { attachments } : {},
@@ -235024,6 +235236,7 @@ function createChatSessionsDomain(opts) {
235024
235236
  const blobAccess = opts.resolveBlobs ? await opts.resolveBlobs(req.auth?.companyId) : opts.blobs;
235025
235237
  const messageItems = await collectMessageFiles(attachmentRows, blobAccess);
235026
235238
  let workdirItems = [];
235239
+ let materializedCopies = /* @__PURE__ */ new Set();
235027
235240
  let workdirAvailable = false;
235028
235241
  let workdirTruncated = false;
235029
235242
  let workdirReason = "NOT_ENABLED";
@@ -235037,13 +235250,18 @@ function createChatSessionsDomain(opts) {
235037
235250
  if (outcome.ok) {
235038
235251
  workdirAvailable = true;
235039
235252
  workdirItems = outcome.items;
235253
+ materializedCopies = await findMaterializedAttachmentCopies(
235254
+ messageItems,
235255
+ workdirItems,
235256
+ (path41, maxBytes) => bridge.read(located.loc.nodeId, { ...located.loc, path: path41 }, maxBytes)
235257
+ );
235040
235258
  workdirTruncated = outcome.truncated;
235041
235259
  } else {
235042
235260
  workdirReason = outcome.reason;
235043
235261
  }
235044
235262
  }
235045
235263
  }
235046
- const items = mergeChatSessionFiles(messageItems, workdirItems);
235264
+ const items = mergeChatSessionFiles(messageItems, workdirItems, materializedCopies);
235047
235265
  const body2 = {
235048
235266
  total: items.length,
235049
235267
  items,
@@ -235112,11 +235330,11 @@ function createChatSessionsDomain(opts) {
235112
235330
  });
235113
235331
  };
235114
235332
  }
235115
- var import_node_crypto59, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS, WORKDIR_READ_MAX_BYTES;
235333
+ var import_node_crypto60, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS, WORKDIR_READ_MAX_BYTES;
235116
235334
  var init_chat_sessions = __esm({
235117
235335
  "../server/src/domains/chat-sessions/index.ts"() {
235118
235336
  "use strict";
235119
- import_node_crypto59 = require("node:crypto");
235337
+ import_node_crypto60 = require("node:crypto");
235120
235338
  init_src();
235121
235339
  init_chat_session();
235122
235340
  init_files_view();
@@ -236520,7 +236738,7 @@ function busyError(chatSessionId, blockedBy) {
236520
236738
  return `\u76EE\u6807\u4F1A\u8BDD ${chatSessionId} \u6B63\u6709\u4E00\u8F6E\u5728\u8DD1${blockedBy ? `\uFF08${blockedBy}\uFF09` : ""}\u2014\u2014\u672C\u6B21\u89E6\u53D1\u5DF2\u4E22\u5F03\uFF0C\u4E0D\u6392\u961F\u3001\u4E0D\u8865\u8DD1`;
236521
236739
  }
236522
236740
  function genWebhookToken() {
236523
- return `owt_${(0, import_node_crypto60.randomBytes)(32).toString("base64url")}`;
236741
+ return `owt_${(0, import_node_crypto61.randomBytes)(32).toString("base64url")}`;
236524
236742
  }
236525
236743
  function validateTitleTemplate(tpl) {
236526
236744
  const residue = tpl.replace(DATE_TOKEN, "");
@@ -236576,11 +236794,11 @@ function appendSourceNote(brief, automation, source, atIso) {
236576
236794
  ---
236577
236795
  \u672C\u5DE5\u5355\u7531\u81EA\u52A8\u5316\u300C${automation.name}\u300D\u4E8E ${atIso} \u89E6\u53D1\uFF08\u6765\u6E90\uFF1A${source}\uFF09\u3002\u5F00\u5DE5\u540E\u8BF7\u628A\u5DE5\u5355\u6807\u9898\u6539\u6210\u51C6\u786E\u53CD\u6620\u5B9E\u9645\u5DE5\u4F5C\u7684\u63CF\u8FF0\u3002`;
236578
236796
  }
236579
- var import_node_crypto60, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
236797
+ var import_node_crypto61, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
236580
236798
  var init_service9 = __esm({
236581
236799
  "../server/src/domains/automations/service.ts"() {
236582
236800
  "use strict";
236583
- import_node_crypto60 = require("node:crypto");
236801
+ import_node_crypto61 = require("node:crypto");
236584
236802
  init_src();
236585
236803
  init_registry3();
236586
236804
  init_attribution();
@@ -237394,11 +237612,11 @@ function verifySignature(rawBody, headers, secret) {
237394
237612
  if (!secret) return "not_required";
237395
237613
  const given = header1(headers, "x-hub-signature-256");
237396
237614
  if (!given) return "missing";
237397
- const expected = `sha256=${(0, import_node_crypto61.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
237615
+ const expected = `sha256=${(0, import_node_crypto62.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
237398
237616
  const a = Buffer.from(given);
237399
237617
  const b2 = Buffer.from(expected);
237400
237618
  if (a.length !== b2.length) return "invalid";
237401
- return (0, import_node_crypto61.timingSafeEqual)(a, b2) ? "valid" : "invalid";
237619
+ return (0, import_node_crypto62.timingSafeEqual)(a, b2) ? "valid" : "invalid";
237402
237620
  }
237403
237621
  function eventAllowed(filters, envelope) {
237404
237622
  if (!filters || filters.length === 0) return true;
@@ -237428,11 +237646,11 @@ function splitEvent(envelope) {
237428
237646
  }
237429
237647
  return { eventName, actionCandidates: candidates };
237430
237648
  }
237431
- var import_node_crypto61, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
237649
+ var import_node_crypto62, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
237432
237650
  var init_webhook = __esm({
237433
237651
  "../server/src/domains/automations/webhook.ts"() {
237434
237652
  "use strict";
237435
- import_node_crypto61 = require("node:crypto");
237653
+ import_node_crypto62 = require("node:crypto");
237436
237654
  MAX_BODY_BYTES = 256 * 1024;
237437
237655
  AutomationWebhookService = class {
237438
237656
  store;
@@ -238588,7 +238806,7 @@ function playbooksDomain(opts) {
238588
238806
  opts.overrides.set(companyOf(req), ref2, nodeKey, nextOverride);
238589
238807
  try {
238590
238808
  await opts.audit?.({
238591
- id: `reg_${(0, import_node_crypto62.randomUUID)()}`,
238809
+ id: `reg_${(0, import_node_crypto63.randomUUID)()}`,
238592
238810
  actor: req.auth.actor,
238593
238811
  kind: "registry_change",
238594
238812
  target: `playbook:${ref2}#${nodeKey}`,
@@ -238605,11 +238823,11 @@ function playbooksDomain(opts) {
238605
238823
  });
238606
238824
  };
238607
238825
  }
238608
- var import_node_crypto62;
238826
+ var import_node_crypto63;
238609
238827
  var init_routes12 = __esm({
238610
238828
  "../server/src/domains/playbooks/routes.ts"() {
238611
238829
  "use strict";
238612
- import_node_crypto62 = require("node:crypto");
238830
+ import_node_crypto63 = require("node:crypto");
238613
238831
  init_router();
238614
238832
  init_registry3();
238615
238833
  init_planner();
@@ -238723,17 +238941,17 @@ function completionFingerprint(workspace, members) {
238723
238941
  workspace,
238724
238942
  members: [...members].sort((a, b2) => a.artifactId < b2.artifactId ? -1 : a.artifactId > b2.artifactId ? 1 : 0).map((m2) => ({ artifactId: m2.artifactId, currentRevisionId: m2.currentRevisionId ?? null }))
238725
238943
  });
238726
- return (0, import_node_crypto63.createHash)("sha256").update(canonical).digest("hex");
238944
+ return (0, import_node_crypto64.createHash)("sha256").update(canonical).digest("hex");
238727
238945
  }
238728
238946
  function eventConsumptionKey(args) {
238729
238947
  const raw = `project-event:${EVENT_KEY_VERSION}:${args.triggerId}:${args.event}:${args.workspace}:${args.fingerprint}`;
238730
- return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto63.createHash)("sha256").update(raw).digest("hex")}`;
238948
+ return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto64.createHash)("sha256").update(raw).digest("hex")}`;
238731
238949
  }
238732
- var import_node_crypto63, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
238950
+ var import_node_crypto64, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
238733
238951
  var init_project_event_poller = __esm({
238734
238952
  "../server/src/automations/project-event-poller.ts"() {
238735
238953
  "use strict";
238736
- import_node_crypto63 = require("node:crypto");
238954
+ import_node_crypto64 = require("node:crypto");
238737
238955
  EVENT_KEY_VERSION = "v1";
238738
238956
  EMPTY_METRICS = () => ({
238739
238957
  scanDurationMs: 0,
@@ -238941,7 +239159,7 @@ function routeToSession(hub, nodeId, dispatchId, msg) {
238941
239159
  function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
238942
239160
  const unknownIfInterrupting = input.interrupt ? { interrupt: "unknown" } : {};
238943
239161
  if (entry.exited) return Promise.resolve({ accepted: false, reason: "session-closing", ...unknownIfInterrupting });
238944
- const requestId = (0, import_node_crypto64.randomUUID)();
239162
+ const requestId = (0, import_node_crypto65.randomUUID)();
238945
239163
  const sent = routeToSession(hub, nodeId, dispatchId, { type: "append_input", dispatchId, input, requestId });
238946
239164
  if (!sent) return Promise.resolve({ accepted: false, reason: "session-closing", ...unknownIfInterrupting });
238947
239165
  return new Promise((resolve10) => {
@@ -238954,11 +239172,11 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
238954
239172
  entry.appendWaiters.push({ requestId, resolve: resolve10, timer });
238955
239173
  });
238956
239174
  }
238957
- var import_node_crypto64, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
239175
+ var import_node_crypto65, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
238958
239176
  var init_daemon_adapter = __esm({
238959
239177
  "../server/src/daemon-adapter.ts"() {
238960
239178
  "use strict";
238961
- import_node_crypto64 = require("node:crypto");
239179
+ import_node_crypto65 = require("node:crypto");
238962
239180
  init_src();
238963
239181
  STASH_TTL_MS = 5 * 6e4;
238964
239182
  STASH_MAX_FRAMES_PER_ID = 500;
@@ -239308,7 +239526,7 @@ var init_daemon_adapter = __esm({
239308
239526
  async spawn(job) {
239309
239527
  const nodeId = job.binding?.nodeId;
239310
239528
  if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
239311
- const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto64.randomUUID)()}`;
239529
+ const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto65.randomUUID)()}`;
239312
239530
  if (job.dispatchId && this.pending.has(dispatchId)) {
239313
239531
  return this.recover(dispatchId, nodeId, job.binding?.runtimeKind, job.actor);
239314
239532
  }
@@ -239522,7 +239740,7 @@ var init_daemon_adapter = __esm({
239522
239740
  }));
239523
239741
  }
239524
239742
  request(nodeId, buildFrame) {
239525
- const requestId = (0, import_node_crypto64.randomUUID)();
239743
+ const requestId = (0, import_node_crypto65.randomUUID)();
239526
239744
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
239527
239745
  if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
239528
239746
  return new Promise((resolve10) => {
@@ -239630,13 +239848,13 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
239630
239848
  if (sha(body2) === lastPushedHash) return { kind: "echo" };
239631
239849
  return { kind: "human-edit", content: body2, updatedBy: page.updatedBy };
239632
239850
  }
239633
- var import_node_crypto65, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
239851
+ var import_node_crypto66, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
239634
239852
  var init_engine = __esm({
239635
239853
  "../server/src/mirror/engine.ts"() {
239636
239854
  "use strict";
239637
- import_node_crypto65 = require("node:crypto");
239855
+ import_node_crypto66 = require("node:crypto");
239638
239856
  init_src();
239639
- sha = (s2) => (0, import_node_crypto65.createHash)("sha256").update(s2, "utf8").digest("hex");
239857
+ sha = (s2) => (0, import_node_crypto66.createHash)("sha256").update(s2, "utf8").digest("hex");
239640
239858
  enc3 = (s2) => new TextEncoder().encode(s2);
239641
239859
  dec = (b2) => new TextDecoder().decode(b2);
239642
239860
  MirrorEngine = class {
@@ -239883,11 +240101,11 @@ function humanExitCause(exit3) {
239883
240101
  const base = (exit3.reason ? label[exit3.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit3.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
239884
240102
  return exit3.errorMessage ? `${base}\uFF1A${exit3.errorMessage.slice(0, 200)}` : base;
239885
240103
  }
239886
- var import_node_crypto66, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
240104
+ var import_node_crypto67, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
239887
240105
  var init_worker = __esm({
239888
240106
  "../server/src/coordinator/worker.ts"() {
239889
240107
  "use strict";
239890
- import_node_crypto66 = require("node:crypto");
240108
+ import_node_crypto67 = require("node:crypto");
239891
240109
  init_src5();
239892
240110
  init_identity();
239893
240111
  AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
@@ -240156,7 +240374,7 @@ ${HIGH_RISK_COMMANDS}`;
240156
240374
  return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
240157
240375
  }
240158
240376
  evaluationKey(kind, artifactId, evidence) {
240159
- const fingerprint2 = (0, import_node_crypto66.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
240377
+ const fingerprint2 = (0, import_node_crypto67.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
240160
240378
  return `${kind}:${artifactId}:${fingerprint2}`;
240161
240379
  }
240162
240380
  artifactEvidence(id) {
@@ -240535,8 +240753,8 @@ ${ctx.nodeFault}
240535
240753
  this.deps.log?.(`[coordinator] ${workspace} \u5168\u5C40\u4E0A\u4E0B\u6587\u8BFB\u53D6\u5931\u8D25\uFF0C\u7EE7\u7EED\u6CBF\u7528\u539F\u534F\u8C03\u8005\u4EFB\u52A1\uFF1A${String(error2)}`);
240536
240754
  }
240537
240755
  }
240538
- const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto66.randomUUID)();
240539
- const coordinatorRunId = `coordinate:${(0, import_node_crypto66.randomUUID)()}`;
240756
+ const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto67.randomUUID)();
240757
+ const coordinatorRunId = `coordinate:${(0, import_node_crypto67.randomUUID)()}`;
240540
240758
  const taskWithContext = coordinatorContext ? [
240541
240759
  task,
240542
240760
  ``,
@@ -240580,7 +240798,7 @@ ${ctx.nodeFault}
240580
240798
  const startedAtMs = Date.now();
240581
240799
  let trajectoryStarted = false;
240582
240800
  if (this.deps.trajectory) {
240583
- const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto66.createHash)("sha256").update(content3).digest("hex")]));
240801
+ const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto67.createHash)("sha256").update(content3).digest("hex")]));
240584
240802
  const session = {
240585
240803
  runId: coordinatorRunId,
240586
240804
  runtimeSessionId,
@@ -240785,11 +241003,11 @@ var init_pg_ident = __esm({
240785
241003
  });
240786
241004
 
240787
241005
  // ../storage/src/postgres.ts
240788
- var import_node_crypto67, ident3, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
241006
+ var import_node_crypto68, ident3, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
240789
241007
  var init_postgres = __esm({
240790
241008
  "../storage/src/postgres.ts"() {
240791
241009
  "use strict";
240792
- import_node_crypto67 = require("node:crypto");
241010
+ import_node_crypto68 = require("node:crypto");
240793
241011
  init_pg_ident();
240794
241012
  init_esm();
240795
241013
  init_src();
@@ -240969,7 +241187,7 @@ var init_postgres = __esm({
240969
241187
  return new _PostgresBlobStore(pool, schema);
240970
241188
  }
240971
241189
  async put(bytes2) {
240972
- const hash2 = (0, import_node_crypto67.createHash)("sha256").update(bytes2).digest("hex");
241190
+ const hash2 = (0, import_node_crypto68.createHash)("sha256").update(bytes2).digest("hex");
240973
241191
  await this.pool.query(
240974
241192
  `INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
240975
241193
  [hash2, Buffer.from(bytes2), bytes2.byteLength, sniffContentType(bytes2) ?? null]
@@ -249392,11 +249610,11 @@ var init_postgres_delegations = __esm({
249392
249610
  });
249393
249611
 
249394
249612
  // ../storage/src/postgres-nodes.ts
249395
- var import_node_crypto68, ident12, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
249613
+ var import_node_crypto69, ident12, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
249396
249614
  var init_postgres_nodes = __esm({
249397
249615
  "../storage/src/postgres-nodes.ts"() {
249398
249616
  "use strict";
249399
- import_node_crypto68 = require("node:crypto");
249617
+ import_node_crypto69 = require("node:crypto");
249400
249618
  init_pg_ident();
249401
249619
  init_esm();
249402
249620
  ident12 = (s2) => {
@@ -249606,7 +249824,7 @@ var init_postgres_nodes = __esm({
249606
249824
  return store;
249607
249825
  }
249608
249826
  issue(nodeId) {
249609
- const token = `ont_${(0, import_node_crypto68.randomBytes)(24).toString("base64url")}`;
249827
+ const token = `ont_${(0, import_node_crypto69.randomBytes)(24).toString("base64url")}`;
249610
249828
  this.cache.set(token, nodeId);
249611
249829
  void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
249612
249830
  return token;
@@ -249872,11 +250090,11 @@ var init_postgres_type_registry = __esm({
249872
250090
  });
249873
250091
 
249874
250092
  // ../storage/src/postgres-actor-memory.ts
249875
- var import_node_crypto69, matchClause, ident14, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
250093
+ var import_node_crypto70, matchClause, ident14, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
249876
250094
  var init_postgres_actor_memory = __esm({
249877
250095
  "../storage/src/postgres-actor-memory.ts"() {
249878
250096
  "use strict";
249879
- import_node_crypto69 = require("node:crypto");
250097
+ import_node_crypto70 = require("node:crypto");
249880
250098
  init_pg_ident();
249881
250099
  init_src();
249882
250100
  matchClause = (q2) => q2.requireMatch && q2.keywords.length > 0 ? " WHERE m > 0" : "";
@@ -250086,7 +250304,7 @@ var init_postgres_actor_memory = __esm({
250086
250304
  }
250087
250305
  async write(input, now) {
250088
250306
  if (input.memId === void 0) {
250089
- const memId = `mem:${(0, import_node_crypto69.randomUUID)()}`;
250307
+ const memId = `mem:${(0, import_node_crypto70.randomUUID)()}`;
250090
250308
  const r2 = await this.pool.query(
250091
250309
  `INSERT INTO ${this.s}.actor_memories
250092
250310
  (mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
@@ -251533,14 +251751,14 @@ function companySchemaName(companyId) {
251533
251751
  if (typeof companyId !== "string" || !companyId.trim()) {
251534
251752
  throw new Error("company schema mapping \u9700\u8981\u975E\u7A7A companyId\uFF08fail-closed\uFF1A\u4E0D\u56DE\u9000\u9ED8\u8BA4\u516C\u53F8\uFF09");
251535
251753
  }
251536
- const hex = (0, import_node_crypto70.createHash)("sha256").update(companyId, "utf8").digest("hex");
251754
+ const hex = (0, import_node_crypto71.createHash)("sha256").update(companyId, "utf8").digest("hex");
251537
251755
  return `${COMPANY_SCHEMA_PREFIX}${hex.slice(0, DIGEST_HEX_LEN)}`;
251538
251756
  }
251539
- var import_node_crypto70, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
251757
+ var import_node_crypto71, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
251540
251758
  var init_tenant_schema_mapping = __esm({
251541
251759
  "../storage/src/tenant-schema-mapping.ts"() {
251542
251760
  "use strict";
251543
- import_node_crypto70 = require("node:crypto");
251761
+ import_node_crypto71 = require("node:crypto");
251544
251762
  init_esm();
251545
251763
  init_pg_ident();
251546
251764
  COMPANY_SCHEMA_PREFIX = "c_";
@@ -251614,7 +251832,7 @@ var init_tenant_schema_mapping = __esm({
251614
251832
 
251615
251833
  // ../storage/src/company-data-plane-schema.ts
251616
251834
  function canonicalTypeDefHash(def) {
251617
- return `sha256:${(0, import_node_crypto71.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
251835
+ return `sha256:${(0, import_node_crypto72.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
251618
251836
  }
251619
251837
  function defaultArtifactTypeTemplate() {
251620
251838
  return [
@@ -251794,11 +252012,11 @@ async function seedDefaultArtifactTypes(pool, schema, templates = defaultArtifac
251794
252012
  }
251795
252013
  return { inserted };
251796
252014
  }
251797
- var import_node_crypto71, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
252015
+ var import_node_crypto72, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
251798
252016
  var init_company_data_plane_schema = __esm({
251799
252017
  "../storage/src/company-data-plane-schema.ts"() {
251800
252018
  "use strict";
251801
- import_node_crypto71 = require("node:crypto");
252019
+ import_node_crypto72 = require("node:crypto");
251802
252020
  init_esm();
251803
252021
  init_pg_ident();
251804
252022
  DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION = "4c368e5c-types-v1";
@@ -251877,11 +252095,11 @@ function rowToFile2(row) {
251877
252095
  updatedAt: new Date(row.updated_at).toISOString()
251878
252096
  };
251879
252097
  }
251880
- var import_node_crypto72, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
252098
+ var import_node_crypto73, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
251881
252099
  var init_company_owned_registry_store = __esm({
251882
252100
  "../storage/src/company-owned-registry-store.ts"() {
251883
252101
  "use strict";
251884
- import_node_crypto72 = require("node:crypto");
252102
+ import_node_crypto73 = require("node:crypto");
251885
252103
  init_esm();
251886
252104
  init_src();
251887
252105
  init_pg_ident();
@@ -252266,7 +252484,7 @@ var init_company_owned_registry_store = __esm({
252266
252484
  enabled: skill.enabled,
252267
252485
  files: files.map((f2) => ({ path: f2.path, blobHash: f2.blobHash }))
252268
252486
  });
252269
- return (0, import_node_crypto72.createHash)("sha256").update(body2).digest("hex");
252487
+ return (0, import_node_crypto73.createHash)("sha256").update(body2).digest("hex");
252270
252488
  }
252271
252489
  };
252272
252490
  }
@@ -254742,7 +254960,7 @@ function sourceSetConservation(input) {
254742
254960
  return input.sourceFinal === input.copied + input.approvedRuntimeConversion + input.approvedArchive + input.approvedDiscard;
254743
254961
  }
254744
254962
  function payloadChecksum(value2) {
254745
- return (0, import_node_crypto73.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
254963
+ return (0, import_node_crypto74.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
254746
254964
  }
254747
254965
  async function copyWorkorderDraftsIdempotent(pool, companyId, companySchema, manifestId, opts) {
254748
254966
  const cs = quoteIdent2(companySchema);
@@ -255130,11 +255348,11 @@ async function copyTraceFamilyIdempotent(pool, companyId, companySchema, manifes
255130
255348
  sequences
255131
255349
  };
255132
255350
  }
255133
- var import_node_crypto73, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
255351
+ var import_node_crypto74, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
255134
255352
  var init_tenant_migration_operator = __esm({
255135
255353
  "../storage/src/tenant-migration-operator.ts"() {
255136
255354
  "use strict";
255137
- import_node_crypto73 = require("node:crypto");
255355
+ import_node_crypto74 = require("node:crypto");
255138
255356
  init_esm();
255139
255357
  init_pg_ident();
255140
255358
  init_tenancy_attribution();
@@ -255366,8 +255584,8 @@ var init_tenant_new_company_provision = __esm({
255366
255584
  // ../storage/src/knowledge-snapshot-crypto.ts
255367
255585
  function sealSnapshot(snapshot, key) {
255368
255586
  if (snapshot.content.startsWith(PREFIX3)) return structuredClone(snapshot);
255369
- const iv = (0, import_node_crypto74.randomBytes)(12);
255370
- const cipher = (0, import_node_crypto74.createCipheriv)("aes-256-gcm", bytes(key), iv);
255587
+ const iv = (0, import_node_crypto75.randomBytes)(12);
255588
+ const cipher = (0, import_node_crypto75.createCipheriv)("aes-256-gcm", bytes(key), iv);
255371
255589
  cipher.setAAD(aad2(snapshot));
255372
255590
  const encrypted = Buffer.concat([cipher.update(snapshot.content, "utf8"), cipher.final()]);
255373
255591
  return { ...structuredClone(snapshot), content: `${PREFIX3}${iv.toString("base64url")}.${cipher.getAuthTag().toString("base64url")}.${encrypted.toString("base64url")}` };
@@ -255377,7 +255595,7 @@ function openSnapshot(snapshot, key) {
255377
255595
  const packed = snapshot.content.slice(PREFIX3.length).split(".");
255378
255596
  if (packed.length !== 3 || packed.some((part) => !part)) throw new Error(`knowledge snapshot ${snapshot.ref} ciphertext is malformed`);
255379
255597
  const [iv, tag, data] = packed;
255380
- const decipher = (0, import_node_crypto74.createDecipheriv)("aes-256-gcm", bytes(key), Buffer.from(iv, "base64url"));
255598
+ const decipher = (0, import_node_crypto75.createDecipheriv)("aes-256-gcm", bytes(key), Buffer.from(iv, "base64url"));
255381
255599
  decipher.setAAD(aad2(snapshot));
255382
255600
  decipher.setAuthTag(Buffer.from(tag, "base64url"));
255383
255601
  return {
@@ -255385,11 +255603,11 @@ function openSnapshot(snapshot, key) {
255385
255603
  content: Buffer.concat([decipher.update(Buffer.from(data, "base64url")), decipher.final()]).toString("utf8")
255386
255604
  };
255387
255605
  }
255388
- var import_node_crypto74, PREFIX3, bytes, aad2;
255606
+ var import_node_crypto75, PREFIX3, bytes, aad2;
255389
255607
  var init_knowledge_snapshot_crypto = __esm({
255390
255608
  "../storage/src/knowledge-snapshot-crypto.ts"() {
255391
255609
  "use strict";
255392
- import_node_crypto74 = require("node:crypto");
255610
+ import_node_crypto75 = require("node:crypto");
255393
255611
  PREFIX3 = "oasis-knowledge:v1:";
255394
255612
  bytes = (key) => {
255395
255613
  const value2 = Buffer.from(key);
@@ -257620,20 +257838,20 @@ var fs44 = __toESM(require("node:fs"));
257620
257838
  var os14 = __toESM(require("node:os"));
257621
257839
  var path40 = __toESM(require("node:path"));
257622
257840
  var import_node_child_process23 = require("node:child_process");
257623
- var import_node_crypto81 = require("node:crypto");
257841
+ var import_node_crypto82 = require("node:crypto");
257624
257842
 
257625
257843
  // ../cli/src/cli.ts
257626
257844
  var fs39 = __toESM(require("node:fs"), 1);
257627
257845
  var os12 = __toESM(require("node:os"), 1);
257628
257846
  var path35 = __toESM(require("node:path"), 1);
257629
- var import_node_crypto79 = require("node:crypto");
257847
+ var import_node_crypto80 = require("node:crypto");
257630
257848
 
257631
257849
  // ../cli/src/serve.ts
257632
257850
  var fs33 = __toESM(require("node:fs"), 1);
257633
257851
  var os8 = __toESM(require("node:os"), 1);
257634
257852
  var path29 = __toESM(require("node:path"), 1);
257635
257853
  var import_node_child_process18 = require("node:child_process");
257636
- var import_node_crypto75 = require("node:crypto");
257854
+ var import_node_crypto76 = require("node:crypto");
257637
257855
  var import_node_url7 = require("node:url");
257638
257856
 
257639
257857
  // ../cli/src/artifact-discovery-source.ts
@@ -258318,7 +258536,7 @@ init_src9();
258318
258536
  init_src();
258319
258537
  init_trajectory();
258320
258538
  function deterministicUuid2(seed) {
258321
- const h = (0, import_node_crypto75.createHash)("sha256").update(seed).digest();
258539
+ const h = (0, import_node_crypto76.createHash)("sha256").update(seed).digest();
258322
258540
  const b2 = Buffer.from(h.subarray(0, 16));
258323
258541
  b2[6] = b2[6] & 15 | 80;
258324
258542
  b2[8] = b2[8] & 63 | 128;
@@ -258518,7 +258736,7 @@ function oasisWrapperScript() {
258518
258736
  const repoRoot = (0, import_node_url7.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
258519
258737
  const tsx = path29.join(repoRoot, "node_modules/.bin/tsx");
258520
258738
  const main = path29.join(repoRoot, "packages/cli/src/main.ts");
258521
- const key = (0, import_node_crypto75.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
258739
+ const key = (0, import_node_crypto76.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
258522
258740
  const uid = typeof process.getuid === "function" ? process.getuid() : "na";
258523
258741
  const stable = path29.join(os8.tmpdir(), `oasis-cli-wrapper-${uid}-${key}`);
258524
258742
  let dir;
@@ -258899,7 +259117,7 @@ async function startServe(opts) {
258899
259117
  if (!hub) return Promise.resolve({ ok: false, reason: "\u672C\u8FDB\u7A0B\u6CA1\u6709\u8282\u70B9\u7F51\u5173" });
258900
259118
  const online = hub.connectedDaemons().some((d) => d.daemonId === nodeId);
258901
259119
  if (!online) return Promise.resolve({ ok: false, reason: "\u8282\u70B9\u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u7528\u7684\u662F\u5B83\u4E0A\u6B21\u62A5\u7684\u6E05\u5355" });
258902
- const requestId = (0, import_node_crypto75.randomUUID)();
259120
+ const requestId = (0, import_node_crypto76.randomUUID)();
258903
259121
  const task = new Promise((resolve10) => {
258904
259122
  const timer = setTimeout(() => {
258905
259123
  modelRefreshWaiters.delete(requestId);
@@ -259007,6 +259225,16 @@ async function startServe(opts) {
259007
259225
  // ADR 0125:写 scope=project 变量时校验 projectId 真实存在(getProject 直查,含临时项目)。
259008
259226
  // projectStateStore 在下方 ~行 1023 才赋值——本箭头只在请求时跑,那时已就绪(同 resolveDispatchScope 惰性模式)。
259009
259227
  projectExists: async (id) => await projectStateStore.getProject(id) !== null,
259228
+ /* 子 PRD《密钥管理》§1 的权限判据:密钥的写 / 删要认「创建者本人或组织管理者」。
259229
+ owner 与 admin 都算组织管理者(与组织页 `orgIdentityOf` 同一口径)。
259230
+ `controlPlaneStore` 在下方才赋值——本箭头只在请求时跑,那时已就绪(同 projectExists 的惰性模式)。
259231
+ 成员表按 accountId 主键,但行上带 actorId,密钥那边手里只有 actorId,所以按 actorId 找。 */
259232
+ isCompanyManager: async (companyId, actorId) => {
259233
+ if (!companyId || !actorId) return false;
259234
+ const members = await controlPlaneStore.listMembers(companyId).catch(() => []);
259235
+ const me = members.find((m2) => m2.actorId === actorId);
259236
+ return me?.role === "owner" || me?.role === "admin";
259237
+ },
259010
259238
  // CO-302 数据面隔离(actors 域试点):按当前公司取其引擎,用各自 registry 服务该域请求。
259011
259239
  // engineRouter 在下方(行 ~411)以 const 声明;此箭头只在请求时调用,那时已初始化,闭包引用合法
259012
259240
  // (TDZ 只在构造时访问才报错,这里不访问)。默认公司命中现有单实例(registry===registryStore),
@@ -259151,6 +259379,20 @@ async function startServe(opts) {
259151
259379
  const defaultBranchResolver = makeDefaultBranchResolver({ log: (m2) => console.warn(m2) });
259152
259380
  const perCompanyActorNames = /* @__PURE__ */ new Map();
259153
259381
  const projectsDomain2 = createProjectsDomain({
259382
+ listChatFiles: async (companyId, projectId2) => {
259383
+ const chat = await chatStoreFor(companyId);
259384
+ const facts = await blobStoreFor(companyId);
259385
+ return collectProjectChatFiles(chat, projectId2, async (ref2) => {
259386
+ for (const cabinet of [assets, facts]) {
259387
+ try {
259388
+ const meta = await cabinet.meta?.(ref2);
259389
+ if (meta) return meta;
259390
+ } catch {
259391
+ }
259392
+ }
259393
+ return null;
259394
+ });
259395
+ },
259154
259396
  projects: projectStateStore,
259155
259397
  artifacts: artifactStateStore,
259156
259398
  kernel: defaultCompanyKernel,
@@ -259597,7 +259839,7 @@ async function startServe(opts) {
259597
259839
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
259598
259840
  if (buf || session.runId) {
259599
259841
  await turnStore.appendMessage({
259600
- id: (0, import_node_crypto75.randomUUID)(),
259842
+ id: (0, import_node_crypto76.randomUUID)(),
259601
259843
  sessionId: chatSessionId,
259602
259844
  role: "assistant",
259603
259845
  content: buf,
@@ -259643,7 +259885,7 @@ async function startServe(opts) {
259643
259885
  chatTargets: {
259644
259886
  inspect: inspectChatTarget,
259645
259887
  createTargetSession: async ({ companyId, agentId, ownerHumanActorId, title }) => {
259646
- const sessionId = (0, import_node_crypto75.randomUUID)();
259888
+ const sessionId = (0, import_node_crypto76.randomUUID)();
259647
259889
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
259648
259890
  const binding = await registryStore.getBinding(agentId).catch(() => null);
259649
259891
  await (await chatStoreFor(companyId)).createSession({
@@ -260646,7 +260888,7 @@ async function startServe(opts) {
260646
260888
  }));
260647
260889
  }
260648
260890
  const limits = { wallClockMs: wallClockForKind("planner") };
260649
- const artifactId = `artifact:planner:${(0, import_node_crypto75.randomUUID)()}`;
260891
+ const artifactId = `artifact:planner:${(0, import_node_crypto76.randomUUID)()}`;
260650
260892
  const handle = await chatRemoteAdapter.spawn({
260651
260893
  actor: planner.id,
260652
260894
  // companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
@@ -261498,10 +261740,10 @@ async function startServe(opts) {
261498
261740
  if (isDeletedActor(dispatchTarget)) {
261499
261741
  throw new ApiError(409, ACTOR_DELETED_ERROR_CODE, ACTOR_DELETED_CHAT_MESSAGE);
261500
261742
  }
261501
- const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid2(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto75.randomUUID)());
261743
+ const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid2(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto76.randomUUID)());
261502
261744
  const resumeRuntimeSession = Boolean(sessionId);
261503
- const traceRunId = requestDispatchId ? `chat-run:${deterministicUuid2(`chat-trace:${requestDispatchId}`)}` : `chat-run:${(0, import_node_crypto75.randomUUID)()}`;
261504
- const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid2(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto75.randomUUID)()}`;
261745
+ const traceRunId = requestDispatchId ? `chat-run:${deterministicUuid2(`chat-trace:${requestDispatchId}`)}` : `chat-run:${(0, import_node_crypto76.randomUUID)()}`;
261746
+ const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid2(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto76.randomUUID)()}`;
261505
261747
  let stableDispatch = requestDispatchId ? await resolveStableChatDispatch({ store: await traceStoreFor(runCompanyId), runId: traceRunId }) : null;
261506
261748
  if (stableDispatch && !stableDispatch.shouldDispatch) {
261507
261749
  return {
@@ -262623,7 +262865,7 @@ async function startServe(opts) {
262623
262865
  console.log(`[new-engine] dispatchWork ${workId} \u662F human work\u2014\u2014\u4E0D\u6D3E runtime\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406`);
262624
262866
  return;
262625
262867
  }
262626
- const ledgerId = `dispatch:${(0, import_node_crypto75.randomUUID)()}`;
262868
+ const ledgerId = `dispatch:${(0, import_node_crypto76.randomUUID)()}`;
262627
262869
  try {
262628
262870
  await (await dispatchLedgerFor(companyId)).insertOpen({
262629
262871
  id: ledgerId,
@@ -262708,7 +262950,7 @@ async function startServe(opts) {
262708
262950
  const kernelModel = newKernel.model;
262709
262951
  const art = kernelModel.artifacts.get(nodeId);
262710
262952
  const revWid = art?.workspace ?? "";
262711
- const ledgerId = `dispatch:${(0, import_node_crypto75.randomUUID)()}`;
262953
+ const ledgerId = `dispatch:${(0, import_node_crypto76.randomUUID)()}`;
262712
262954
  try {
262713
262955
  await (await dispatchLedgerFor(companyId)).insertOpen({
262714
262956
  id: ledgerId,
@@ -263086,7 +263328,7 @@ async function startServe(opts) {
263086
263328
  resolveSupplementalContext: server.knowledgeGovernance ? async ({ actorId, artifactId, workOrderId, action, part, runtimeKind, taskMarkdown }) => {
263087
263329
  try {
263088
263330
  const governance = server.knowledgeGovernance;
263089
- const taskFingerprint = (0, import_node_crypto75.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
263331
+ const taskFingerprint = (0, import_node_crypto76.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
263090
263332
  const requestContext = {
263091
263333
  organizationId: companyId,
263092
263334
  actorId,
@@ -264976,7 +265218,7 @@ var SessionProcessState = class {
264976
265218
  // ../cli/src/daemon/ws-client.ts
264977
265219
  init_wrapper();
264978
265220
  var import_node_os9 = require("node:os");
264979
- var import_node_crypto76 = require("node:crypto");
265221
+ var import_node_crypto77 = require("node:crypto");
264980
265222
  init_src8();
264981
265223
  init_src6();
264982
265224
 
@@ -265699,7 +265941,7 @@ var DaemonWsClient = class {
265699
265941
  */
265700
265942
  async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
265701
265943
  if (artifactIds.length === 0) return {};
265702
- const requestId = (0, import_node_crypto76.randomUUID)();
265944
+ const requestId = (0, import_node_crypto77.randomUUID)();
265703
265945
  return new Promise((resolve10, reject) => {
265704
265946
  const timer = setTimeout(() => {
265705
265947
  this.gcPending.delete(requestId);
@@ -266099,7 +266341,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
266099
266341
  // ../cli/src/daemon/machine-id.ts
266100
266342
  var import_node_child_process21 = require("node:child_process");
266101
266343
  var import_node_fs21 = require("node:fs");
266102
- var import_node_crypto77 = require("node:crypto");
266344
+ var import_node_crypto78 = require("node:crypto");
266103
266345
  var import_node_os12 = require("node:os");
266104
266346
  function linuxMachineId() {
266105
266347
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -266165,7 +266407,7 @@ var defaultSources = {
266165
266407
  function resolveNodeId(sources = {}) {
266166
266408
  const s2 = { ...defaultSources, ...sources };
266167
266409
  const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
266168
- const digest = (0, import_node_crypto77.createHash)("sha256").update(material).digest("hex").slice(0, 12);
266410
+ const digest = (0, import_node_crypto78.createHash)("sha256").update(material).digest("hex").slice(0, 12);
266169
266411
  return `node-${digest}`;
266170
266412
  }
266171
266413
 
@@ -267936,13 +268178,13 @@ function ownFlagsFromDecl(command) {
267936
268178
  }
267937
268179
 
267938
268180
  // ../cli/src/connector-effect-runner.ts
267939
- var import_node_crypto78 = require("node:crypto");
268181
+ var import_node_crypto79 = require("node:crypto");
267940
268182
  var import_node_child_process22 = require("node:child_process");
267941
268183
  init_src8();
267942
268184
  function stableKey(effect) {
267943
268185
  const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
267944
268186
  const canonical = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
267945
- return `sha256:${(0, import_node_crypto78.createHash)("sha256").update(canonical).digest("hex")}`;
268187
+ return `sha256:${(0, import_node_crypto79.createHash)("sha256").update(canonical).digest("hex")}`;
267946
268188
  }
267947
268189
  async function runConnectorEffect(connector, toolArgs, deps) {
267948
268190
  const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
@@ -268152,7 +268394,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
268152
268394
  const timeoutMs = resolveAuthWaitMs();
268153
268395
  const controller = new AbortController();
268154
268396
  const timer = setTimeout(() => controller.abort(), timeoutMs);
268155
- const waiterId = (0, import_node_crypto79.randomUUID)();
268397
+ const waiterId = (0, import_node_crypto80.randomUUID)();
268156
268398
  process.stderr.write(
268157
268399
  `\u7B49\u5F85\u4EBA\u5DE5\u6388\u6743\u4E2D\u2026\u2026\u6700\u591A ${Math.round(timeoutMs / 1e3)} \u79D2\uFF0C\u8D85\u65F6\u4F1A\u81EA\u52A8\u8F6C\u4E3A\u5F85\u5BA1\uFF1B\u8FD9\u4E0D\u662F\u9519\u8BEF\uFF0C\u8BF7\u52FF\u91CD\u8BD5\u540C\u4E00\u6761\u547D\u4EE4\u3002
268158
268400
  `
@@ -268574,7 +268816,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
268574
268816
  const store = createNodeTokenStore(path35.join(dir, "node-tokens.json"));
268575
268817
  const sub = positional[0];
268576
268818
  if (sub === "issue") {
268577
- const id = flags.get("id") ?? `node-${(0, import_node_crypto79.randomUUID)()}`;
268819
+ const id = flags.get("id") ?? `node-${(0, import_node_crypto80.randomUUID)()}`;
268578
268820
  const token2 = store.issue(id);
268579
268821
  println(token2);
268580
268822
  process.stderr.write(
@@ -271587,13 +271829,13 @@ var LEGACY_SYSTEMD_UNIT = "oasis-node.service";
271587
271829
  var LEGACY_LAUNCHD_LABEL = "com.oasis.node";
271588
271830
 
271589
271831
  // src/install.ts
271590
- var import_node_crypto80 = require("node:crypto");
271832
+ var import_node_crypto81 = require("node:crypto");
271591
271833
  var fs43 = __toESM(require("node:fs"));
271592
271834
  var path39 = __toESM(require("node:path"));
271593
271835
  function versionLabel(sourceFile, pkgVersion, npmPrefixDir, pkgName = LEGACY_PKG_NAME) {
271594
271836
  const tag = pkgName === LEGACY_PKG_NAME ? "" : `+pkg.${pkgName.replace(/[^A-Za-z0-9-]/g, "-")}`;
271595
271837
  if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return `${pkgVersion}${tag}`;
271596
- const digest = (0, import_node_crypto80.createHash)("sha256").update(fs43.readFileSync(sourceFile)).digest("hex").slice(0, 8);
271838
+ const digest = (0, import_node_crypto81.createHash)("sha256").update(fs43.readFileSync(sourceFile)).digest("hex").slice(0, 8);
271597
271839
  return tag ? `${pkgVersion}${tag}.local.${digest}` : `${pkgVersion}+local.${digest}`;
271598
271840
  }
271599
271841
  function isFromNpmPrefix(sourceFile, npmPrefixDir) {
@@ -271716,7 +271958,7 @@ function shimScript() {
271716
271958
  }
271717
271959
 
271718
271960
  // src/index.ts
271719
- var PKG_VERSION = true ? "2.2.15" : "dev";
271961
+ var PKG_VERSION = true ? "2.2.17" : "dev";
271720
271962
  var LOCAL_BIN = localBin();
271721
271963
  var NPM_PREFIX = npmPrefix();
271722
271964
  var INSTANCE = DEFAULT_INSTANCE;
@@ -272001,7 +272243,7 @@ function newInstanceName() {
272001
272243
  const existing = new Set(listInstances());
272002
272244
  if (!existing.has(DEFAULT_INSTANCE)) return DEFAULT_INSTANCE;
272003
272245
  for (; ; ) {
272004
- const n = `inst-${(0, import_node_crypto81.randomBytes)(3).toString("hex")}`;
272246
+ const n = `inst-${(0, import_node_crypto82.randomBytes)(3).toString("hex")}`;
272005
272247
  if (!existing.has(n)) return n;
272006
272248
  }
272007
272249
  }