oasis_test_v2 2.2.33 → 2.2.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1057 -505
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -370,10 +370,11 @@ function commentThreadSessionId(a, b2) {
370
370
  function reviewThreadSessionId(a, b2) {
371
371
  return `${REVIEW_THREAD_SESSION_PREFIX}${[a, b2].sort().join("|")}`;
372
372
  }
373
- var GAP_THREAD_SESSION_PREFIX, COMMENT_THREAD_SESSION_PREFIX, REVIEW_THREAD_SESSION_PREFIX;
373
+ var WORKORDER_HOLD_AT_CREATE_REASON, GAP_THREAD_SESSION_PREFIX, COMMENT_THREAD_SESSION_PREFIX, REVIEW_THREAD_SESSION_PREFIX;
374
374
  var init_collab_view = __esm({
375
375
  "../contract/src/collab-view.ts"() {
376
376
  "use strict";
377
+ WORKORDER_HOLD_AT_CREATE_REASON = "\u5EFA\u5355\u65F6\u9009\u62E9\u7A0D\u540E\u6D3E\u53D1";
377
378
  GAP_THREAD_SESSION_PREFIX = "gapline:";
378
379
  COMMENT_THREAD_SESSION_PREFIX = "commentline:";
379
380
  REVIEW_THREAD_SESSION_PREFIX = "reviewline:";
@@ -406,9 +407,22 @@ var init_inbox_read = __esm({
406
407
  });
407
408
 
408
409
  // ../contract/src/notifications.ts
410
+ var LEDGER_INBOX_KINDS;
409
411
  var init_notifications = __esm({
410
412
  "../contract/src/notifications.ts"() {
411
413
  "use strict";
414
+ LEDGER_INBOX_KINDS = /* @__PURE__ */ new Set([
415
+ "review-request",
416
+ "rework",
417
+ "stale",
418
+ "pushback",
419
+ "assigned",
420
+ "needs-staffing",
421
+ "gap-escalation",
422
+ "change-confirm",
423
+ "review-waiting",
424
+ "work-waiting"
425
+ ]);
412
426
  }
413
427
  });
414
428
 
@@ -1647,7 +1661,7 @@ var init_file_preview = __esm({
1647
1661
  "../contract/src/file-preview.ts"() {
1648
1662
  "use strict";
1649
1663
  OFFICE_PREVIEW_MAX_BYTES = 20 * 1024 * 1024;
1650
- FILE_CONTENT_MAX_BYTES = 50 * 1024 * 1024;
1664
+ FILE_CONTENT_MAX_BYTES = 200 * 1024 * 1024;
1651
1665
  }
1652
1666
  });
1653
1667
 
@@ -21293,14 +21307,17 @@ var init_kernel_bridge = __esm({
21293
21307
  });
21294
21308
  }
21295
21309
  }
21296
- /** 工单级暂停:workorder.paused handler 杀全部在途会话 + 挡新派发。 */
21297
- async pauseWorkorder(workorderId, actor) {
21310
+ /**
21311
+ * 工单级暂停:workorder.paused handler 杀全部在途会话 + 挡新派发。
21312
+ * `reason` 缺省「暂停派发」(人点暂停);建单即挂起的入口传 `WORKORDER_HOLD_AT_CREATE_REASON`。
21313
+ */
21314
+ async pauseWorkorder(workorderId, actor, reason = "\u6682\u505C\u6D3E\u53D1") {
21298
21315
  this.trackedWorkorders.add(workorderId);
21299
21316
  await this.commit({
21300
21317
  companyId: "",
21301
21318
  workorderId,
21302
21319
  actorId: actor,
21303
- event: { kind: "workorder.paused", workorderId, by: actor, reason: "\u6682\u505C\u6D3E\u53D1" }
21320
+ event: { kind: "workorder.paused", workorderId, by: actor, reason }
21304
21321
  });
21305
21322
  }
21306
21323
  /**
@@ -22033,12 +22050,12 @@ async function readArtifactFile(kernel, blobs, artifactId, path41, expectedRef,
22033
22050
  if (!entry) throw new ApiError(404, "NOT_FOUND", "\u5305\u5185\u6587\u4EF6\u4E0D\u5B58\u5728");
22034
22051
  hash2 = entry.hash;
22035
22052
  name = entry.path;
22036
- if (entry.size !== void 0 && entry.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
22053
+ if (entry.size !== void 0 && entry.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", `\u6587\u4EF6\u8D85\u8FC7 ${FILE_CONTENT_MAX_BYTES / 1024 / 1024} MiB \u8BFB\u53D6\u9650\u5236`);
22037
22054
  } else if (output.contentKind !== "inline-blob") {
22038
22055
  throw new ApiError(404, "NOT_FOUND", "\u6B64\u4EA7\u7269\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u539F\u6587\u4EF6");
22039
22056
  }
22040
22057
  const meta = await blobs.meta?.(hash2);
22041
- if (meta && meta.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
22058
+ if (meta && meta.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", `\u6587\u4EF6\u8D85\u8FC7 ${FILE_CONTENT_MAX_BYTES / 1024 / 1024} MiB \u8BFB\u53D6\u9650\u5236`);
22042
22059
  try {
22043
22060
  return { bytes: await blobs.get(hash2), name, contentType: meta?.contentType };
22044
22061
  } catch {
@@ -22065,7 +22082,7 @@ async function serveFileRequest(req, res, url, engine) {
22065
22082
  url.searchParams.get("ref"),
22066
22083
  url.searchParams.get("revisionId")
22067
22084
  );
22068
- if (bytes2.length > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
22085
+ if (bytes2.length > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", `\u6587\u4EF6\u8D85\u8FC7 ${FILE_CONTENT_MAX_BYTES / 1024 / 1024} MiB \u8BFB\u53D6\u9650\u5236`);
22069
22086
  res.writeHead(200, {
22070
22087
  "content-type": contentType ?? sniffContentType(bytes2) ?? "application/octet-stream",
22071
22088
  "content-length": String(bytes2.length),
@@ -25413,7 +25430,7 @@ async function runProtocolSession(job, cfg) {
25413
25430
  let appendChain = Promise.resolve();
25414
25431
  let eventSeq = 0;
25415
25432
  let runtimeSessionId;
25416
- let turnCompletePending = false;
25433
+ const pendingTurnMessages = [];
25417
25434
  const stderrTail = [];
25418
25435
  const finish = (info) => {
25419
25436
  if (exited) return;
@@ -25452,7 +25469,15 @@ async function runProtocolSession(job, cfg) {
25452
25469
  }
25453
25470
  });
25454
25471
  };
25472
+ const completeTurn = (message) => {
25473
+ if (exited || killReason) return;
25474
+ const result = cfg.turnResult(message, state);
25475
+ if (!result) return;
25476
+ finish(result);
25477
+ child.kill("SIGTERM");
25478
+ };
25455
25479
  const handle = (message) => {
25480
+ if (exited || killReason) return;
25456
25481
  const responseId = commandId(message.id);
25457
25482
  if (isResponse(message) && responseId !== void 0 && pending.has(responseId)) {
25458
25483
  const waiter = pending.get(responseId);
@@ -25468,13 +25493,15 @@ async function runProtocolSession(job, cfg) {
25468
25493
  const event = { kind: line.kind, payload: line.payload, seq: ++eventSeq, ts: (/* @__PURE__ */ new Date()).toISOString() };
25469
25494
  for (const cb of telemetryCbs) cb(event);
25470
25495
  }
25471
- if (cfg.isTurnComplete(message, state)) {
25472
- if (!started) {
25473
- turnCompletePending = true;
25474
- } else {
25475
- finish({ code: 0, reason: "clean", transcriptRef, ...runtimeSessionId ? { runtimeSessionId } : {} });
25476
- child.kill("SIGTERM");
25477
- }
25496
+ if (!started) pendingTurnMessages.push(message);
25497
+ else completeTurn(message);
25498
+ };
25499
+ const consumeLine = (line) => {
25500
+ if (!line) return;
25501
+ transcript.write(line + "\n");
25502
+ try {
25503
+ handle(JSON.parse(line));
25504
+ } catch {
25478
25505
  }
25479
25506
  };
25480
25507
  let buffer = "";
@@ -25484,13 +25511,7 @@ async function runProtocolSession(job, cfg) {
25484
25511
  while (newline >= 0) {
25485
25512
  const line = buffer.slice(0, newline).replace(/\r$/, "");
25486
25513
  buffer = buffer.slice(newline + 1);
25487
- if (line) {
25488
- transcript.write(line + "\n");
25489
- try {
25490
- handle(JSON.parse(line));
25491
- } catch {
25492
- }
25493
- }
25514
+ consumeLine(line);
25494
25515
  newline = buffer.indexOf("\n");
25495
25516
  }
25496
25517
  });
@@ -25504,12 +25525,23 @@ async function runProtocolSession(job, cfg) {
25504
25525
  killReason ??= "timeout";
25505
25526
  child.kill("SIGKILL");
25506
25527
  }, job.limits.wallClockMs);
25507
- child.on("error", (error2) => finish({ code: null, reason: "error", transcriptRef, errorMessage: error2.message }));
25508
- child.on("exit", (code2) => {
25528
+ child.on("error", (error2) => {
25509
25529
  clearTimeout(timer);
25530
+ finish({ code: null, reason: "error", transcriptRef, errorMessage: error2.message });
25531
+ });
25532
+ child.on("close", (code2) => {
25533
+ clearTimeout(timer);
25534
+ consumeLine(buffer.trim());
25535
+ buffer = "";
25536
+ transcript.end();
25510
25537
  if (!exited) {
25511
25538
  const reason = killReason ?? (code2 === 0 ? "clean" : "error");
25512
- finish({ code: killReason ? null : code2, reason, transcriptRef, ...reason === "error" && stderrTail.length ? { errorMessage: stderrTail.join("\n").slice(-4e3) } : {} });
25539
+ const info = {
25540
+ code: killReason ? null : code2,
25541
+ reason,
25542
+ ...reason === "error" && stderrTail.length ? { errorMessage: stderrTail.join("\n").slice(-4e3) } : {}
25543
+ };
25544
+ finish(!killReason && cfg.processExitInfo ? cfg.processExitInfo(info, state) : info);
25513
25545
  }
25514
25546
  });
25515
25547
  void (async () => {
@@ -25523,10 +25555,7 @@ async function runProtocolSession(job, cfg) {
25523
25555
  await cfg.start(send, job, dir, task, state);
25524
25556
  runtimeSessionId ??= cfg.runtimeSessionId?.(state);
25525
25557
  started = true;
25526
- if (turnCompletePending && !exited) {
25527
- finish({ code: 0, reason: "clean", transcriptRef, ...runtimeSessionId ? { runtimeSessionId } : {} });
25528
- child.kill("SIGTERM");
25529
- }
25558
+ for (const message of pendingTurnMessages.splice(0)) completeTurn(message);
25530
25559
  } catch (error2) {
25531
25560
  if (!exited) {
25532
25561
  const msg = error2 instanceof Error ? error2.message : String(error2);
@@ -25735,7 +25764,7 @@ function codexSessionsRoot() {
25735
25764
  }
25736
25765
  function listRecentRollouts(root, sinceMs) {
25737
25766
  const margin = sinceMs - 6e4;
25738
- const found = [];
25767
+ const found2 = [];
25739
25768
  const walk = (d) => {
25740
25769
  let ents;
25741
25770
  try {
@@ -25752,16 +25781,16 @@ function listRecentRollouts(root, sinceMs) {
25752
25781
  m2 = fs8.statSync(p2).mtimeMs;
25753
25782
  } catch {
25754
25783
  }
25755
- if (m2 >= margin) found.push({ f: p2, m: m2 });
25784
+ if (m2 >= margin) found2.push({ f: p2, m: m2 });
25756
25785
  }
25757
25786
  }
25758
25787
  };
25759
25788
  walk(root);
25760
- return found.sort((a, b2) => b2.m - a.m).map((x2) => x2.f);
25789
+ return found2.sort((a, b2) => b2.m - a.m).map((x2) => x2.f);
25761
25790
  }
25762
25791
  function findRolloutsBySessionId(root, sessionId) {
25763
25792
  const suffix = `-${sessionId}.jsonl`;
25764
- const found = [];
25793
+ const found2 = [];
25765
25794
  const walk = (d) => {
25766
25795
  let ents;
25767
25796
  try {
@@ -25772,11 +25801,11 @@ function findRolloutsBySessionId(root, sessionId) {
25772
25801
  for (const ent of ents) {
25773
25802
  const p2 = path10.join(d, ent.name);
25774
25803
  if (ent.isDirectory()) walk(p2);
25775
- else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(suffix)) found.push(p2);
25804
+ else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(suffix)) found2.push(p2);
25776
25805
  }
25777
25806
  };
25778
25807
  walk(root);
25779
- return found;
25808
+ return found2;
25780
25809
  }
25781
25810
  function telemetryFromRolloutLines(lines, sinceMs) {
25782
25811
  const usage = parseCodexTokenUsage(lines, sinceMs);
@@ -26036,6 +26065,15 @@ function normalizeCodexConsoleLine(line, state = createCodexNormalizeState()) {
26036
26065
  tool.output.push(line);
26037
26066
  return [];
26038
26067
  }
26068
+ function codexTurnErrorMessage(value2) {
26069
+ const error2 = record2(value2);
26070
+ const code2 = error2?.codexErrorInfo;
26071
+ const label = typeof code2 === "string" ? code2 : code2 ? JSON.stringify(code2) : "unknown";
26072
+ const message = typeof error2?.message === "string" && error2.message.trim() ? error2.message.trim() : "Codex turn failed without error details";
26073
+ const summary = code2 === "cyberPolicy" || code2 === "misalignmentPolicyViolation" ? `Codex \u5B89\u5168\u7B56\u7565\u62D2\u7EDD [${label}]\uFF1A${message}` : `Codex [${label}]: ${message}`;
26074
+ const detail = typeof error2?.additionalDetails === "string" ? error2.additionalDetails.trim() : "";
26075
+ return [summary, detail].filter(Boolean).join("\n").slice(0, 4e3);
26076
+ }
26039
26077
  var import_node_child_process7, import_node_crypto11, fs8, os5, path10, readline2, CODEX_BLOCKED_FLAGS, CODEX_ARGV_PROMPT_MAX_BYTES, CODEX_APP_SERVER_BLOCKED_FLAGS, CodexAdapter;
26040
26078
  var init_codex = __esm({
26041
26079
  "../adapters/src/codex/index.ts"() {
@@ -26119,6 +26157,7 @@ var init_codex = __esm({
26119
26157
  threadId: void 0,
26120
26158
  turnId: void 0,
26121
26159
  active: false,
26160
+ errorsByTurn: /* @__PURE__ */ new Map(),
26122
26161
  pendingInputs: /* @__PURE__ */ new Set(),
26123
26162
  codex: createCodexNormalizeState(),
26124
26163
  // 本次 run 的用量:`thread/tokenUsage/updated` 的 `last`(见该解析函数注释)按轮次记下,
@@ -26202,6 +26241,9 @@ ${task}` : task;
26202
26241
  }
26203
26242
  }
26204
26243
  if (method === "turn/completed") {
26244
+ const turn = record2(params?.turn);
26245
+ const failure = typeof turn?.id === "string" ? state.errorsByTurn.get(turn.id) : void 0;
26246
+ if (params?.threadId !== state.threadId || turn?.id !== state.turnId || turn?.status !== "completed" || turn.error || failure && !failure.willRetry) return [];
26205
26247
  return [{ ...base, itemId: "turn-end", itemType: "control", operation: "completed", payload: { code: "output_block_completed" } }];
26206
26248
  }
26207
26249
  return [];
@@ -26209,10 +26251,15 @@ ${task}` : task;
26209
26251
  normalize(message, state) {
26210
26252
  const params = message.params;
26211
26253
  const method = typeof message.method === "string" ? message.method : "";
26254
+ if (method === "error" && params?.threadId === state.threadId && typeof params?.turnId === "string") {
26255
+ state.errorsByTurn.set(params.turnId, { error: params.error, willRetry: params.willRetry === true });
26256
+ }
26212
26257
  if (method === "thread/started") {
26213
26258
  const thread = params?.thread;
26214
- if (typeof thread?.id === "string") state.threadId = thread.id;
26215
- if (typeof thread?.path === "string") state.rolloutPath = thread.path;
26259
+ if (typeof thread?.id === "string" && (!state.threadId || state.threadId === thread.id)) {
26260
+ state.threadId = thread.id;
26261
+ if (typeof thread.path === "string") state.rolloutPath = thread.path;
26262
+ }
26216
26263
  }
26217
26264
  if (method === "thread/tokenUsage/updated") {
26218
26265
  const turn = parseCodexAppServerTurnUsage(params);
@@ -26220,7 +26267,7 @@ ${task}` : task;
26220
26267
  }
26221
26268
  if (method === "turn/started") {
26222
26269
  const turn = params?.turn;
26223
- if (typeof turn?.id === "string") {
26270
+ if (params?.threadId === state.threadId && state.turnRequested && typeof turn?.id === "string" && (!state.turnId || state.turnId === turn.id)) {
26224
26271
  state.turnId = turn.id;
26225
26272
  state.active = true;
26226
26273
  if (state.turnRequested) state.runTurnIds.add(turn.id);
@@ -26241,10 +26288,29 @@ ${task}` : task;
26241
26288
  }
26242
26289
  return [];
26243
26290
  },
26244
- isTurnComplete(message, state) {
26245
- if (message.method !== "turn/completed" || !state.active) return false;
26291
+ turnResult(message, state) {
26292
+ if (message.method !== "turn/completed" || !state.active) return void 0;
26293
+ const params = record2(message.params);
26294
+ const turn = record2(params?.turn);
26295
+ if (params?.threadId !== state.threadId || !state.turnId || turn?.id !== state.turnId) return void 0;
26246
26296
  state.active = false;
26247
- return true;
26297
+ if (turn.status === "interrupted") return { code: null, reason: "cancelled" };
26298
+ const failure = state.errorsByTurn.get(state.turnId);
26299
+ const error2 = turn.error ?? (turn.status === "failed" || !failure?.willRetry ? failure?.error : void 0);
26300
+ if (turn.status === "completed" && !error2) return { code: 0, reason: "clean" };
26301
+ return { code: 1, reason: "error", errorMessage: codexTurnErrorMessage(error2) };
26302
+ },
26303
+ processExitInfo(info, state) {
26304
+ const error2 = state.turnId ? state.errorsByTurn.get(state.turnId)?.error : void 0;
26305
+ return {
26306
+ code: info.code === 0 ? 1 : info.code,
26307
+ reason: "error",
26308
+ errorMessage: [
26309
+ error2 ? codexTurnErrorMessage(error2) : void 0,
26310
+ info.errorMessage,
26311
+ "Codex app-server exited before reporting a terminal result"
26312
+ ].filter(Boolean).join("\n").slice(0, 4e3)
26313
+ };
26248
26314
  },
26249
26315
  runtimeSessionId: (state) => state.threadId,
26250
26316
  exitExtras: (state) => {
@@ -28304,8 +28370,8 @@ ${task}` : task;
28304
28370
  normalize(message) {
28305
28371
  return normalizePi(JSON.stringify(message));
28306
28372
  },
28307
- isTurnComplete(message) {
28308
- return message.type === "agent_settled";
28373
+ turnResult(message) {
28374
+ return message.type === "agent_settled" ? { code: 0, reason: "clean" } : void 0;
28309
28375
  },
28310
28376
  runtimeSessionId(state) {
28311
28377
  return state.sessionId;
@@ -30815,8 +30881,26 @@ function runBackgroundAssistantChatTurn(deps) {
30815
30881
  }
30816
30882
  };
30817
30883
  void Promise.resolve(deps.session.done).then(() => finalize("done").catch(() => void 0)).catch((err) => {
30884
+ const chatExit = err instanceof Error ? err.chatExit : void 0;
30885
+ const text5 = chatExit?.summary ?? (err instanceof Error ? err.message : String(err));
30886
+ const extra = {
30887
+ ...chatExit?.reason ? { reason: chatExit.reason } : {},
30888
+ ...chatExit?.detail ? { detail: chatExit.detail } : {}
30889
+ };
30890
+ const part = { type: "error", seq: ++localSeq, text: text5, ...extra };
30891
+ localParts.push(part);
30892
+ liveTurn?.emit(part);
30818
30893
  try {
30819
- itemLedger.recordError(err instanceof Error ? err.message : String(err));
30894
+ const outcome = itemLedger.recordError(text5, extra);
30895
+ liveTurn?.publishServerItem({
30896
+ itemId: outcome.itemId,
30897
+ itemType: "control",
30898
+ startedVersion: outcome.startedVersion,
30899
+ version: outcome.version,
30900
+ ord: outcome.ord,
30901
+ status: "failed",
30902
+ payload: { code: "error", note: text5, ...extra }
30903
+ });
30820
30904
  } catch {
30821
30905
  }
30822
30906
  return finalize("error").catch(() => void 0);
@@ -178838,14 +178922,14 @@ var require_Alias = __commonJS({
178838
178922
  if (ctx)
178839
178923
  ctx.aliasResolveCache = nodes;
178840
178924
  }
178841
- let found = void 0;
178925
+ let found2 = void 0;
178842
178926
  for (const node2 of nodes) {
178843
178927
  if (node2 === this)
178844
178928
  break;
178845
178929
  if (node2.anchor === this.source)
178846
- found = node2;
178930
+ found2 = node2;
178847
178931
  }
178848
- return found;
178932
+ return found2;
178849
178933
  }
178850
178934
  toJSON(_arg, ctx) {
178851
178935
  if (!ctx)
@@ -181970,7 +182054,7 @@ var require_resolve_props = __commonJS({
181970
182054
  let tag = null;
181971
182055
  let newlineAfterProp = null;
181972
182056
  let comma = null;
181973
- let found = null;
182057
+ let found2 = null;
181974
182058
  let start = null;
181975
182059
  for (const token of tokens) {
181976
182060
  if (reqSpace) {
@@ -182007,7 +182091,7 @@ var require_resolve_props = __commonJS({
182007
182091
  if (atNewline) {
182008
182092
  if (comment)
182009
182093
  comment += token.source;
182010
- else if (!found || indicator !== "seq-item-ind")
182094
+ else if (!found2 || indicator !== "seq-item-ind")
182011
182095
  spaceBefore = true;
182012
182096
  } else
182013
182097
  commentSep += token.source;
@@ -182041,9 +182125,9 @@ var require_resolve_props = __commonJS({
182041
182125
  case indicator:
182042
182126
  if (anchor || tag)
182043
182127
  onError(token, "BAD_PROP_ORDER", `Anchors and tags must be after the ${token.source} indicator`);
182044
- if (found)
182128
+ if (found2)
182045
182129
  onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.source} in ${flow3 ?? "collection"}`);
182046
- found = token;
182130
+ found2 = token;
182047
182131
  atNewline = indicator === "seq-item-ind" || indicator === "explicit-key-ind";
182048
182132
  hasSpace = false;
182049
182133
  break;
@@ -182072,7 +182156,7 @@ var require_resolve_props = __commonJS({
182072
182156
  onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation");
182073
182157
  return {
182074
182158
  comma,
182075
- found,
182159
+ found: found2,
182076
182160
  spaceBefore,
182077
182161
  comment,
182078
182162
  hasNewline,
@@ -201214,8 +201298,8 @@ async function resolveOutlineKnowledgeBinding(credentials, input) {
201214
201298
  const collections = (await api.listCollections()).filter(isWritableCollection);
201215
201299
  for (const collection of collections) {
201216
201300
  const documents = await api.listDocuments(collection["id"]);
201217
- const found = documents.find((document4) => string5(document4["url"]) === pasted);
201218
- if (found) return bindingFromDocument(api, found, collection, identity);
201301
+ const found2 = documents.find((document4) => string5(document4["url"]) === pasted);
201302
+ if (found2) return bindingFromDocument(api, found2, collection, identity);
201219
201303
  }
201220
201304
  throw new Error("\u627E\u4E0D\u5230\u8BE5\u6839\u6587\u6863\uFF0C\u6216\u5F53\u524D Token \u5BF9\u5176\u6240\u5728 Collection \u6CA1\u6709\u5199\u6743\u9650");
201221
201305
  }
@@ -204172,10 +204256,10 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
204172
204256
  }
204173
204257
  let artifactId;
204174
204258
  if (escalationId !== void 0) {
204175
- const found = escalationById(kernel.model, escalationId);
204176
- if (!found) throw new Error(`\u4E0A\u62A5 ${escalationId} \u4E0D\u5B58\u5728\uFF08id \u89C1 \`oasis status <\u8282\u70B9>\` \u7684\u300C\u4E0A\u62A5\u300D\u6BB5\uFF09\u3002`);
204177
- if (found.resolved) throw new Error(`\u4E0A\u62A5 ${escalationId} \u5DF2\u7ECF\u5173\u95ED\uFF0C\u65E0\u9700\u518D\u5173\u3002`);
204178
- artifactId = found.artifactId;
204259
+ const found2 = escalationById(kernel.model, escalationId);
204260
+ if (!found2) throw new Error(`\u4E0A\u62A5 ${escalationId} \u4E0D\u5B58\u5728\uFF08id \u89C1 \`oasis status <\u8282\u70B9>\` \u7684\u300C\u4E0A\u62A5\u300D\u6BB5\uFF09\u3002`);
204261
+ if (found2.resolved) throw new Error(`\u4E0A\u62A5 ${escalationId} \u5DF2\u7ECF\u5173\u95ED\uFF0C\u65E0\u9700\u518D\u5173\u3002`);
204262
+ artifactId = found2.artifactId;
204179
204263
  } else {
204180
204264
  const argArtifactId = optStr(args, "artifactId");
204181
204265
  if (argArtifactId === void 0) {
@@ -204457,7 +204541,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
204457
204541
  }
204458
204542
  if (args["dispatch"] === false) {
204459
204543
  await ctx.artifactState?.setWorkspaceDispatchHold(workspace, true);
204460
- await kernel.pauseWorkorder?.(workspace, actor);
204544
+ await kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
204461
204545
  }
204462
204546
  return {
204463
204547
  message: planned.issues.length > 0 ? `planned ${workspace}: created ${planned.spawned.length} nodes\uFF0C${planned.issues.length} \u4E2A staffing \u63D0\u793A\u5DF2\u5199\u5165\u5DE5\u5355` : `planned ${workspace}: created ${planned.spawned.length} nodes`,
@@ -206700,7 +206784,7 @@ async function startOasisServer(opts) {
206700
206784
  }
206701
206785
  if (body2.dispatch === false) {
206702
206786
  await opts.artifactState?.setWorkspaceDispatchHold(workspace, true);
206703
- await engine.kernel.pauseWorkorder?.(workspace, actor);
206787
+ await engine.kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
206704
206788
  }
206705
206789
  await wodraftPlannerIssues.remove(workspace);
206706
206790
  if (opts.chatSession && body2.chatSessionId && workspace !== "ws:default") {
@@ -213283,6 +213367,15 @@ var init_service4 = __esm({
213283
213367
  async getWorkspaceBinding(workOrderId) {
213284
213368
  return this.artifacts.getWorkspaceBinding(workOrderId);
213285
213369
  }
213370
+ /**
213371
+ * 工单负责人(改挂项目门禁的「条目本人」一支)。与列表「负责人」列**同一个出口**:
213372
+ * `buildWorkorderSummaries` 的 `owner`(页头总纲根的 owner)——判权限与界面上显示的负责人不许是两套。
213373
+ * 内核里没有这张工单 → null。
213374
+ */
213375
+ workorderOwnerOf(workOrderId) {
213376
+ const summary = buildWorkorderSummaries(this.kernel.model).find((w2) => w2.id === workOrderId);
213377
+ return summary?.owner.id ?? null;
213378
+ }
213286
213379
  /** 上传一个项目普通文件:内容入 blob 库,记一条元信息旁存(非 oplog 事实)。 */
213287
213380
  async addProjectFile(input) {
213288
213381
  if (!this.blobs) throw new Error("\u9879\u76EE\u6587\u4EF6\u4E0A\u4F20\u9700\u8981 blob \u5E93\uFF08domain \u672A\u6CE8\u5165 blobs\uFF09");
@@ -215385,6 +215478,10 @@ var init_dev_store = __esm({
215385
215478
  async listSessionsByWorkspace(workspace) {
215386
215479
  return [...this.sessions.values()].filter((s2) => s2.workspace === workspace && !s2.analyzedRunId && s2.visibility !== "internal").sort((a, b2) => b2.touchedAt.localeCompare(a.touchedAt)).map((s2) => this.withQuality(s2));
215387
215480
  }
215481
+ /** 语义同 postgres 版:项目「关联对话」,不按人过滤;排除分析 / 内部 / 挂在任务上的会话,created_at 倒序。 */
215482
+ async listSessionsByProject(projectId2) {
215483
+ return [...this.sessions.values()].filter((s2) => (s2.projectId ?? null) === projectId2 && !s2.analyzedRunId && s2.visibility !== "internal" && !s2.workspace).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt) || a.id.localeCompare(b2.id));
215484
+ }
215388
215485
  async listAllAnalysisSessions(opts = {}) {
215389
215486
  const limit = opts.limit ?? 100;
215390
215487
  const sorted = [...this.sessions.values()].filter((s2) => !!s2.analyzedRunId).sort((a, b2) => b2.touchedAt.localeCompare(a.touchedAt) || b2.id.localeCompare(a.id));
@@ -219169,7 +219266,9 @@ var init_sink = __esm({
219169
219266
  st.openTools.set(callId, {
219170
219267
  toolName: typeof p2["name"] === "string" ? p2["name"] : "tool",
219171
219268
  input: p2["input"] ?? null,
219172
- startedAt: event.ts
219269
+ startedAt: event.ts,
219270
+ // 子 Agent 里的调用:adapter 给 payload 标了派出它的那次调用(Claude 的 parent_tool_use_id)。
219271
+ parentCallId: typeof p2["parentToolCallId"] === "string" ? p2["parentToolCallId"] : null
219173
219272
  });
219174
219273
  return;
219175
219274
  }
@@ -219185,7 +219284,7 @@ var init_sink = __esm({
219185
219284
  id: `tc_${st.runId}_${callId}`,
219186
219285
  runId: st.runId,
219187
219286
  callId,
219188
- parentCallId: null,
219287
+ parentCallId: open3.parentCallId,
219189
219288
  toolName: open3.toolName,
219190
219289
  toolKind: toolKindOf(open3.toolName),
219191
219290
  status: isErr ? "failed" : "succeeded",
@@ -219965,6 +220064,7 @@ var init_chat_recovery = __esm({
219965
220064
  durationMs: Date.parse(endedAt) - Date.parse(startedAt),
219966
220065
  exitCode: info.code,
219967
220066
  exitReason: info.reason ?? null,
220067
+ ...exitFailed(info) && info.reason !== "cancelled" ? { errorCode: info.reason ?? "error", errorMessage: formatExitError(info) } : {},
219968
220068
  transcriptRef: info.transcriptRef ?? null,
219969
220069
  usage: info.usage ?? null,
219970
220070
  // 重启前 create/dispatch 路已把 actor/runtime fallback 模型写进 run;退出帧没报 model 时必须保留,
@@ -220262,6 +220362,9 @@ var init_daemon_hub = __esm({
220262
220362
  this.wss = new import_websocket_server.default({
220263
220363
  server,
220264
220364
  path: path41,
220365
+ // workdir_read returns base64 plus a JSON envelope. ws defaults to 100 MiB,
220366
+ // which would disconnect the node before a 200 MiB original reaches HTTP.
220367
+ maxPayload: 4 * Math.ceil(FILE_CONTENT_MAX_BYTES / 3) + 64 * 1024,
220265
220368
  ...resolveNode ? {
220266
220369
  // 异步校验:verifyClient 允许延迟回调,故可在此 await 一次 DB 查询后再放行/拒绝。
220267
220370
  verifyClient: (info, cb) => {
@@ -224914,6 +225017,359 @@ var init_assistant_tier_dispatch = __esm({
224914
225017
  }
224915
225018
  });
224916
225019
 
225020
+ // ../server/src/domains/chat-sessions/files-view.ts
225021
+ function reasonFromWorkdirCode(code2) {
225022
+ switch (code2) {
225023
+ case "NODE_OFFLINE":
225024
+ return "NODE_OFFLINE";
225025
+ case "TIMEOUT":
225026
+ return "TIMEOUT";
225027
+ default:
225028
+ return "ERROR";
225029
+ }
225030
+ }
225031
+ function joinRel2(dir, name) {
225032
+ return dir ? `${dir}/${name}` : name;
225033
+ }
225034
+ function workdirFileItem(rel, name, size, mtime) {
225035
+ return {
225036
+ id: `workdir:${rel}`,
225037
+ name,
225038
+ kind: "file",
225039
+ // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
225040
+ // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
225041
+ content_type: null,
225042
+ size,
225043
+ updated_at: mtime,
225044
+ source: "workdir",
225045
+ blob_ref: null,
225046
+ path: rel,
225047
+ message_id: null,
225048
+ // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
225049
+ // 这里是**按能力不渲染**的判据,不是置灰假钮。
225050
+ editable: false
225051
+ };
225052
+ }
225053
+ async function collectWorkdirFiles(bridge, loc, now = Date.now) {
225054
+ const items = [];
225055
+ if (typeof bridge.walk === "function") {
225056
+ const walked = await bridge.walk(
225057
+ loc.nodeId,
225058
+ { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: "" },
225059
+ WORKDIR_TREE_MAX_ENTRIES
225060
+ );
225061
+ if (walked.ok) {
225062
+ let truncated2 = walked.truncated === true;
225063
+ for (const entry of walked.entries) {
225064
+ if (entry.type !== "file") continue;
225065
+ if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
225066
+ truncated2 = true;
225067
+ break;
225068
+ }
225069
+ const slash = entry.path.lastIndexOf("/");
225070
+ items.push(workdirFileItem(entry.path, entry.path.slice(slash + 1), entry.size, entry.mtime));
225071
+ }
225072
+ return { ok: true, items, truncated: truncated2 };
225073
+ }
225074
+ if (walked.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
225075
+ if (walked.code !== "UNSUPPORTED") return { ok: false, reason: reasonFromWorkdirCode(walked.code) };
225076
+ }
225077
+ const deadline = now() + WORKDIR_WALK_BUDGET_MS;
225078
+ let listCalls = 0;
225079
+ let truncated = false;
225080
+ const listDir = (path41) => {
225081
+ listCalls += 1;
225082
+ return bridge.list(loc.nodeId, {
225083
+ workdirKey: loc.workdirKey,
225084
+ runtimeKind: loc.runtimeKind,
225085
+ rtId: loc.rtId,
225086
+ path: path41
225087
+ });
225088
+ };
225089
+ const root = await listDir("");
225090
+ if (!root.ok) {
225091
+ if (root.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
225092
+ return { ok: false, reason: reasonFromWorkdirCode(root.code) };
225093
+ }
225094
+ let level = [{ path: "", outcome: root }];
225095
+ for (let depth = 0; depth <= WORKDIR_WALK_MAX_DEPTH; depth += 1) {
225096
+ const nextDirs = [];
225097
+ for (const { path: dir, outcome } of level) {
225098
+ if (!outcome.ok) {
225099
+ truncated = true;
225100
+ continue;
225101
+ }
225102
+ if (outcome.truncated) truncated = true;
225103
+ for (const entry of outcome.entries) {
225104
+ if (entry.type === "dir") {
225105
+ nextDirs.push(joinRel2(dir, entry.name));
225106
+ continue;
225107
+ }
225108
+ if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
225109
+ truncated = true;
225110
+ continue;
225111
+ }
225112
+ items.push(workdirFileItem(joinRel2(dir, entry.name), entry.name, entry.size, entry.mtime));
225113
+ }
225114
+ }
225115
+ if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
225116
+ if (nextDirs.length > 0) truncated = true;
225117
+ break;
225118
+ }
225119
+ const budget = WORKDIR_WALK_MAX_LIST_CALLS - listCalls;
225120
+ if (budget <= 0 || now() >= deadline) {
225121
+ truncated = true;
225122
+ break;
225123
+ }
225124
+ const take = nextDirs.slice(0, budget);
225125
+ if (take.length < nextDirs.length) truncated = true;
225126
+ const outcomes = await Promise.all(take.map((p2) => listDir(p2)));
225127
+ level = take.map((p2, i) => ({ path: p2, outcome: outcomes[i] }));
225128
+ }
225129
+ return { ok: true, items, truncated };
225130
+ }
225131
+ async function collectMessageFiles(messages, blobs) {
225132
+ const rows = [];
225133
+ for (const message of messages) {
225134
+ if (!Array.isArray(message.attachments)) continue;
225135
+ message.attachments.forEach((attachment, index2) => {
225136
+ if (!attachment || typeof attachment.name !== "string" || !attachment.name) return;
225137
+ const blobRef = typeof attachment.blobRef === "string" && attachment.blobRef ? attachment.blobRef : null;
225138
+ rows.push({
225139
+ item: {
225140
+ id: `msg:${message.id}:${index2}`,
225141
+ name: attachment.name,
225142
+ kind: "file",
225143
+ updated_at: attachment.updatedAt ?? message.createdAt,
225144
+ source: "message",
225145
+ blob_ref: blobRef,
225146
+ path: null,
225147
+ message_id: message.id,
225148
+ // 没有 blobRef 的是**历史纯文本附件**(契约注释:legacy text attachments may only
225149
+ // contain `name`)——没有可写回的载体,一律不给编辑入口。
225150
+ editable: blobRef !== null && isEditableFileName(attachment.name)
225151
+ },
225152
+ blobRef,
225153
+ declaredType: typeof attachment.contentType === "string" && attachment.contentType ? attachment.contentType : null
225154
+ });
225155
+ });
225156
+ }
225157
+ const metaCache = /* @__PURE__ */ new Map();
225158
+ const metaOf = (ref2) => {
225159
+ if (!blobs) return Promise.resolve(null);
225160
+ let hit = metaCache.get(ref2);
225161
+ if (!hit) {
225162
+ hit = blobs.meta(ref2).catch(() => null);
225163
+ metaCache.set(ref2, hit);
225164
+ }
225165
+ return hit;
225166
+ };
225167
+ return Promise.all(rows.map(async ({ item, blobRef, declaredType }) => {
225168
+ const meta = blobRef ? await metaOf(blobRef) : null;
225169
+ return {
225170
+ ...item,
225171
+ content_type: declaredType ?? meta?.contentType ?? null,
225172
+ size: meta?.size ?? null
225173
+ };
225174
+ }));
225175
+ }
225176
+ function mergeChatSessionFiles(messageItems, workdirItems, materializedCopies = /* @__PURE__ */ new Set()) {
225177
+ const out = [];
225178
+ const seenMessageKeys = /* @__PURE__ */ new Set();
225179
+ for (const item of [...messageItems].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at))) {
225180
+ const key = `${item.name}\0${item.blob_ref ?? ""}`;
225181
+ if (seenMessageKeys.has(key)) continue;
225182
+ seenMessageKeys.add(key);
225183
+ out.push(item);
225184
+ }
225185
+ const seenWorkdirPaths = /* @__PURE__ */ new Set();
225186
+ for (const item of workdirItems) {
225187
+ if (item.path && materializedCopies.has(item.path)) continue;
225188
+ if (seenWorkdirPaths.has(item.id)) continue;
225189
+ seenWorkdirPaths.add(item.id);
225190
+ out.push(item);
225191
+ }
225192
+ out.sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.name.localeCompare(b2.name));
225193
+ return out;
225194
+ }
225195
+ async function findMaterializedAttachmentCopies(messageItems, workdirItems, read) {
225196
+ const copies = /* @__PURE__ */ new Set();
225197
+ const attachments = messageItems.filter((item) => item.blob_ref && /^[a-f0-9]{64}$/.test(item.blob_ref));
225198
+ let budget = CHAT_ATTACHMENT_MAX_BYTES;
225199
+ const deadline = Date.now() + WORKDIR_WALK_BUDGET_MS;
225200
+ let reads = 0;
225201
+ for (const file of workdirItems) {
225202
+ const rootFile = file.path === file.name;
225203
+ const inputFile = file.path?.startsWith("inputs/attachments/") && !file.path.slice("inputs/attachments/".length).includes("/");
225204
+ if (!file.path || !rootFile && !inputFile) continue;
225205
+ const candidates = attachments.filter((attachment) => {
225206
+ const safe = safeAttachmentName(attachment.name);
225207
+ const numbered = file.name.endsWith(`-${safe}`) && /^\d+$/.test(file.name.slice(0, -(safe.length + 1)));
225208
+ return (rootFile ? file.name === attachment.name : file.name === safe || numbered) && attachment.size !== null && attachment.size === file.size;
225209
+ });
225210
+ if (Date.now() >= deadline) break;
225211
+ if (!candidates.length || file.size === null || file.size > budget || reads >= 10) continue;
225212
+ reads += 1;
225213
+ budget -= file.size;
225214
+ let timer;
225215
+ try {
225216
+ const result = await Promise.race([
225217
+ read(file.path, file.size),
225218
+ new Promise((resolve10) => {
225219
+ timer = setTimeout(() => resolve10(null), Math.max(0, deadline - Date.now()));
225220
+ })
225221
+ ]);
225222
+ if (!result?.ok) continue;
225223
+ const bytes2 = Buffer.from(result.contentBase64, "base64");
225224
+ if (bytes2.length !== file.size) continue;
225225
+ const hash2 = (0, import_node_crypto50.createHash)("sha256").update(bytes2).digest("hex");
225226
+ if (candidates.some((attachment) => attachment.blob_ref === hash2)) copies.add(file.path);
225227
+ } catch {
225228
+ } finally {
225229
+ if (timer) clearTimeout(timer);
225230
+ }
225231
+ }
225232
+ return copies;
225233
+ }
225234
+ var import_node_crypto50, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS, WORKDIR_TREE_MAX_ENTRIES;
225235
+ var init_files_view = __esm({
225236
+ "../server/src/domains/chat-sessions/files-view.ts"() {
225237
+ "use strict";
225238
+ import_node_crypto50 = require("node:crypto");
225239
+ init_chat_attachment_files();
225240
+ init_src();
225241
+ WORKDIR_WALK_MAX_DEPTH = 3;
225242
+ WORKDIR_WALK_MAX_ENTRIES = 200;
225243
+ WORKDIR_WALK_MAX_LIST_CALLS = 20;
225244
+ WORKDIR_WALK_BUDGET_MS = 1e4;
225245
+ WORKDIR_TREE_MAX_ENTRIES = 2e3;
225246
+ }
225247
+ });
225248
+
225249
+ // ../server/src/domains/projects/chat-files.ts
225250
+ function isProjectChatSession(session) {
225251
+ if (session.visibility === "internal") return false;
225252
+ if (session.analyzedRunId) return false;
225253
+ if (session.workspace) return false;
225254
+ return true;
225255
+ }
225256
+ function chatFolderTitle(session) {
225257
+ const title = session.title?.trim();
225258
+ return title ? title : "\u672A\u547D\u540D\u5BF9\u8BDD";
225259
+ }
225260
+ function outputDisplayName(path41) {
225261
+ return path41.startsWith("outputs/") ? path41.slice("outputs/".length) : path41;
225262
+ }
225263
+ async function collectProjectChatFiles(chat, projectId2, meta, options = {}) {
225264
+ const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
225265
+ const [attachmentMessages, outputSessions] = await Promise.all([
225266
+ chat.listAttachmentMessages ? chat.listAttachmentMessages(scope) : Promise.resolve([]),
225267
+ chat.listOutputCopies ? chat.listOutputCopies(scope) : Promise.resolve([])
225268
+ ]);
225269
+ const sessionIds = [.../* @__PURE__ */ new Set([
225270
+ ...attachmentMessages.map((m2) => m2.sessionId),
225271
+ ...outputSessions.map((s2) => s2.sessionId)
225272
+ ])];
225273
+ const sessions = /* @__PURE__ */ new Map();
225274
+ await Promise.all(sessionIds.map(async (id) => {
225275
+ const session = await chat.getSession(id);
225276
+ if (!session) return;
225277
+ if ((session.projectId ?? null) !== scope) return;
225278
+ if (!isProjectChatSession(session)) return;
225279
+ if (options.isDelegationChild && await options.isDelegationChild(id).catch(() => false)) return;
225280
+ sessions.set(id, session);
225281
+ }));
225282
+ const out = [];
225283
+ const base = (session) => ({
225284
+ projectId: projectId2,
225285
+ chatSessionId: session.id,
225286
+ chatTitle: chatFolderTitle(session),
225287
+ chatOwnerId: session.humanActorId
225288
+ });
225289
+ const bySession = /* @__PURE__ */ new Map();
225290
+ for (const message of attachmentMessages) {
225291
+ if (!sessions.has(message.sessionId)) continue;
225292
+ const list2 = bySession.get(message.sessionId) ?? [];
225293
+ list2.push(message);
225294
+ bySession.set(message.sessionId, list2);
225295
+ }
225296
+ for (const [sessionId, messages] of bySession) {
225297
+ const session = sessions.get(sessionId);
225298
+ const rows = await collectMessageFiles(messages.map((m2) => ({ ...m2, id: m2.messageId })), { meta });
225299
+ for (const row of mergeChatSessionFiles(rows, [])) {
225300
+ if (row.blob_ref === null) continue;
225301
+ out.push({
225302
+ ...base(session),
225303
+ fileId: `chat:${sessionId}:${row.id}`,
225304
+ name: row.name,
225305
+ blobId: row.blob_ref,
225306
+ ...row.content_type ? { contentType: row.content_type } : {},
225307
+ size: row.size,
225308
+ uploadedBy: session.humanActorId,
225309
+ uploadedAt: row.updated_at,
225310
+ chatSource: "attachment"
225311
+ });
225312
+ }
225313
+ }
225314
+ for (const record8 of outputSessions) {
225315
+ const session = sessions.get(record8.sessionId);
225316
+ if (!session) continue;
225317
+ const seen = new Set(out.filter((file) => file.chatSessionId === session.id).map((file) => `${file.name}\0${file.blobId}`));
225318
+ const copies = record8.copies ?? {};
225319
+ for (const path41 of Object.keys(copies).sort((a, b2) => a.localeCompare(b2))) {
225320
+ const copy = copies[path41];
225321
+ if (!copy?.blobRef) continue;
225322
+ if (seen.has(`${copy.name}\0${copy.blobRef}`)) continue;
225323
+ out.push({
225324
+ ...base(session),
225325
+ fileId: `chat:${session.id}:out:${copy.path}`,
225326
+ name: outputDisplayName(copy.path),
225327
+ blobId: copy.blobRef,
225328
+ ...copy.contentType ? { contentType: copy.contentType } : {},
225329
+ size: copy.size,
225330
+ uploadedBy: session.aiActorId,
225331
+ uploadedAt: Number.isNaN(Date.parse(copy.mtime)) ? copy.copiedAt : new Date(copy.mtime).toISOString(),
225332
+ chatSource: "output"
225333
+ });
225334
+ }
225335
+ }
225336
+ return out;
225337
+ }
225338
+ var init_chat_files2 = __esm({
225339
+ "../server/src/domains/projects/chat-files.ts"() {
225340
+ "use strict";
225341
+ init_files_view();
225342
+ init_uncategorized_project();
225343
+ }
225344
+ });
225345
+
225346
+ // ../server/src/domains/projects/chat-sessions.ts
225347
+ async function listProjectChatSessions(chat, projectId2, delegationChildIds) {
225348
+ if (!chat.listSessionsByProject) return [];
225349
+ const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
225350
+ const sessions = (await chat.listSessionsByProject(scope)).filter((s2) => (s2.projectId ?? null) === scope && isProjectChatSession(s2));
225351
+ const children = delegationChildIds && sessions.length > 0 ? await delegationChildIds(sessions.map((s2) => s2.id)).catch(() => /* @__PURE__ */ new Set()) : /* @__PURE__ */ new Set();
225352
+ return sessions.filter((s2) => !children.has(s2.id)).map(toRow);
225353
+ }
225354
+ function toRow(s2) {
225355
+ const title = s2.title?.trim();
225356
+ return {
225357
+ id: s2.id,
225358
+ title: title ? title : null,
225359
+ humanActorId: s2.humanActorId,
225360
+ aiActorId: s2.aiActorId,
225361
+ createdAt: s2.createdAt,
225362
+ projectId: s2.projectId ?? null
225363
+ };
225364
+ }
225365
+ var init_chat_sessions = __esm({
225366
+ "../server/src/domains/projects/chat-sessions.ts"() {
225367
+ "use strict";
225368
+ init_chat_files2();
225369
+ init_uncategorized_project();
225370
+ }
225371
+ });
225372
+
224917
225373
  // ../server/src/domains/projects/routes.ts
224918
225374
  function projectsDomain(opts) {
224919
225375
  const svc = async (req) => opts.resolveService ? opts.resolveService(req.auth.companyId) : opts.service;
@@ -224927,6 +225383,28 @@ function projectsDomain(opts) {
224927
225383
  if (role === "owner" || role === "admin") return;
224928
225384
  throw new ApiError(403, action.code, `\u4F60\u6CA1\u6709${action.verb}\u8BE5\u9879\u76EE\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7EC4\u7EC7\u7BA1\u7406\u5458`);
224929
225385
  };
225386
+ const mayReassignProject = async (req, item) => {
225387
+ if (item.actorId && item.actorId === req.auth.actor) return true;
225388
+ if (item.currentProjectId && !isUncategorizedProjectId(item.currentProjectId)) {
225389
+ const project = await (await svc(req)).getProject(item.currentProjectId);
225390
+ if (project?.createdBy && project.createdBy.id === req.auth.actor) return true;
225391
+ }
225392
+ if (!opts.resolveMemberRole) return false;
225393
+ const role = await opts.resolveMemberRole(req.auth.companyId, req.auth.actor);
225394
+ return role === "owner" || role === "admin";
225395
+ };
225396
+ const requireWorkorderReassignPermission = async (req, workOrderId) => {
225397
+ if (!opts.resolveMemberRole) return;
225398
+ const service = await svc(req);
225399
+ const binding = await service.getWorkspaceBinding(workOrderId).catch(() => null);
225400
+ const allowed = await mayReassignProject(req, {
225401
+ actorId: service.workorderOwnerOf(workOrderId),
225402
+ currentProjectId: binding?.projectId ?? null
225403
+ });
225404
+ if (!allowed) {
225405
+ throw new ApiError(403, "WORKORDER_REASSIGN_FORBIDDEN", "\u53EA\u6709\u4EFB\u52A1\u8D1F\u8D23\u4EBA\u3001\u9879\u76EE\u8D1F\u8D23\u4EBA\u6216\u7EC4\u7EC7\u7BA1\u7406\u5458\u53EF\u4EE5\u79FB\u52A8\u8FD9\u4E2A\u4EFB\u52A1");
225406
+ }
225407
+ };
224930
225408
  return (router) => {
224931
225409
  router.post("/api/projects", async (req) => {
224932
225410
  const body2 = req.body;
@@ -225044,6 +225522,45 @@ function projectsDomain(opts) {
225044
225522
  const items = await (await svc(req)).listWorkordersForProject(req.params.id);
225045
225523
  return { status: 200, body: { items } };
225046
225524
  });
225525
+ router.get("/api/projects/:id/chat-sessions", async (req) => {
225526
+ const project = await (await svc(req)).getProject(req.params.id);
225527
+ if (!project) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u9879\u76EE\uFF1A${req.params.id}`);
225528
+ if (!opts.chatSessionsFor) return { status: 200, body: { items: [] } };
225529
+ const companyId = req.auth.companyId;
225530
+ const rows = await listProjectChatSessions(
225531
+ await opts.chatSessionsFor(companyId),
225532
+ req.params.id,
225533
+ opts.delegationChildIds ? (ids2) => opts.delegationChildIds(companyId, ids2) : void 0
225534
+ );
225535
+ return { status: 200, body: { items: rows.map(toApiProjectChatSession) } };
225536
+ });
225537
+ router.patch("/api/chat-sessions/:id/project", async (req) => {
225538
+ if (!opts.chatSessionsFor) throw new ApiError(404, "NOT_FOUND", "session not found");
225539
+ const store = await opts.chatSessionsFor(req.auth.companyId);
225540
+ const session = await store.getSession(req.params.id);
225541
+ if (!session || !isProjectChatSession(session)) throw new ApiError(404, "NOT_FOUND", "session not found");
225542
+ if (opts.delegationChildIds && (await opts.delegationChildIds(req.auth.companyId, [session.id])).has(session.id)) {
225543
+ throw new ApiError(404, "NOT_FOUND", "session not found");
225544
+ }
225545
+ const allowed = await mayReassignProject(req, {
225546
+ actorId: session.humanActorId,
225547
+ currentProjectId: session.projectId ?? null
225548
+ });
225549
+ if (!allowed) {
225550
+ throw new ApiError(403, "CHAT_SESSION_REASSIGN_FORBIDDEN", "\u53EA\u6709\u5BF9\u8BDD\u53D1\u8D77\u4EBA\u3001\u9879\u76EE\u8D1F\u8D23\u4EBA\u6216\u7EC4\u7EC7\u7BA1\u7406\u5458\u53EF\u4EE5\u79FB\u52A8\u8FD9\u4E2A\u5BF9\u8BDD");
225551
+ }
225552
+ const b2 = req.body ?? {};
225553
+ if (!("project_id" in b2) && !("projectId" in b2)) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 project_id");
225554
+ const raw = "project_id" in b2 ? b2.project_id : b2.projectId;
225555
+ const target = raw === null || raw === "" || raw === "null" || typeof raw === "string" && isUncategorizedProjectId(raw) ? null : raw;
225556
+ if (target !== null) {
225557
+ if (typeof target !== "string") throw new ApiError(400, "BAD_REQUEST", "project_id \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216 null");
225558
+ const project = await (await svc(req)).getProject(target);
225559
+ if (!project) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u9879\u76EE\uFF1A${target}`);
225560
+ }
225561
+ await store.updateSession(session.id, { projectId: target });
225562
+ return { status: 200, body: { chat_session_id: session.id, project_id: target } };
225563
+ });
225047
225564
  router.post("/api/projects/:id/files", async (req) => {
225048
225565
  const b2 = req.body ?? {};
225049
225566
  const base642 = b2.content_base64 ?? b2.contentBase64;
@@ -225250,6 +225767,7 @@ ${errs.join("\n")}`);
225250
225767
  return { status: 200, body: toApiNodeOutputValidation(validation) };
225251
225768
  });
225252
225769
  router.patch("/api/work-orders/:workOrderId/project", async (req) => {
225770
+ await requireWorkorderReassignPermission(req, req.params.workOrderId);
225253
225771
  const b2 = req.body ?? {};
225254
225772
  const nulledPatch = "project_id" in b2 && b2.project_id === null || "projectId" in b2 && b2.projectId === null || b2.project_id === "null" || b2.project_id === "" || b2.projectId === "null" || b2.projectId === "";
225255
225773
  if (nulledPatch) {
@@ -225270,6 +225788,7 @@ ${errs.join("\n")}`);
225270
225788
  }
225271
225789
  });
225272
225790
  router.delete("/api/work-orders/:workOrderId/project", async (req) => {
225791
+ await requireWorkorderReassignPermission(req, req.params.workOrderId);
225273
225792
  try {
225274
225793
  await (await svc(req)).unlinkWorkorder(req.params.workOrderId);
225275
225794
  return { status: 200, body: { work_order_id: req.params.workOrderId, project_id: null } };
@@ -225743,6 +226262,16 @@ function toApiWorkOrderDetail(detail) {
225743
226262
  }))
225744
226263
  };
225745
226264
  }
226265
+ function toApiProjectChatSession(row) {
226266
+ return {
226267
+ id: row.id,
226268
+ title: row.title,
226269
+ human_actor_id: row.humanActorId,
226270
+ ai_actor_id: row.aiActorId,
226271
+ created_at: row.createdAt,
226272
+ project_id: row.projectId
226273
+ };
226274
+ }
225746
226275
  function toApiProjectFilesView(view) {
225747
226276
  return {
225748
226277
  total: view.total,
@@ -225801,6 +226330,8 @@ var init_routes5 = __esm({
225801
226330
  "use strict";
225802
226331
  init_router();
225803
226332
  init_src();
226333
+ init_chat_files2();
226334
+ init_chat_sessions();
225804
226335
  init_uncategorized_project();
225805
226336
  }
225806
226337
  });
@@ -228095,11 +228626,11 @@ function fromRow(row) {
228095
228626
  for (const [k2, v2] of opt) if (v2 !== void 0 && v2 !== null) r[k2] = v2;
228096
228627
  return r;
228097
228628
  }
228098
- var import_node_crypto50, import_node_fs14, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
228629
+ var import_node_crypto51, import_node_fs14, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
228099
228630
  var init_notification_store = __esm({
228100
228631
  "../server/src/domains/collab/notification-store.ts"() {
228101
228632
  "use strict";
228102
- import_node_crypto50 = require("node:crypto");
228633
+ import_node_crypto51 = require("node:crypto");
228103
228634
  import_node_fs14 = __toESM(require("node:fs"), 1);
228104
228635
  DEFAULT_PAGE = 50;
228105
228636
  MAX_PAGE = 200;
@@ -228132,7 +228663,7 @@ var init_notification_store = __esm({
228132
228663
  async insert(n) {
228133
228664
  const existing = this.findLive(n.recipient, n.sourceKey, n.category);
228134
228665
  if (existing) return existing;
228135
- const row = { ...n, id: `ntf:${(0, import_node_crypto50.randomUUID)()}`, updatedAt: n.enteredAt };
228666
+ const row = { ...n, id: `ntf:${(0, import_node_crypto51.randomUUID)()}`, updatedAt: n.enteredAt };
228136
228667
  this.rows.set(row.id, row);
228137
228668
  this.save();
228138
228669
  return row;
@@ -228151,13 +228682,22 @@ var init_notification_store = __esm({
228151
228682
  this.rows.set(id, next);
228152
228683
  this.save();
228153
228684
  }
228685
+ async rekey(id, sourceKey, patch, at) {
228686
+ const r = this.rows.get(id);
228687
+ if (!r || r.status !== "pending" || this.findLive(r.recipient, sourceKey, r.category)) return false;
228688
+ this.rows.set(id, { ...r, sourceKey, ...patch.nodeId ? { nodeId: patch.nodeId } : {} });
228689
+ await this.refresh(id, patch, at);
228690
+ return true;
228691
+ }
228154
228692
  async close(id, closure) {
228155
228693
  const r = this.rows.get(id);
228156
228694
  if (!r || r.status !== "pending") return false;
228157
228695
  const next = { ...r, status: closure.status, closedAt: closure.closedAt, updatedAt: closure.closedAt };
228158
228696
  if (closure.closedBy) next.closedBy = closure.closedBy;
228159
- if (closure.status === "handled") next.result = closure.result;
228160
- else next.invalidReason = closure.invalidReason;
228697
+ if (closure.status === "handled") {
228698
+ next.result = closure.result;
228699
+ next.readAt ??= closure.closedAt;
228700
+ } else next.invalidReason = closure.invalidReason;
228161
228701
  this.rows.set(id, next);
228162
228702
  this.save();
228163
228703
  return true;
@@ -228176,6 +228716,16 @@ var init_notification_store = __esm({
228176
228716
  }
228177
228717
  if (changed) this.save();
228178
228718
  }
228719
+ async countUnread(recipient) {
228720
+ let pending = 0;
228721
+ let all2 = 0;
228722
+ for (const r of this.rows.values()) {
228723
+ if (r.recipient !== recipient || r.readAt) continue;
228724
+ all2++;
228725
+ if (r.status === "pending") pending++;
228726
+ }
228727
+ return { pending, all: all2 };
228728
+ }
228179
228729
  async purgeBefore(cutoff) {
228180
228730
  let n = 0;
228181
228731
  for (const [id, r] of this.rows) {
@@ -228236,7 +228786,7 @@ var init_notification_store = __esm({
228236
228786
  return new _PostgresNotificationStore(sql, schema);
228237
228787
  }
228238
228788
  async insert(n) {
228239
- const id = `ntf:${(0, import_node_crypto50.randomUUID)()}`;
228789
+ const id = `ntf:${(0, import_node_crypto51.randomUUID)()}`;
228240
228790
  const r = await this.sql.query(
228241
228791
  `INSERT INTO ${this.t} (id, recipient, category, source_key, kind, workorder_id, node_id, card_id, actor_id,
228242
228792
  title, workspace_title, entered_at, updated_at, status, read_at, item)
@@ -228289,9 +228839,34 @@ var init_notification_store = __esm({
228289
228839
  ]
228290
228840
  );
228291
228841
  }
228842
+ async rekey(id, sourceKey, patch, at) {
228843
+ try {
228844
+ const r = await this.sql.query(
228845
+ `UPDATE ${this.t} SET source_key = $2, node_id = COALESCE($3, node_id), title = $4, workspace_title = $5,
228846
+ actor_id = $6, card_id = $7, item = $8, updated_at = $9
228847
+ WHERE id = $1 AND status = 'pending'`,
228848
+ [
228849
+ id,
228850
+ sourceKey,
228851
+ patch.nodeId ?? null,
228852
+ patch.title,
228853
+ patch.workspaceTitle ?? null,
228854
+ patch.actorId ?? null,
228855
+ patch.cardId ?? null,
228856
+ patch.item ? JSON.stringify(patch.item) : null,
228857
+ at
228858
+ ]
228859
+ );
228860
+ return (r.rowCount ?? 0) > 0;
228861
+ } catch (err) {
228862
+ if (err.code === "23505") return false;
228863
+ throw err;
228864
+ }
228865
+ }
228292
228866
  async close(id, closure) {
228293
228867
  const r = await this.sql.query(
228294
- `UPDATE ${this.t} SET status = $2, closed_at = $3, closed_by = $4, result = $5, invalid_reason = $6, updated_at = $3
228868
+ `UPDATE ${this.t} SET status = $2, closed_at = $3, closed_by = $4, result = $5, invalid_reason = $6, updated_at = $3,
228869
+ read_at = CASE WHEN $2 = 'handled' THEN COALESCE(read_at, $3::timestamptz) ELSE read_at END
228295
228870
  WHERE id = $1 AND status = 'pending'`,
228296
228871
  [
228297
228872
  id,
@@ -228331,6 +228906,15 @@ var init_notification_store = __esm({
228331
228906
  [recipient, [...ids2], at]
228332
228907
  );
228333
228908
  }
228909
+ async countUnread(recipient) {
228910
+ const r = await this.sql.query(
228911
+ `SELECT count(*) FILTER (WHERE status = 'pending')::int AS pending, count(*)::int AS "all"
228912
+ FROM ${this.t} WHERE recipient = $1 AND read_at IS NULL`,
228913
+ [recipient]
228914
+ );
228915
+ const row = r.rows[0] ?? {};
228916
+ return { pending: Number(row["pending"] ?? 0), all: Number(row["all"] ?? 0) };
228917
+ }
228334
228918
  async purgeBefore(cutoff) {
228335
228919
  const r = await this.sql.query(
228336
228920
  `DELETE FROM ${this.t}
@@ -229153,13 +229737,13 @@ function nameKey(name) {
229153
229737
  function cleanName(name) {
229154
229738
  return name.trim().replace(/\s+/g, " ");
229155
229739
  }
229156
- var import_node_fs15, import_node_path20, import_node_crypto51, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
229740
+ var import_node_fs15, import_node_path20, import_node_crypto52, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
229157
229741
  var init_tags = __esm({
229158
229742
  "../server/src/domains/collab/tags.ts"() {
229159
229743
  "use strict";
229160
229744
  import_node_fs15 = __toESM(require("node:fs"), 1);
229161
229745
  import_node_path20 = __toESM(require("node:path"), 1);
229162
- import_node_crypto51 = __toESM(require("node:crypto"), 1);
229746
+ import_node_crypto52 = __toESM(require("node:crypto"), 1);
229163
229747
  SEP = "::";
229164
229748
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
229165
229749
  prefixOf = (companyId) => `${companyId}${SEP}`;
@@ -229177,7 +229761,7 @@ var init_tags = __esm({
229177
229761
  if (!name) return null;
229178
229762
  if (this.entries(companyId).some((t) => nameKey(t.name) === nameKey(name))) return null;
229179
229763
  const tag = {
229180
- id: `tag-${import_node_crypto51.default.randomBytes(6).toString("hex")}`,
229764
+ id: `tag-${import_node_crypto52.default.randomBytes(6).toString("hex")}`,
229181
229765
  name,
229182
229766
  ...input.color?.trim() ? { color: input.color.trim() } : {},
229183
229767
  ...input.description?.trim() ? { description: input.description.trim() } : {}
@@ -230028,7 +230612,27 @@ function collabDomain(opts) {
230028
230612
  limit
230029
230613
  });
230030
230614
  const last = items.at(-1);
230031
- return { status: 200, body: { items, cursor: last && items.length === limit ? notificationCursor(last) : null } };
230615
+ const unread = await store.countUnread(req.auth.actor);
230616
+ const body2 = { items, cursor: last && items.length === limit ? notificationCursor(last) : null, unread };
230617
+ return { status: 200, body: body2 };
230618
+ });
230619
+ router.post("/api/notifications/read", async (req) => {
230620
+ if (!opts.resolveNotificationLedger) {
230621
+ return { status: 501, body: { error: { code: "not_implemented", message: "\u672C\u90E8\u7F72\u672A\u542F\u7528\u901A\u77E5\u53F0\u8D26" } } };
230622
+ }
230623
+ const ids2 = req.body?.ids;
230624
+ if (!Array.isArray(ids2) || ids2.length === 0 || ids2.length > 200 || !ids2.every((id) => typeof id === "string" && id.length > 0)) {
230625
+ return { status: 400, body: { error: { code: "bad_request", message: "ids \u5FC5\u987B\u662F 1\u2013200 \u4E2A\u901A\u77E5 id" } } };
230626
+ }
230627
+ let store;
230628
+ try {
230629
+ store = await opts.resolveNotificationLedger(req.auth.companyId);
230630
+ } catch (err) {
230631
+ console.warn(`[notifications] \u65E0\u53EF\u4FE1\u516C\u53F8\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u6807\u5DF2\u8BFB\uFF08\u4E0D\u56DE\u843D\u9ED8\u8BA4\u516C\u53F8\uFF09: ${String(err)}`);
230632
+ }
230633
+ if (!store) return { status: 503, body: { error: { code: "unavailable", message: "\u901A\u77E5\u53F0\u8D26\u6682\u4E0D\u53EF\u7528" } } };
230634
+ await store.markRead(req.auth.actor, ids2, (/* @__PURE__ */ new Date()).toISOString());
230635
+ return { status: 200, body: { ok: true } };
230032
230636
  });
230033
230637
  const companyOf = (req) => req.auth.companyId ?? "company:default";
230034
230638
  const noTagStore = { status: 501, body: { error: { code: "not_implemented", message: "\u672C\u90E8\u7F72\u672A\u542F\u7528\u5DE5\u5355\u6807\u7B7E" } } };
@@ -230316,326 +230920,6 @@ var init_collab = __esm({
230316
230920
  }
230317
230921
  });
230318
230922
 
230319
- // ../server/src/domains/chat-sessions/files-view.ts
230320
- function reasonFromWorkdirCode(code2) {
230321
- switch (code2) {
230322
- case "NODE_OFFLINE":
230323
- return "NODE_OFFLINE";
230324
- case "TIMEOUT":
230325
- return "TIMEOUT";
230326
- default:
230327
- return "ERROR";
230328
- }
230329
- }
230330
- function joinRel2(dir, name) {
230331
- return dir ? `${dir}/${name}` : name;
230332
- }
230333
- function workdirFileItem(rel, name, size, mtime) {
230334
- return {
230335
- id: `workdir:${rel}`,
230336
- name,
230337
- kind: "file",
230338
- // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
230339
- // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
230340
- content_type: null,
230341
- size,
230342
- updated_at: mtime,
230343
- source: "workdir",
230344
- blob_ref: null,
230345
- path: rel,
230346
- message_id: null,
230347
- // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
230348
- // 这里是**按能力不渲染**的判据,不是置灰假钮。
230349
- editable: false
230350
- };
230351
- }
230352
- async function collectWorkdirFiles(bridge, loc, now = Date.now) {
230353
- const items = [];
230354
- if (typeof bridge.walk === "function") {
230355
- const walked = await bridge.walk(
230356
- loc.nodeId,
230357
- { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: "" },
230358
- WORKDIR_TREE_MAX_ENTRIES
230359
- );
230360
- if (walked.ok) {
230361
- let truncated2 = walked.truncated === true;
230362
- for (const entry of walked.entries) {
230363
- if (entry.type !== "file") continue;
230364
- if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
230365
- truncated2 = true;
230366
- break;
230367
- }
230368
- const slash = entry.path.lastIndexOf("/");
230369
- items.push(workdirFileItem(entry.path, entry.path.slice(slash + 1), entry.size, entry.mtime));
230370
- }
230371
- return { ok: true, items, truncated: truncated2 };
230372
- }
230373
- if (walked.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
230374
- if (walked.code !== "UNSUPPORTED") return { ok: false, reason: reasonFromWorkdirCode(walked.code) };
230375
- }
230376
- const deadline = now() + WORKDIR_WALK_BUDGET_MS;
230377
- let listCalls = 0;
230378
- let truncated = false;
230379
- const listDir = (path41) => {
230380
- listCalls += 1;
230381
- return bridge.list(loc.nodeId, {
230382
- workdirKey: loc.workdirKey,
230383
- runtimeKind: loc.runtimeKind,
230384
- rtId: loc.rtId,
230385
- path: path41
230386
- });
230387
- };
230388
- const root = await listDir("");
230389
- if (!root.ok) {
230390
- if (root.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
230391
- return { ok: false, reason: reasonFromWorkdirCode(root.code) };
230392
- }
230393
- let level = [{ path: "", outcome: root }];
230394
- for (let depth = 0; depth <= WORKDIR_WALK_MAX_DEPTH; depth += 1) {
230395
- const nextDirs = [];
230396
- for (const { path: dir, outcome } of level) {
230397
- if (!outcome.ok) {
230398
- truncated = true;
230399
- continue;
230400
- }
230401
- if (outcome.truncated) truncated = true;
230402
- for (const entry of outcome.entries) {
230403
- if (entry.type === "dir") {
230404
- nextDirs.push(joinRel2(dir, entry.name));
230405
- continue;
230406
- }
230407
- if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
230408
- truncated = true;
230409
- continue;
230410
- }
230411
- items.push(workdirFileItem(joinRel2(dir, entry.name), entry.name, entry.size, entry.mtime));
230412
- }
230413
- }
230414
- if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
230415
- if (nextDirs.length > 0) truncated = true;
230416
- break;
230417
- }
230418
- const budget = WORKDIR_WALK_MAX_LIST_CALLS - listCalls;
230419
- if (budget <= 0 || now() >= deadline) {
230420
- truncated = true;
230421
- break;
230422
- }
230423
- const take = nextDirs.slice(0, budget);
230424
- if (take.length < nextDirs.length) truncated = true;
230425
- const outcomes = await Promise.all(take.map((p2) => listDir(p2)));
230426
- level = take.map((p2, i) => ({ path: p2, outcome: outcomes[i] }));
230427
- }
230428
- return { ok: true, items, truncated };
230429
- }
230430
- async function collectMessageFiles(messages, blobs) {
230431
- const rows = [];
230432
- for (const message of messages) {
230433
- if (!Array.isArray(message.attachments)) continue;
230434
- message.attachments.forEach((attachment, index2) => {
230435
- if (!attachment || typeof attachment.name !== "string" || !attachment.name) return;
230436
- const blobRef = typeof attachment.blobRef === "string" && attachment.blobRef ? attachment.blobRef : null;
230437
- rows.push({
230438
- item: {
230439
- id: `msg:${message.id}:${index2}`,
230440
- name: attachment.name,
230441
- kind: "file",
230442
- updated_at: attachment.updatedAt ?? message.createdAt,
230443
- source: "message",
230444
- blob_ref: blobRef,
230445
- path: null,
230446
- message_id: message.id,
230447
- // 没有 blobRef 的是**历史纯文本附件**(契约注释:legacy text attachments may only
230448
- // contain `name`)——没有可写回的载体,一律不给编辑入口。
230449
- editable: blobRef !== null && isEditableFileName(attachment.name)
230450
- },
230451
- blobRef,
230452
- declaredType: typeof attachment.contentType === "string" && attachment.contentType ? attachment.contentType : null
230453
- });
230454
- });
230455
- }
230456
- const metaCache = /* @__PURE__ */ new Map();
230457
- const metaOf = (ref2) => {
230458
- if (!blobs) return Promise.resolve(null);
230459
- let hit = metaCache.get(ref2);
230460
- if (!hit) {
230461
- hit = blobs.meta(ref2).catch(() => null);
230462
- metaCache.set(ref2, hit);
230463
- }
230464
- return hit;
230465
- };
230466
- return Promise.all(rows.map(async ({ item, blobRef, declaredType }) => {
230467
- const meta = blobRef ? await metaOf(blobRef) : null;
230468
- return {
230469
- ...item,
230470
- content_type: declaredType ?? meta?.contentType ?? null,
230471
- size: meta?.size ?? null
230472
- };
230473
- }));
230474
- }
230475
- function mergeChatSessionFiles(messageItems, workdirItems, materializedCopies = /* @__PURE__ */ new Set()) {
230476
- const out = [];
230477
- const seenMessageKeys = /* @__PURE__ */ new Set();
230478
- for (const item of [...messageItems].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at))) {
230479
- const key = `${item.name}\0${item.blob_ref ?? ""}`;
230480
- if (seenMessageKeys.has(key)) continue;
230481
- seenMessageKeys.add(key);
230482
- out.push(item);
230483
- }
230484
- const seenWorkdirPaths = /* @__PURE__ */ new Set();
230485
- for (const item of workdirItems) {
230486
- if (item.path && materializedCopies.has(item.path)) continue;
230487
- if (seenWorkdirPaths.has(item.id)) continue;
230488
- seenWorkdirPaths.add(item.id);
230489
- out.push(item);
230490
- }
230491
- out.sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.name.localeCompare(b2.name));
230492
- return out;
230493
- }
230494
- async function findMaterializedAttachmentCopies(messageItems, workdirItems, read) {
230495
- const copies = /* @__PURE__ */ new Set();
230496
- const attachments = messageItems.filter((item) => item.blob_ref && /^[a-f0-9]{64}$/.test(item.blob_ref));
230497
- let budget = CHAT_ATTACHMENT_MAX_BYTES;
230498
- const deadline = Date.now() + WORKDIR_WALK_BUDGET_MS;
230499
- let reads = 0;
230500
- for (const file of workdirItems) {
230501
- const rootFile = file.path === file.name;
230502
- const inputFile = file.path?.startsWith("inputs/attachments/") && !file.path.slice("inputs/attachments/".length).includes("/");
230503
- if (!file.path || !rootFile && !inputFile) continue;
230504
- const candidates = attachments.filter((attachment) => {
230505
- const safe = safeAttachmentName(attachment.name);
230506
- const numbered = file.name.endsWith(`-${safe}`) && /^\d+$/.test(file.name.slice(0, -(safe.length + 1)));
230507
- return (rootFile ? file.name === attachment.name : file.name === safe || numbered) && attachment.size !== null && attachment.size === file.size;
230508
- });
230509
- if (Date.now() >= deadline) break;
230510
- if (!candidates.length || file.size === null || file.size > budget || reads >= 10) continue;
230511
- reads += 1;
230512
- budget -= file.size;
230513
- let timer;
230514
- try {
230515
- const result = await Promise.race([
230516
- read(file.path, file.size),
230517
- new Promise((resolve10) => {
230518
- timer = setTimeout(() => resolve10(null), Math.max(0, deadline - Date.now()));
230519
- })
230520
- ]);
230521
- if (!result?.ok) continue;
230522
- const bytes2 = Buffer.from(result.contentBase64, "base64");
230523
- if (bytes2.length !== file.size) continue;
230524
- const hash2 = (0, import_node_crypto52.createHash)("sha256").update(bytes2).digest("hex");
230525
- if (candidates.some((attachment) => attachment.blob_ref === hash2)) copies.add(file.path);
230526
- } catch {
230527
- } finally {
230528
- if (timer) clearTimeout(timer);
230529
- }
230530
- }
230531
- return copies;
230532
- }
230533
- var import_node_crypto52, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS, WORKDIR_TREE_MAX_ENTRIES;
230534
- var init_files_view = __esm({
230535
- "../server/src/domains/chat-sessions/files-view.ts"() {
230536
- "use strict";
230537
- import_node_crypto52 = require("node:crypto");
230538
- init_chat_attachment_files();
230539
- init_src();
230540
- WORKDIR_WALK_MAX_DEPTH = 3;
230541
- WORKDIR_WALK_MAX_ENTRIES = 200;
230542
- WORKDIR_WALK_MAX_LIST_CALLS = 20;
230543
- WORKDIR_WALK_BUDGET_MS = 1e4;
230544
- WORKDIR_TREE_MAX_ENTRIES = 2e3;
230545
- }
230546
- });
230547
-
230548
- // ../server/src/domains/projects/chat-files.ts
230549
- function chatFolderTitle(session) {
230550
- const title = session.title?.trim();
230551
- return title ? title : "\u672A\u547D\u540D\u5BF9\u8BDD";
230552
- }
230553
- function outputDisplayName(path41) {
230554
- return path41.startsWith("outputs/") ? path41.slice("outputs/".length) : path41;
230555
- }
230556
- async function collectProjectChatFiles(chat, projectId2, meta, options = {}) {
230557
- const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
230558
- const [attachmentMessages, outputSessions] = await Promise.all([
230559
- chat.listAttachmentMessages ? chat.listAttachmentMessages(scope) : Promise.resolve([]),
230560
- chat.listOutputCopies ? chat.listOutputCopies(scope) : Promise.resolve([])
230561
- ]);
230562
- const sessionIds = [.../* @__PURE__ */ new Set([
230563
- ...attachmentMessages.map((m2) => m2.sessionId),
230564
- ...outputSessions.map((s2) => s2.sessionId)
230565
- ])];
230566
- const sessions = /* @__PURE__ */ new Map();
230567
- await Promise.all(sessionIds.map(async (id) => {
230568
- const session = await chat.getSession(id);
230569
- if (!session) return;
230570
- if ((session.projectId ?? null) !== scope) return;
230571
- if (session.visibility === "internal" || session.analyzedRunId) return;
230572
- if (options.isDelegationChild && await options.isDelegationChild(id).catch(() => false)) return;
230573
- sessions.set(id, session);
230574
- }));
230575
- const out = [];
230576
- const base = (session) => ({
230577
- projectId: projectId2,
230578
- chatSessionId: session.id,
230579
- chatTitle: chatFolderTitle(session),
230580
- chatOwnerId: session.humanActorId
230581
- });
230582
- const bySession = /* @__PURE__ */ new Map();
230583
- for (const message of attachmentMessages) {
230584
- if (!sessions.has(message.sessionId)) continue;
230585
- const list2 = bySession.get(message.sessionId) ?? [];
230586
- list2.push(message);
230587
- bySession.set(message.sessionId, list2);
230588
- }
230589
- for (const [sessionId, messages] of bySession) {
230590
- const session = sessions.get(sessionId);
230591
- const rows = await collectMessageFiles(messages.map((m2) => ({ ...m2, id: m2.messageId })), { meta });
230592
- for (const row of mergeChatSessionFiles(rows, [])) {
230593
- if (row.blob_ref === null) continue;
230594
- out.push({
230595
- ...base(session),
230596
- fileId: `chat:${sessionId}:${row.id}`,
230597
- name: row.name,
230598
- blobId: row.blob_ref,
230599
- ...row.content_type ? { contentType: row.content_type } : {},
230600
- size: row.size,
230601
- uploadedBy: session.humanActorId,
230602
- uploadedAt: row.updated_at,
230603
- chatSource: "attachment"
230604
- });
230605
- }
230606
- }
230607
- for (const record8 of outputSessions) {
230608
- const session = sessions.get(record8.sessionId);
230609
- if (!session) continue;
230610
- const seen = new Set(out.filter((file) => file.chatSessionId === session.id).map((file) => `${file.name}\0${file.blobId}`));
230611
- const copies = record8.copies ?? {};
230612
- for (const path41 of Object.keys(copies).sort((a, b2) => a.localeCompare(b2))) {
230613
- const copy = copies[path41];
230614
- if (!copy?.blobRef) continue;
230615
- if (seen.has(`${copy.name}\0${copy.blobRef}`)) continue;
230616
- out.push({
230617
- ...base(session),
230618
- fileId: `chat:${session.id}:out:${copy.path}`,
230619
- name: outputDisplayName(copy.path),
230620
- blobId: copy.blobRef,
230621
- ...copy.contentType ? { contentType: copy.contentType } : {},
230622
- size: copy.size,
230623
- uploadedBy: session.aiActorId,
230624
- uploadedAt: Number.isNaN(Date.parse(copy.mtime)) ? copy.copiedAt : new Date(copy.mtime).toISOString(),
230625
- chatSource: "output"
230626
- });
230627
- }
230628
- }
230629
- return out;
230630
- }
230631
- var init_chat_files2 = __esm({
230632
- "../server/src/domains/projects/chat-files.ts"() {
230633
- "use strict";
230634
- init_files_view();
230635
- init_uncategorized_project();
230636
- }
230637
- });
230638
-
230639
230923
  // ../server/src/domains/projects/index.ts
230640
230924
  function createProjectsDomain(opts) {
230641
230925
  const build = (companyId, deps) => {
@@ -230694,7 +230978,9 @@ function createProjectsDomain(opts) {
230694
230978
  service,
230695
230979
  ...opts.resolveCompany ? { resolveService } : {},
230696
230980
  ...opts.defaultBranchResolver ? { defaultBranchResolver: opts.defaultBranchResolver } : {},
230697
- ...opts.resolveMemberRole ? { resolveMemberRole: opts.resolveMemberRole } : {}
230981
+ ...opts.resolveMemberRole ? { resolveMemberRole: opts.resolveMemberRole } : {},
230982
+ ...opts.chatSessionsFor ? { chatSessionsFor: opts.chatSessionsFor } : {},
230983
+ ...opts.delegationChildIds ? { delegationChildIds: opts.delegationChildIds } : {}
230698
230984
  }),
230699
230985
  ...opts.resolveCompany ? { resolveService } : {}
230700
230986
  };
@@ -233521,21 +233807,21 @@ function nodesDomain(deps) {
233521
233807
  return { status: 200, body: { override } };
233522
233808
  });
233523
233809
  router.get("/api/runtimes/:runtimeId/activity", async (req) => {
233524
- const found = await runtimeInScope(req);
233525
- if (found.denied) return found.denied;
233810
+ const found2 = await runtimeInScope(req);
233811
+ if (found2.denied) return found2.denied;
233526
233812
  const reader = await deps.runtimeRuns?.(req.auth.companyId);
233527
- const activity = reader ? await reader.runtimeRunActivity(found.runtimeId) : null;
233813
+ const activity = reader ? await reader.runtimeRunActivity(found2.runtimeId) : null;
233528
233814
  return { status: 200, body: { activity } };
233529
233815
  });
233530
233816
  router.get("/api/runtimes/:runtimeId/usage", async (req) => {
233531
- const found = await runtimeInScope(req);
233532
- if (found.denied) return found.denied;
233817
+ const found2 = await runtimeInScope(req);
233818
+ if (found2.denied) return found2.denied;
233533
233819
  const granularity = req.query.get("granularity") === "day" ? "day" : "hour";
233534
233820
  const window2 = parseRuntimeUsageWindow(req.query, granularity, Date.now());
233535
233821
  if (typeof window2 === "string") return { status: 400, body: { error: window2 } };
233536
233822
  const reader = await deps.runtimeRuns?.(req.auth.companyId);
233537
- const runs = reader ? await reader.listRuntimeRuns(found.runtimeId, window2) : null;
233538
- return { status: 200, body: { items: bucketRuntimeRuns(found.runtimeId, runs ?? [], granularity) } };
233823
+ const runs = reader ? await reader.listRuntimeRuns(found2.runtimeId, window2) : null;
233824
+ return { status: 200, body: { items: bucketRuntimeRuns(found2.runtimeId, runs ?? [], granularity) } };
233539
233825
  });
233540
233826
  router.get("/api/runtimes/:runtimeId/config", async (req) => {
233541
233827
  const runtimeId = req.params["runtimeId"];
@@ -233931,9 +234217,56 @@ var init_node_store = __esm({
233931
234217
  }
233932
234218
  });
233933
234219
 
234220
+ // ../server/src/domains/collab/task-notices.ts
234221
+ function taskNoticeRecipients(model, workorderId, snapshot, members, trigger) {
234222
+ const out = /* @__PURE__ */ new Set();
234223
+ for (const a of model.artifacts.values()) {
234224
+ if (a.workspace === workorderId && isHumanActorId(a.owner)) out.add(a.owner);
234225
+ }
234226
+ for (const r of snapshot?.requirements ?? []) if (isHumanActorId(r.reviewerActorId)) out.add(r.reviewerActorId);
234227
+ for (const r of snapshot?.reviews ?? []) if (isHumanActorId(r.reviewerActorId)) out.add(r.reviewerActorId);
234228
+ if (trigger) out.delete(trigger);
234229
+ return [...out].filter((id) => members.has(id)).sort();
234230
+ }
234231
+ function settleTrigger(events, accept) {
234232
+ const e = accept.event;
234233
+ if (e.by && isHumanActorId(e.by)) return e.by;
234234
+ const workId = e.workId;
234235
+ const before = events.filter((x2) => x2.seq <= accept.seq);
234236
+ for (let i = before.length - 1; i >= 0; i--) {
234237
+ const x2 = before[i].event;
234238
+ if (x2.kind === "review.response" && x2.targetWorkId === workId && x2.verdict === "approve" && x2.reviewerActorId && isHumanActorId(x2.reviewerActorId)) {
234239
+ return x2.reviewerActorId;
234240
+ }
234241
+ if (x2.kind === "work.conclude" && x2.workId === workId && isHumanActorId(before[i].actorId)) return before[i].actorId;
234242
+ }
234243
+ return void 0;
234244
+ }
234245
+ function isCreationHold(paused) {
234246
+ return paused.event.reason === WORKORDER_HOLD_AT_CREATE_REASON;
234247
+ }
234248
+ function endsCreationHold(pauses, resumed) {
234249
+ let last;
234250
+ for (const p2 of pauses) if (p2.seq < resumed.seq && p2.event.kind === "workorder.paused") last = p2;
234251
+ return last !== void 0 && isCreationHold(last);
234252
+ }
234253
+ var TASK_NOTICE_TITLE;
234254
+ var init_task_notices = __esm({
234255
+ "../server/src/domains/collab/task-notices.ts"() {
234256
+ "use strict";
234257
+ init_src();
234258
+ TASK_NOTICE_TITLE = {
234259
+ "workorder-settled": "\u4EFB\u52A1\u5DF2\u5B8C\u6210",
234260
+ "workorder-cancelled": "\u4EFB\u52A1\u5DF2\u53D6\u6D88",
234261
+ "workorder-paused": "\u4EFB\u52A1\u5DF2\u6682\u505C",
234262
+ "workorder-resumed": "\u4EFB\u52A1\u5DF2\u6062\u590D"
234263
+ };
234264
+ }
234265
+ });
234266
+
233934
234267
  // ../server/src/domains/collab/notification-ledger.ts
233935
234268
  function isLedgerItem(item) {
233936
- return item.actionRequired && LEDGER_KINDS.has(item.kind);
234269
+ return item.actionRequired && LEDGER_INBOX_KINDS.has(item.kind);
233937
234270
  }
233938
234271
  function generationOf(model, item) {
233939
234272
  if (item.kind === "rework") return item.revisionId;
@@ -233943,7 +234276,17 @@ function generationOf(model, item) {
233943
234276
  }
233944
234277
  return void 0;
233945
234278
  }
234279
+ function staffingRoleOf(model, item) {
234280
+ if (item.kind !== "needs-staffing" || !item.artifactId) return void 0;
234281
+ const owner = model.artifacts.get(item.artifactId)?.owner;
234282
+ return owner?.startsWith(PENDING_ROLE_PREFIX) ? owner.slice(PENDING_ROLE_PREFIX.length) : void 0;
234283
+ }
234284
+ function staffingRoleOfKey(sourceKey) {
234285
+ return STAFFING_ROLE_KEY.exec(sourceKey)?.[1];
234286
+ }
233946
234287
  function notificationSourceKey(model, item) {
234288
+ const role = staffingRoleOf(model, item);
234289
+ if (role && item.workspace) return `needs-staffing:${item.workspace}:role:${role}`;
233947
234290
  const base = inboxItemKey(item);
233948
234291
  if (!base) return void 0;
233949
234292
  const gen = generationOf(model, item);
@@ -233953,6 +234296,10 @@ function generationOfKey(sourceKey) {
233953
234296
  const at = sourceKey.lastIndexOf("@");
233954
234297
  return at >= 0 ? sourceKey.slice(at + 1) : void 0;
233955
234298
  }
234299
+ function workNumber(workId) {
234300
+ const m2 = /:(\d+)$/.exec(workId);
234301
+ return m2 ? Number(m2[1]) : void 0;
234302
+ }
233956
234303
  function nodeOfWork(workId) {
233957
234304
  if (!workId.startsWith("wk:")) return null;
233958
234305
  const body2 = workId.slice(3);
@@ -233981,38 +234328,41 @@ function classifyClosure(row, facts) {
233981
234328
  const o = facts.draftOutcome;
233982
234329
  return o.by === me ? handled2(me, o.approved ? "\u5DF2\u786E\u8BA4" : "\u5DF2\u9A73\u56DE", facts.now) : invalidated("handled-by-other", facts.now, o.by);
233983
234330
  }
233984
- const mine = resolvedByMe(row, evs, me, onNode);
233985
- if (mine) return mine;
234331
+ const evidence = [];
234332
+ const add = (f2) => {
234333
+ if (f2) evidence.push(f2);
234334
+ };
234335
+ add(resolvedByMe(row, evs, me, onNode));
233986
234336
  const sealCancel = evs.find((rec) => {
233987
234337
  const e = payload(rec, "plan.delete_node");
233988
234338
  return !!e && e.nodeId === node2 && e.sealReason === "cancelled";
233989
234339
  });
233990
- if (facts.workorderCancelled) return invalidated("workorder-cancelled", sealCancel ? at(sealCancel) : facts.now, sealCancel?.actorId);
233991
- if (sealCancel) return invalidated("node-cancelled", at(sealCancel), sealCancel.actorId);
234340
+ if (sealCancel) add({ rec: sealCancel, closure: invalidated(facts.workorderCancelled ? "workorder-cancelled" : "node-cancelled", at(sealCancel), sealCancel.actorId) });
233992
234341
  const deleted = evs.find((rec) => {
233993
234342
  const e = payload(rec, "plan.delete_node");
233994
- return !!e && e.nodeId === node2 && e.sealReason !== "accepted";
234343
+ return !!e && e.nodeId === node2 && e.sealReason !== "accepted" && e.sealReason !== "cancelled";
233995
234344
  });
233996
- if (deleted) return invalidated("node-cancelled", at(deleted), deleted.actorId);
233997
- const other = resolvedByOther(row, evs, me, onNode);
233998
- if (other) return other;
234345
+ if (deleted) add({ rec: deleted, closure: invalidated("node-cancelled", at(deleted), deleted.actorId) });
234346
+ add(resolvedByOther(row, evs, me, onNode));
233999
234347
  const gen = generationOfKey(row.sourceKey);
234000
234348
  if (gen) {
234349
+ const genNo = workNumber(gen);
234350
+ const isNewer = (workId) => workId !== gen && (genNo === void 0 || (workNumber(workId) ?? Number.POSITIVE_INFINITY) > genNo);
234001
234351
  const newer = evs.find((rec) => {
234002
234352
  const c = payload(rec, "work.create");
234003
- if (c && c.nodeId === node2 && c.workId !== gen) return true;
234353
+ if (c && c.nodeId === node2 && isNewer(c.workId)) return true;
234004
234354
  const s2 = payload(rec, "work.submit_output");
234005
- return !!s2 && nodeOfWork(s2.workId) === node2 && s2.workId !== gen;
234355
+ return !!s2 && nodeOfWork(s2.workId) === node2 && isNewer(s2.workId);
234006
234356
  });
234007
- if (newer) return invalidated("superseded", at(newer), newer.actorId);
234357
+ if (newer) add({ rec: newer, closure: invalidated("superseded", at(newer), newer.actorId) });
234008
234358
  }
234009
234359
  const reassign = evs.find((rec) => {
234010
234360
  const a = payload(rec, "plan.assign_actor");
234011
234361
  if (a && a.nodeId === node2 && a.assigneeActorId !== me && ownerKinds.has(row.kind)) return true;
234012
234362
  const r = payload(rec, "plan.update_review_requirements");
234013
- return !!r && r.nodeId === node2 && reviewKinds.has(row.kind);
234363
+ return !!r && r.nodeId === node2 && reviewKinds.has(row.kind) && !r.reviewers.some((x2) => x2.reviewerActorId === me);
234014
234364
  });
234015
- if (reassign) return invalidated("reassigned", at(reassign), reassign.actorId);
234365
+ if (reassign) add({ rec: reassign, closure: invalidated("reassigned", at(reassign), reassign.actorId) });
234016
234366
  const myReviewPrefix = gen ? `rv:${gen}:${me}:` : void 0;
234017
234367
  const withdrawn = evs.find((rec) => {
234018
234368
  const k2 = payload(rec, "review.kill") ?? payload(rec, "review.timeout");
@@ -234020,9 +234370,15 @@ function classifyClosure(row, facts) {
234020
234370
  if (item?.waitingRowId !== void 0 && k2.reviewId === item.waitingRowId) return true;
234021
234371
  return row.kind === "review-request" && !!myReviewPrefix && k2.reviewId.startsWith(myReviewPrefix);
234022
234372
  });
234023
- if (withdrawn) return invalidated("source-changed", at(withdrawn), withdrawn.actorId);
234373
+ if (withdrawn) add({ rec: withdrawn, closure: invalidated("source-changed", at(withdrawn), withdrawn.actorId) });
234374
+ const first = evidence.reduce((best, f2) => !best || f2.rec.seq < best.rec.seq ? f2 : best, null);
234375
+ if (first) return first.closure;
234376
+ if (facts.workorderCancelled) return invalidated("workorder-cancelled", facts.now);
234024
234377
  return null;
234025
234378
  }
234379
+ function staffingNodeMatches(row, nodeId) {
234380
+ return staffingRoleOfKey(row.sourceKey) !== void 0 || nodeId === row.nodeId;
234381
+ }
234026
234382
  function issueIdsOf(row) {
234027
234383
  const it = row.item;
234028
234384
  return new Set([it?.gap?.gapId, it?.escalationId, it?.annotationId].filter((x2) => !!x2));
@@ -234034,16 +234390,16 @@ function resolvedByMe(row, evs, me, onNode) {
234034
234390
  switch (row.kind) {
234035
234391
  case "review-request": {
234036
234392
  const v2 = payload(rec, "review.response");
234037
- if (v2 && v2.reviewerActorId === me && onNode(rec) && (!gen || v2.targetWorkId === gen)) return handled2(me, verdictWord(v2.verdict), rec.createdAt);
234393
+ if (v2 && v2.reviewerActorId === me && onNode(rec) && (!gen || v2.targetWorkId === gen)) return found(rec, handled2(me, verdictWord(v2.verdict), rec.createdAt));
234038
234394
  const acc = payload(rec, "work.accept");
234039
- if (acc && acc.by === me && gen && acc.workId === gen) return handled2(me, "\u5DF2\u9A8C\u6536", rec.createdAt);
234395
+ if (acc && acc.by === me && gen && acc.workId === gen) return found(rec, handled2(me, "\u5DF2\u9A8C\u6536", rec.createdAt));
234040
234396
  const rej = payload(rec, "work.reject");
234041
- if (rej && rej.by === me && gen && rej.workId === gen) return handled2(me, "\u5DF2\u6253\u56DE", rec.createdAt);
234397
+ if (rej && rej.by === me && gen && rej.workId === gen) return found(rec, handled2(me, "\u5DF2\u6253\u56DE", rec.createdAt));
234042
234398
  break;
234043
234399
  }
234044
234400
  case "review-waiting": {
234045
234401
  const v2 = payload(rec, "review.response");
234046
- if (v2 && v2.reviewerActorId === me && item?.waitingRowId && v2.reviewId === item.waitingRowId) return handled2(me, verdictWord(v2.verdict), rec.createdAt);
234402
+ if (v2 && v2.reviewerActorId === me && item?.waitingRowId && v2.reviewId === item.waitingRowId) return found(rec, handled2(me, verdictWord(v2.verdict), rec.createdAt));
234047
234403
  break;
234048
234404
  }
234049
234405
  case "work-waiting":
@@ -234051,24 +234407,24 @@ function resolvedByMe(row, evs, me, onNode) {
234051
234407
  case "rework":
234052
234408
  case "stale": {
234053
234409
  const s2 = payload(rec, "work.submit_output") ?? payload(rec, "work.conclude");
234054
- if (s2 && doerOf(rec) === me && onNode(rec) && !(row.kind === "rework" && s2.workId === gen)) return handled2(me, "\u5DF2\u63D0\u4EA4", rec.createdAt);
234410
+ if (s2 && doerOf(rec) === me && onNode(rec) && !(row.kind === "rework" && s2.workId === gen)) return found(rec, handled2(me, "\u5DF2\u63D0\u4EA4", rec.createdAt));
234055
234411
  break;
234056
234412
  }
234057
234413
  case "pushback": {
234058
234414
  const r = payload(rec, "issue.resolve");
234059
- if (r && r.by === me && issueIdsOf(row).has(r.issueId)) return handled2(me, "\u5DF2\u5904\u7406", rec.createdAt);
234415
+ if (r && r.by === me && issueIdsOf(row).has(r.issueId)) return found(rec, handled2(me, "\u5DF2\u5904\u7406", rec.createdAt));
234060
234416
  const s2 = payload(rec, "work.submit_output");
234061
- if (s2 && doerOf(rec) === me && onNode(rec)) return handled2(me, "\u5DF2\u63D0\u4EA4", rec.createdAt);
234417
+ if (s2 && doerOf(rec) === me && onNode(rec)) return found(rec, handled2(me, "\u5DF2\u63D0\u4EA4", rec.createdAt));
234062
234418
  break;
234063
234419
  }
234064
234420
  case "gap-escalation": {
234065
234421
  const r = payload(rec, "issue.resolve");
234066
- if (r && r.by === me && issueIdsOf(row).has(r.issueId)) return handled2(me, "\u5DF2\u89E3\u51B3", rec.createdAt);
234422
+ if (r && r.by === me && issueIdsOf(row).has(r.issueId)) return found(rec, handled2(me, "\u5DF2\u89E3\u51B3", rec.createdAt));
234067
234423
  break;
234068
234424
  }
234069
234425
  case "needs-staffing": {
234070
234426
  const a = payload(rec, "plan.assign_actor");
234071
- if (a && rec.actorId === me && a.nodeId === row.nodeId && a.assigneeActorId) return handled2(me, "\u5DF2\u6307\u6D3E", rec.createdAt);
234427
+ if (a && rec.actorId === me && staffingNodeMatches(row, a.nodeId) && a.assigneeActorId) return found(rec, handled2(me, "\u5DF2\u6307\u6D3E", rec.createdAt));
234072
234428
  break;
234073
234429
  }
234074
234430
  default:
@@ -234085,21 +234441,25 @@ function resolvedByOther(row, evs, me, onNode) {
234085
234441
  switch (row.kind) {
234086
234442
  case "review-request":
234087
234443
  case "review-waiting": {
234088
- const v2 = payload(rec, "review.response");
234089
- if (v2 && onNode(rec) && (!gen || v2.targetWorkId === gen)) return invalidated("handled-by-other", rec.createdAt, who);
234090
234444
  const acc = payload(rec, "work.accept") ?? payload(rec, "work.reject");
234091
- if (acc && gen && acc.workId === gen) return invalidated("handled-by-other", rec.createdAt, who);
234445
+ if (acc && gen && acc.workId === gen) {
234446
+ const decider = evs.filter((x2) => x2.seq <= rec.seq).reverse().find((x2) => {
234447
+ const vv = payload(x2, "review.response");
234448
+ return !!vv && vv.targetWorkId === gen && vv.reviewerActorId !== me;
234449
+ });
234450
+ return found(rec, invalidated("handled-by-other", rec.createdAt, decider ? doerOf(decider) : who));
234451
+ }
234092
234452
  break;
234093
234453
  }
234094
234454
  case "gap-escalation":
234095
234455
  case "pushback": {
234096
234456
  const r = payload(rec, "issue.resolve");
234097
- if (r && issueIdsOf(row).has(r.issueId)) return invalidated("handled-by-other", rec.createdAt, who);
234457
+ if (r && issueIdsOf(row).has(r.issueId)) return found(rec, invalidated("handled-by-other", rec.createdAt, who));
234098
234458
  break;
234099
234459
  }
234100
234460
  case "needs-staffing": {
234101
234461
  const a = payload(rec, "plan.assign_actor");
234102
- if (a && a.nodeId === row.nodeId && a.assigneeActorId) return invalidated("handled-by-other", rec.createdAt, rec.actorId);
234462
+ if (a && staffingNodeMatches(row, a.nodeId) && a.assigneeActorId) return found(rec, invalidated("handled-by-other", rec.createdAt, rec.actorId));
234103
234463
  break;
234104
234464
  }
234105
234465
  case "assigned":
@@ -234107,7 +234467,7 @@ function resolvedByOther(row, evs, me, onNode) {
234107
234467
  case "stale":
234108
234468
  case "work-waiting": {
234109
234469
  const acc = payload(rec, "work.accept");
234110
- if (acc && onNode(rec)) return invalidated("handled-by-other", rec.createdAt, who);
234470
+ if (acc && onNode(rec)) return found(rec, invalidated("handled-by-other", rec.createdAt, who));
234111
234471
  break;
234112
234472
  }
234113
234473
  default:
@@ -234119,24 +234479,13 @@ function resolvedByOther(row, evs, me, onNode) {
234119
234479
  function needsGrace(missingSinceMs, nowMs, graceMs) {
234120
234480
  return nowMs - missingSinceMs < graceMs;
234121
234481
  }
234122
- var LEDGER_KINDS, CLOSURE_EVENT_KINDS, payload, handled2, invalidated, ownerKinds, reviewKinds, verdictWord;
234482
+ var STAFFING_ROLE_KEY, CLOSURE_EVENT_KINDS, payload, handled2, invalidated, found, ownerKinds, reviewKinds, verdictWord;
234123
234483
  var init_notification_ledger = __esm({
234124
234484
  "../server/src/domains/collab/notification-ledger.ts"() {
234125
234485
  "use strict";
234126
234486
  init_src5();
234127
234487
  init_src();
234128
- LEDGER_KINDS = /* @__PURE__ */ new Set([
234129
- "review-request",
234130
- "rework",
234131
- "stale",
234132
- "pushback",
234133
- "assigned",
234134
- "needs-staffing",
234135
- "gap-escalation",
234136
- "change-confirm",
234137
- "review-waiting",
234138
- "work-waiting"
234139
- ]);
234488
+ STAFFING_ROLE_KEY = /^needs-staffing:.+:role:([^:@]+)$/;
234140
234489
  CLOSURE_EVENT_KINDS = [
234141
234490
  "review.response",
234142
234491
  "review.kill",
@@ -234155,6 +234504,7 @@ var init_notification_ledger = __esm({
234155
234504
  payload = (rec, kind) => rec.event.kind === kind ? rec.event : null;
234156
234505
  handled2 = (closedBy, result, at) => ({ status: "handled", closedAt: at, closedBy, result });
234157
234506
  invalidated = (invalidReason, at, closedBy) => ({ status: "invalidated", closedAt: at, invalidReason, ...closedBy ? { closedBy } : {} });
234507
+ found = (rec, closure) => ({ rec, closure });
234158
234508
  ownerKinds = /* @__PURE__ */ new Set(["assigned", "rework", "stale", "pushback", "work-waiting"]);
234159
234509
  reviewKinds = /* @__PURE__ */ new Set(["review-request", "review-waiting"]);
234160
234510
  verdictWord = (verdict) => verdict === "approve" ? "\u5BA1\u6838\u901A\u8FC7" : verdict === "request_changes" || verdict === "reject" ? "\u8981\u6C42\u4FEE\u6539" : "\u5DF2\u5BA1\u6838";
@@ -234218,15 +234568,18 @@ function displayOf(model, item, recipient, reviewOf) {
234218
234568
  function sameDisplay(row, d) {
234219
234569
  return row.title === d.title && row.workspaceTitle === d.workspaceTitle && row.actorId === d.actorId && row.cardId === d.cardId && JSON.stringify(row.item ?? null) === JSON.stringify(d.item ?? null);
234220
234570
  }
234221
- var DAY2, EMPTY, EVENT_WINDOW_SLACK_MS, CLOSURE_EVENT_LIMIT, DRAFT_OUTCOME_TTL_MS, SLOW_RECONCILE_MS, rowKey, NotificationLedgerWorker;
234571
+ var DAY2, EMPTY, EVENT_WINDOW_SLACK_MS, CLOSURE_EVENT_LIMIT, DRAFT_OUTCOME_TTL_MS, SLOW_RECONCILE_MS, TASK_NOTICE_FRESH_MS, TASK_NOTICE_EVENT_LIMIT, rowKey, NotificationLedgerWorker;
234222
234572
  var init_notification_reconciler = __esm({
234223
234573
  "../server/src/domains/collab/notification-reconciler.ts"() {
234224
234574
  "use strict";
234225
234575
  init_src5();
234576
+ init_src();
234226
234577
  init_inbox();
234227
234578
  init_waiting_inbox();
234228
234579
  init_node_state();
234229
234580
  init_review_activity();
234581
+ init_workorder_terminal();
234582
+ init_task_notices();
234230
234583
  init_notification_ledger();
234231
234584
  DAY2 = 24 * 36e5;
234232
234585
  EMPTY = { inserted: 0, refreshed: 0, closed: 0, pending: 0 };
@@ -234234,6 +234587,8 @@ var init_notification_reconciler = __esm({
234234
234587
  CLOSURE_EVENT_LIMIT = 400;
234235
234588
  DRAFT_OUTCOME_TTL_MS = 36e5;
234236
234589
  SLOW_RECONCILE_MS = 2e3;
234590
+ TASK_NOTICE_FRESH_MS = 15 * 6e4;
234591
+ TASK_NOTICE_EVENT_LIMIT = 500;
234237
234592
  rowKey = (recipient, sourceKey) => `${recipient}\0${sourceKey}`;
234238
234593
  NotificationLedgerWorker = class {
234239
234594
  constructor(deps) {
@@ -234256,7 +234611,7 @@ var init_notification_reconciler = __esm({
234256
234611
  state(companyId) {
234257
234612
  let st = this.states.get(companyId);
234258
234613
  if (!st) {
234259
- st = { running: false, rerun: false, lastRunAt: 0, snaps: /* @__PURE__ */ new Map(), lastSeenAt: /* @__PURE__ */ new Map(), missingSince: /* @__PURE__ */ new Map(), drafts: /* @__PURE__ */ new Map(), lastPurgeAt: 0, primed: false };
234614
+ st = { running: false, rerun: false, lastRunAt: 0, snaps: /* @__PURE__ */ new Map(), lastSeenAt: /* @__PURE__ */ new Map(), missingSince: /* @__PURE__ */ new Map(), drafts: /* @__PURE__ */ new Map(), lastPurgeAt: 0, primed: false, terminals: /* @__PURE__ */ new Map(), noticeSeq: 0 };
234260
234615
  this.states.set(companyId, st);
234261
234616
  }
234262
234617
  return st;
@@ -234375,6 +234730,14 @@ var init_notification_reconciler = __esm({
234375
234730
  const pending = await ctx.store.listPending();
234376
234731
  const byKey = new Map(pending.map((r) => [rowKey(r.recipient, r.sourceKey), r]));
234377
234732
  const markersOf = /* @__PURE__ */ new Map();
234733
+ const adopted = /* @__PURE__ */ new Set();
234734
+ const roleOfRow = (r) => {
234735
+ const fromKey = staffingRoleOfKey(r.sourceKey);
234736
+ if (fromKey) return fromKey;
234737
+ const owner = r.nodeId ? model.artifacts.get(r.nodeId)?.owner : void 0;
234738
+ return owner?.startsWith(PENDING_ROLE_PREFIX) ? owner.slice(PENDING_ROLE_PREFIX.length) : void 0;
234739
+ };
234740
+ const staffingOrphans = pending.filter((r) => r.kind === "needs-staffing" && !current.has(rowKey(r.recipient, r.sourceKey)));
234378
234741
  for (const [k2, c] of current) {
234379
234742
  const display = displayOf(model, c.item, c.recipient, reviewOf);
234380
234743
  const row = byKey.get(k2);
@@ -234387,6 +234750,15 @@ var init_notification_reconciler = __esm({
234387
234750
  }
234388
234751
  continue;
234389
234752
  }
234753
+ const role = staffingRoleOf(model, c.item);
234754
+ const orphan = role ? staffingOrphans.find((r) => !adopted.has(r.id) && r.recipient === c.recipient && r.workorderId === c.item.workspace && roleOfRow(r) === role) : void 0;
234755
+ if (orphan && await ctx.store.rekey(orphan.id, c.sourceKey, { ...display, ...c.item.artifactId ? { nodeId: c.item.artifactId } : {} }, nowIso)) {
234756
+ adopted.add(orphan.id);
234757
+ st.missingSince.delete(orphan.id);
234758
+ st.lastSeenAt.set(orphan.id, nowIso);
234759
+ stats.refreshed++;
234760
+ continue;
234761
+ }
234390
234762
  const readAt = st.primed ? void 0 : await this.alreadyReadAt(ctx, c.recipient, c.item, markersOf, nowIso);
234391
234763
  const sinceOk = Number.isFinite(Date.parse(c.item.since));
234392
234764
  const enteredAt = st.primed || !sinceOk ? nowIso : new Date(c.item.since).toISOString();
@@ -234409,7 +234781,7 @@ var init_notification_reconciler = __esm({
234409
234781
  const cancelledOf = /* @__PURE__ */ new Map();
234410
234782
  let waitingForGrace = false;
234411
234783
  for (const row of pending) {
234412
- if (current.has(rowKey(row.recipient, row.sourceKey))) continue;
234784
+ if (current.has(rowKey(row.recipient, row.sourceKey)) || adopted.has(row.id)) continue;
234413
234785
  const closure = await this.judge(st, ctx, model, row, nowMs, nowIso, eventsOf, cancelledOf);
234414
234786
  if (!closure) {
234415
234787
  waitingForGrace = true;
@@ -234420,6 +234792,7 @@ var init_notification_reconciler = __esm({
234420
234792
  st.lastSeenAt.delete(row.id);
234421
234793
  }
234422
234794
  stats.pending = current.size;
234795
+ stats.notices = await this.reconcileTaskNotices(st, ctx, model, nowMs, loadSnapshot2);
234423
234796
  if (nowMs - st.lastPurgeAt > (this.deps.purgeEveryMs ?? 6 * 36e5)) {
234424
234797
  st.lastPurgeAt = nowMs;
234425
234798
  const purged = await ctx.store.purgeBefore(new Date(nowMs - (this.deps.retentionMs ?? 90 * DAY2)).toISOString());
@@ -234458,6 +234831,74 @@ var init_notification_reconciler = __esm({
234458
234831
  if (needsGrace(since, nowMs, this.graceMs)) return null;
234459
234832
  return { status: "invalidated", closedAt: nowIso, invalidReason: "source-changed" };
234460
234833
  }
234834
+ /**
234835
+ * 任务告知(第 2 步 B)。完成 / 取消从当前状态现算(与结案播报、任务列表同一判据),暂停 / 恢复读引擎事件。
234836
+ *
234837
+ * **不补历史**:进程第一次对账时(上线当天、重启后)只发最近 {@link TASK_NOTICE_FRESH_MS} 内发生的变化——
234838
+ * 早就结束的任务不能在上线那一刻集体「任务已完成」一遍。之后每拍新看到的变化都发。
234839
+ * 来源键 = `<种类>:<工单>@<那件事的 seq>`:同一件事重启后再算一遍撞唯一约束即收手;任务重开后再完成是新的一件事。
234840
+ */
234841
+ async reconcileTaskNotices(st, ctx, model, nowMs, loadSnapshot2) {
234842
+ const store = ctx.engineStore;
234843
+ if (!store) return 0;
234844
+ const members = new Set(ctx.humans);
234845
+ const titleOf = workspaceTitleMap(model);
234846
+ const fresh = (iso7) => nowMs - Date.parse(iso7) <= TASK_NOTICE_FRESH_MS;
234847
+ let sent = 0;
234848
+ const emit = async (workorderId, kind, rec, trigger) => {
234849
+ const recipients = taskNoticeRecipients(model, workorderId, await loadSnapshot2(workorderId), members, trigger);
234850
+ const title = titleOf.get(workorderId);
234851
+ for (const recipient of recipients) {
234852
+ await ctx.store.insert({
234853
+ recipient,
234854
+ category: "task-notice",
234855
+ sourceKey: `${kind}:${workorderId}@${rec.seq}`,
234856
+ kind,
234857
+ workorderId,
234858
+ title: TASK_NOTICE_TITLE[kind],
234859
+ ...title ? { workspaceTitle: title } : {},
234860
+ ...trigger ? { actorId: trigger } : {},
234861
+ enteredAt: new Date(rec.createdAt).toISOString()
234862
+ });
234863
+ sent++;
234864
+ }
234865
+ };
234866
+ const pauses = await store.transaction((tx) => tx.listEvents(void 0, st.noticeSeq, TASK_NOTICE_EVENT_LIMIT, ["workorder.paused", "workorder.resumed"]));
234867
+ for (const rec of pauses) {
234868
+ st.noticeSeq = Math.max(st.noticeSeq, rec.seq);
234869
+ if (!st.primed && !fresh(rec.createdAt)) continue;
234870
+ const e = rec.event;
234871
+ const paused = e.kind === "workorder.paused";
234872
+ if (paused ? isCreationHold(rec) : endsCreationHold(
234873
+ await store.transaction((tx) => tx.listEvents(rec.workorderId, 0, TASK_NOTICE_EVENT_LIMIT, ["workorder.paused"])),
234874
+ rec
234875
+ )) continue;
234876
+ await emit(rec.workorderId, paused ? "workorder-paused" : "workorder-resumed", rec, e.by ?? rec.actorId);
234877
+ }
234878
+ const terminals = computeWorkorderTerminals(model);
234879
+ const now = /* @__PURE__ */ new Set();
234880
+ for (const t of terminals) {
234881
+ const kind = t.kind;
234882
+ now.add(t.workOrderId);
234883
+ if (st.terminals.get(t.workOrderId) === kind) continue;
234884
+ st.terminals.set(t.workOrderId, kind);
234885
+ if (!st.primed && !(t.terminalAt && fresh(t.terminalAt))) continue;
234886
+ const cancelled = kind === "workorder-cancelled";
234887
+ const evs = await store.transaction((tx) => tx.listEvents(
234888
+ t.workOrderId,
234889
+ 0,
234890
+ TASK_NOTICE_EVENT_LIMIT,
234891
+ cancelled ? ["plan.delete_node"] : ["work.accept", "review.response", "work.conclude"]
234892
+ ));
234893
+ const decisive = [...evs].reverse().find((rec) => cancelled ? rec.event.sealReason === "cancelled" : rec.event.kind === "work.accept");
234894
+ if (!decisive) continue;
234895
+ if (!st.primed && !fresh(decisive.createdAt)) continue;
234896
+ const trigger = cancelled ? decisive.actorId : settleTrigger(evs, decisive);
234897
+ await emit(t.workOrderId, kind, decisive, trigger && !isSystemActorId(trigger) ? trigger : void 0);
234898
+ }
234899
+ for (const wid of [...st.terminals.keys()]) if (!now.has(wid)) st.terminals.delete(wid);
234900
+ return sent;
234901
+ }
234461
234902
  /** 台账上线前已经看过的待办不重新亮红点:沿用 `/api/inbox` 的未读判据(基线 + 逐条水位)。 */
234462
234903
  async alreadyReadAt(ctx, recipient, item, cache, nowIso) {
234463
234904
  if (!ctx.markers) return void 0;
@@ -234822,7 +235263,7 @@ function makeSeededWorkorderCreator(deps) {
234822
235263
  }
234823
235264
  if (input.dispatch === false && artifactState) {
234824
235265
  await artifactState.setWorkspaceDispatchHold(workspace, true);
234825
- await kernel.pauseWorkorder?.(workspace, input.actor);
235266
+ await kernel.pauseWorkorder?.(workspace, input.actor, WORKORDER_HOLD_AT_CREATE_REASON);
234826
235267
  }
234827
235268
  return { workspace, rootArtifactId: briefId, spawned: planned.spawned };
234828
235269
  };
@@ -234847,6 +235288,7 @@ var init_create_seeded_workorder = __esm({
234847
235288
  "use strict";
234848
235289
  import_node_crypto59 = require("node:crypto");
234849
235290
  init_uncategorized_project();
235291
+ init_src();
234850
235292
  init_planner();
234851
235293
  enc2 = (s2) => new TextEncoder().encode(s2);
234852
235294
  }
@@ -235703,6 +236145,37 @@ var init_trace_health = __esm({
235703
236145
  }
235704
236146
  });
235705
236147
 
236148
+ // ../server/src/domains/trace/subagent-events.ts
236149
+ function subagentRunEventFromLive(event) {
236150
+ if (event.itemType !== "control") return null;
236151
+ const payload2 = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : void 0;
236152
+ if (payload2?.code !== SUBAGENT_TASK_CODE || typeof payload2.toolCallId !== "string") return null;
236153
+ const raw = payload2.task;
236154
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
236155
+ const task = raw;
236156
+ const cost = task.estimatedCostUsdMicros ?? estimateSubagentCostUsdMicros(task.usage);
236157
+ const enriched = cost !== void 0 ? { ...task, estimatedCostUsdMicros: cost } : task;
236158
+ const eventType = task.status === "running" ? "sub_agent.started" : "sub_agent.finished";
236159
+ return {
236160
+ eventType,
236161
+ stream: "tool",
236162
+ ...task.status === "failed" ? { level: "error" } : {},
236163
+ ...task.description ? { message: task.description } : {},
236164
+ payload: {
236165
+ kind: "subagent_task",
236166
+ raw: { code: SUBAGENT_TASK_CODE, toolCallId: payload2.toolCallId, task: enriched }
236167
+ },
236168
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
236169
+ };
236170
+ }
236171
+ var init_subagent_events = __esm({
236172
+ "../server/src/domains/trace/subagent-events.ts"() {
236173
+ "use strict";
236174
+ init_src();
236175
+ init_subagent_cost();
236176
+ }
236177
+ });
236178
+
235706
236179
  // ../server/src/domains/trace/index.ts
235707
236180
  function createTraceDomain(opts) {
235708
236181
  const service = new TraceService({ store: opts.store });
@@ -235735,6 +236208,7 @@ var init_trace2 = __esm({
235735
236208
  init_chat_parts();
235736
236209
  init_trace_health();
235737
236210
  init_resilient_appender();
236211
+ init_subagent_events();
235738
236212
  }
235739
236213
  });
235740
236214
 
@@ -237385,7 +237859,12 @@ async function buildChatProjectBundle(projectId2, projects, readBlob) {
237385
237859
  "",
237386
237860
  "\u751F\u6210 Brief \u548C\u534F\u4F5C\u56FE\u524D\uFF0C\u5148\u9605\u8BFB\u672C\u6587\u4EF6\u5217\u51FA\u7684\u516C\u5F00\u8F93\u5165\u3002\u4E0D\u8981\u5047\u8BBE Benchmark \u4ED3\u5E93\u3001\u9690\u85CF\u6D4B\u8BD5\u3001Oracle \u6216\u53C2\u8003\u7B54\u6848\u53EF\u89C1\u3002"
237387
237861
  ].join("\n");
237388
- return { files, binaryFiles, contextLines };
237862
+ return {
237863
+ files,
237864
+ binaryFiles,
237865
+ contextLines,
237866
+ project: { id: project.id, name: project.name, ...project.description ? { description: project.description } : {} }
237867
+ };
237389
237868
  }
237390
237869
  var textFile, safeProjectPath;
237391
237870
  var init_project_bundle = __esm({
@@ -238095,7 +238574,7 @@ function createChatSessionsDomain(opts) {
238095
238574
  const outcome = await bridge.read(
238096
238575
  loc.nodeId,
238097
238576
  { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: req.query?.get("path") ?? "" },
238098
- WORKDIR_READ_MAX_BYTES
238577
+ FILE_CONTENT_MAX_BYTES
238099
238578
  );
238100
238579
  if (outcome.ok) {
238101
238580
  const buf = Buffer.from(outcome.contentBase64, "base64");
@@ -238223,8 +238702,8 @@ function createChatSessionsDomain(opts) {
238223
238702
  });
238224
238703
  };
238225
238704
  }
238226
- var import_node_crypto61, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS, WORKDIR_READ_MAX_BYTES;
238227
- var init_chat_sessions = __esm({
238705
+ var import_node_crypto61, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS;
238706
+ var init_chat_sessions2 = __esm({
238228
238707
  "../server/src/domains/chat-sessions/index.ts"() {
238229
238708
  "use strict";
238230
238709
  import_node_crypto61 = require("node:crypto");
@@ -238247,7 +238726,6 @@ var init_chat_sessions = __esm({
238247
238726
  init_project_bundle();
238248
238727
  DEFAULT_SNAPSHOT_ITEMS = 400;
238249
238728
  MAX_SNAPSHOT_ITEMS = 1e3;
238250
- WORKDIR_READ_MAX_BYTES = 2 * 1024 * 1024;
238251
238729
  }
238252
238730
  });
238253
238731
 
@@ -241140,11 +241618,11 @@ function automationsDomain(opts) {
241140
241618
  });
241141
241619
  router.get("/api/automations/:id/runs/:runId", async (req) => {
241142
241620
  const automation = await mustGet(req);
241143
- const found = await service.getRun(req.params.runId);
241144
- if (!found || found.automationId !== automation.id) {
241621
+ const found2 = await service.getRun(req.params.runId);
241622
+ if (!found2 || found2.automationId !== automation.id) {
241145
241623
  throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u6761\u8FD0\u884C\u8BB0\u5F55\uFF1A${req.params.runId}`);
241146
241624
  }
241147
- return { status: 200, body: found };
241625
+ return { status: 200, body: found2 };
241148
241626
  });
241149
241627
  router.get("/api/automations/:id/deliveries", async (req) => {
241150
241628
  const automation = await mustGet(req);
@@ -250504,6 +250982,20 @@ var init_postgres_chat_sessions = __esm({
250504
250982
  );
250505
250983
  return r.rows.map(rowToSessionWithQuality);
250506
250984
  }
250985
+ async listSessionsByProject(projectId2) {
250986
+ const r = await this.pool.query(
250987
+ `SELECT s.*
250988
+ FROM ${this.s}.chat_sessions s
250989
+ WHERE s.company_id = $2
250990
+ AND s.project_id IS NOT DISTINCT FROM $1
250991
+ AND s.analyzed_run_id IS NULL
250992
+ AND s.visibility IS DISTINCT FROM 'internal'
250993
+ AND COALESCE(s.workspace, '') = ''
250994
+ ORDER BY s.created_at DESC, s.id`,
250995
+ [projectId2, this.companyId]
250996
+ );
250997
+ return r.rows.map(rowToSession);
250998
+ }
250507
250999
  async listSessionsByAnalyzedRun(analyzedRunId) {
250508
251000
  const r = await this.pool.query(
250509
251001
  `SELECT s.*, lm.status AS last_assistant_status, lm.content AS last_assistant_content
@@ -260921,7 +261413,7 @@ var init_src11 = __esm({
260921
261413
  init_experiments2();
260922
261414
  init_trace2();
260923
261415
  init_execution_continuity2();
260924
- init_chat_sessions();
261416
+ init_chat_sessions2();
260925
261417
  init_dev_store();
260926
261418
  init_continuation3();
260927
261419
  init_knowledge2();
@@ -261326,6 +261818,54 @@ function buildChatTurnPrompts(input) {
261326
261818
  };
261327
261819
  }
261328
261820
 
261821
+ // ../cli/src/chat-task-md.ts
261822
+ var CHAT_PROJECT_SECTION_TITLE = "## \u5F53\u524D\u9879\u76EE\uFF08\u4EE5\u672C\u8F6E\u4E3A\u51C6\uFF09";
261823
+ var CHAT_PROJECT_INPUTS_POINTER = [
261824
+ "",
261825
+ "## \u5F53\u524D\u9879\u76EE\u516C\u5F00\u8F93\u5165",
261826
+ "",
261827
+ "\u751F\u6210 Brief \u524D\u5FC5\u987B\u5148\u8BFB\u53D6 `PROJECT_CONTEXT.md` \u53CA\u5176\u4E2D\u5217\u51FA\u7684 `project/` \u6587\u4EF6\uFF1B\u4ED3\u5E93\u7B49\u5916\u90E8\u4F4D\u7F6E\u4E5F\u8BB0\u5F55\u5728\u8BE5\u6587\u4EF6\u4E2D\u3002"
261828
+ ];
261829
+ function buildChatProjectSection(project, opts) {
261830
+ if (project.state === "unknown") return [];
261831
+ if (project.state === "none") {
261832
+ return opts.announceNone ? [CHAT_PROJECT_SECTION_TITLE, "", "\u672C\u5BF9\u8BDD\u672A\u5F52\u5C5E\u4EFB\u4F55\u9879\u76EE\u3002"] : [];
261833
+ }
261834
+ const description = project.description?.trim();
261835
+ const descriptionLines = !description ? ["\u9879\u76EE\u63CF\u8FF0: \uFF08\u672A\u586B\u5199\uFF09"] : description.includes("\n") ? ["\u9879\u76EE\u63CF\u8FF0:", description] : [`\u9879\u76EE\u63CF\u8FF0: ${description}`];
261836
+ return [
261837
+ CHAT_PROJECT_SECTION_TITLE,
261838
+ "",
261839
+ `project_id: ${project.id}`,
261840
+ `\u9879\u76EE\u540D\u79F0: ${project.name}`,
261841
+ ...descriptionLines
261842
+ ];
261843
+ }
261844
+ function buildChatTaskMd(p2) {
261845
+ return [
261846
+ ...p2.headerLines,
261847
+ // 项目段紧跟抬头:这一轮在哪个项目里,是读后面所有内容的前提。
261848
+ ...p2.projectSection.length ? ["", ...p2.projectSection] : [],
261849
+ ...p2.attachmentPaths.length ? ["", "## \u7528\u6237\u9644\u4EF6", "", "\u7528\u6237\u968F\u6D88\u606F\u9644\u5E26\u4E86\u4EE5\u4E0B\u6587\u4EF6\uFF08\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...p2.attachmentPaths.map((x2) => `- ${x2}`)] : [],
261850
+ // 委派子会话的主会话对话快照(提案 §6.1.4):一句固定说明 + 一份清单;没有这个文件时整段不拼。
261851
+ // 指针与文件由 delegationBundleAdditions **一处**给出,拆开迟早漂成「有指针没文件」。
261852
+ ...p2.parentConversationLines,
261853
+ // 委派两条路搬过来的文件(去程 `--file` / 回程专家产物):**不能复用「## 用户附件」的措辞**
261854
+ // (专家的产物会被介绍成用户附带的)。
261855
+ ...p2.workspaceLines,
261856
+ ...p2.projectInputsPointer,
261857
+ // 诊断 1be339c7:身份段紧贴在「## 用户消息」之前——让 agent 先知道「你在跟谁说话」,再读那条消息。
261858
+ ...p2.whoLines.length ? ["", ...p2.whoLines] : [],
261859
+ "",
261860
+ "## \u7528\u6237\u6D88\u606F",
261861
+ "",
261862
+ p2.message,
261863
+ ...p2.memoryLines,
261864
+ // 压在最末:这条要盖住紧邻其上的「收工前想一下」,否则记忆写入就落到正文之后(见常量注释)。
261865
+ ...p2.finalReplyLines
261866
+ ].join("\n");
261867
+ }
261868
+
261329
261869
  // ../cli/src/company-dispatch-bridge.ts
261330
261870
  var CompanyDispatchBridges = class {
261331
261871
  constructor(opts) {
@@ -261802,6 +262342,7 @@ init_src10();
261802
262342
  init_src11();
261803
262343
  init_src11();
261804
262344
  init_src11();
262345
+ init_src11();
261805
262346
  init_src9();
261806
262347
  init_src();
261807
262348
  init_trajectory();
@@ -262696,6 +263237,15 @@ async function startServe(opts) {
262696
263237
  const members = await listCompanyMembers(companyId);
262697
263238
  return members.find((m2) => m2.actorId === actorId)?.role;
262698
263239
  },
263240
+ // 项目「关联对话」列表与对话改挂项目(2026-09-14)。闭包在请求时才读 chatStoreFor /
263241
+ // delegationStoreFor,避开它们在下方声明的 TDZ(与 listChatFiles 同法)。
263242
+ chatSessionsFor: (companyId) => chatStoreFor(companyId),
263243
+ // 委派子会话不进项目(ADR 0555);一次台账查询批量判,不逐条点查。
263244
+ delegationChildIds: async (companyId, sessionIds) => {
263245
+ const ledger = await delegationStoreFor(companyId).catch(() => null);
263246
+ if (!ledger) return /* @__PURE__ */ new Set();
263247
+ return new Set(await ledger.listChildSessionIds(sessionIds));
263248
+ },
262699
263249
  resolveActorContext: (actorId) => buildActorContext(actors.service, actorId),
262700
263250
  defaultBranchResolver,
262701
263251
  // 展示:GET content-locations 给 git 仓补 resolvedDefaultBranch
@@ -263651,6 +264201,7 @@ async function startServe(opts) {
263651
264201
  };
263652
264202
  const readMarkerStoreFor = async (companyId) => pgPool && companyId ? (await openCompanyPlane(companyId)).readMarkers : readMarkerStore;
263653
264203
  const fileNotificationStore = pgPool ? void 0 : new MemoryNotificationStore(path29.join(opts.dir, "notifications.json"));
264204
+ const notificationLedgerEnabled = process.env["OASIS_NOTIFICATION_LEDGER"] !== "off";
263654
264205
  const notificationStoreFor = async (companyId) => pgPool ? (await openCompanyPlane(companyId ?? defaultCompanyId)).notifications : fileNotificationStore;
263655
264206
  const actorMemoryFor = async (companyId) => pgPool && companyId ? (await openCompanyPlane(companyId)).memory : actorMemoryService;
263656
264207
  const workorderDraftsFor = async (companyId) => pgPool && companyId ? (await openCompanyPlane(companyId)).drafts : workorderDrafts;
@@ -264395,7 +264946,7 @@ async function startServe(opts) {
264395
264946
  readMarkers: readMarkerStore,
264396
264947
  // bundle=`inbox`:水位按当前公司取(与上面 resolveChatSession 成对)。
264397
264948
  ...pgPool ? { resolveReadMarkers: readMarkerStoreFor } : {},
264398
- resolveNotificationLedger: notificationStoreFor,
264949
+ ...notificationLedgerEnabled ? { resolveNotificationLedger: notificationStoreFor } : {},
264399
264950
  escalationReminders: escalationDelivery.policy,
264400
264951
  defaultCompanyId,
264401
264952
  // 工单标签词表(组织级受控、旁账,与 playbook-overrides 同款侧存)。
@@ -265189,7 +265740,11 @@ async function startServe(opts) {
265189
265740
  }
265190
265741
  }
265191
265742
  }
265192
- const analyzedRunId = chatSessionId ? (await chatSessionStore.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
265743
+ const turnChatStore = chatSessionId ? await chatStoreFor(runCompanyId).catch((err) => {
265744
+ console.warn(`[chat] \u53D6\u516C\u53F8 ${runCompanyId} \u7684\u4F1A\u8BDD\u5B58\u50A8\u5931\u8D25\uFF08\u672C\u8F6E\u6309\u65E0\u4F1A\u8BDD\u8BB0\u5F55\u5904\u7406\uFF09: ${String(err)}`);
265745
+ return null;
265746
+ }) : null;
265747
+ const analyzedRunId = chatSessionId ? (await turnChatStore?.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
265193
265748
  const actorConfigVersion = await actorService.latestConfig(actorId).then((c) => c?.version ?? null).catch(() => null);
265194
265749
  const candidateTraceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
265195
265750
  const traceCreateInput = (target, startedAt) => ({
@@ -265243,7 +265798,7 @@ async function startServe(opts) {
265243
265798
  requireNodeConnectors();
265244
265799
  }
265245
265800
  }
265246
- const priorChatSession = chatSessionId ? await chatSessionStore?.getSession(chatSessionId).catch(() => null) : null;
265801
+ const priorChatSession = chatSessionId ? await turnChatStore?.getSession(chatSessionId).catch(() => null) : null;
265247
265802
  const freshChatWorkdir = chatSessionId ? chatNodeChanged(
265248
265803
  { nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
265249
265804
  { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
@@ -265301,17 +265856,19 @@ async function startServe(opts) {
265301
265856
  const attachmentFiles = { ...preparedAttachments.files };
265302
265857
  const attachmentBinary = { ...preparedAttachments.binaryFiles };
265303
265858
  const attachmentPaths = [...preparedAttachments.paths];
265304
- let projectContextLines = [];
265859
+ let turnProject = priorChatSession ? { state: "none" } : { state: "unknown" };
265305
265860
  if (priorChatSession?.projectId) {
265306
- const projectBundle = await buildChatProjectBundle(
265307
- priorChatSession.projectId,
265308
- projectStateStore,
265309
- (blobId) => assets.get(blobId)
265310
- ).catch(() => null);
265861
+ const projectId2 = priorChatSession.projectId;
265862
+ const projectBundle = await projectStateFor(runCompanyId).then((projects) => buildChatProjectBundle(projectId2, projects, (blobId) => assets.get(blobId))).catch((err) => {
265863
+ console.warn(`[chat] \u8BFB\u9879\u76EE ${projectId2} \u5931\u8D25\uFF08\u672C\u8F6E\u4E0D\u5199\u9879\u76EE\u6BB5\uFF09: ${String(err)}`);
265864
+ return void 0;
265865
+ });
265311
265866
  if (projectBundle) {
265312
265867
  Object.assign(attachmentFiles, projectBundle.files);
265313
265868
  Object.assign(attachmentBinary, projectBundle.binaryFiles);
265314
- projectContextLines = projectBundle.contextLines;
265869
+ turnProject = { state: "bound", ...projectBundle.project };
265870
+ } else if (projectBundle === void 0) {
265871
+ turnProject = { state: "unknown" };
265315
265872
  }
265316
265873
  }
265317
265874
  const sessionLine = chatSessionId ? `chat_session_id: ${chatSessionId}` : `runtime_session_id: ${runtimeSessionId}`;
@@ -265334,29 +265891,21 @@ async function startServe(opts) {
265334
265891
  const chatMemoryLines = chatMemorySvc ? await chatMemorySvc.list({ actorId }, { scope: "all", keywords: ngramQueryTerms([message]), limit: MEMORY_INJECT_MAX, offset: 0 }).then((out) => [...memoryIndexLines(out.items), ...memoryHowToLines()]).catch(() => []) : [];
265335
265892
  const parentConversationBundle = delegationBundleAdditions(parentConversationSnapshot);
265336
265893
  const files = {
265337
- "TASK.md": [
265338
- ...taskHeader,
265339
- ...attachmentPaths.length ? ["", "## \u7528\u6237\u9644\u4EF6", "", "\u7528\u6237\u968F\u6D88\u606F\u9644\u5E26\u4E86\u4EE5\u4E0B\u6587\u4EF6\uFF08\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...attachmentPaths.map((p2) => `- ${p2}`)] : [],
265340
- // 委派子会话的主会话对话快照(提案 §6.1.4)。形状照上面「用户附件」那一块:
265341
- // 一句固定说明 + 一份清单;**没有这个文件时整段不拼**(同一个条件块写法)——
265342
- // 主会话没有可转储的历史时就不该提它,专家不得假设它存在。
265343
- // 指针与文件由 delegationBundleAdditions **一处**给出:拆成两个 if 迟早会漂成
265344
- // 「有指针没文件」(专家照着去读一个不存在的路径,然后开始猜)。
265345
- ...parentConversationBundle.taskLines,
265346
- // 委派两条路搬过来的文件(去程 `--file` / 回程专家产物):清单由调用方给全,
265347
- // **回程不能复用上面「## 用户附件」的措辞**(专家的产物会被介绍成用户附带的)。
265348
- ...workspaceBundle?.taskLines ?? [],
265349
- ...projectContextLines.length ? ["", "## \u5F53\u524D\u9879\u76EE\u516C\u5F00\u8F93\u5165", "", "\u751F\u6210 Brief \u524D\u5FC5\u987B\u5148\u8BFB\u53D6 `PROJECT_CONTEXT.md` \u53CA\u5176\u4E2D\u5217\u51FA\u7684 `project/` \u6587\u4EF6\uFF1B\u4ED3\u5E93\u7B49\u5916\u90E8\u4F4D\u7F6E\u4E5F\u8BB0\u5F55\u5728\u8BE5\u6587\u4EF6\u4E2D\u3002"] : [],
265350
- // 诊断 1be339c7:身份段紧贴在「## 用户消息」之前——让 agent 先知道「你在跟谁说话」,再读那条消息。
265351
- ...whoLines.length ? ["", ...whoLines] : [],
265352
- "",
265353
- "## \u7528\u6237\u6D88\u606F",
265354
- "",
265894
+ "TASK.md": buildChatTaskMd({
265895
+ headerLines: taskHeader,
265896
+ // 「未归属」只对普通对话说:挂在工单上的对话(协调者 / 上报讨论房)项目看工单绑定,
265897
+ // 委派子会话建出来本就不带项目;知识库单轮受限生成连抬头都是另一套。
265898
+ projectSection: isolatedKnowledgeTurn ? [] : buildChatProjectSection(turnProject, { announceNone: !workspace && !delegatedChildTurn }),
265899
+ attachmentPaths,
265900
+ parentConversationLines: parentConversationBundle.taskLines,
265901
+ workspaceLines: workspaceBundle?.taskLines ?? [],
265902
+ // 项目公开输入指针:文字、位置、出现条件(查到了项目)都与改动前一致。
265903
+ projectInputsPointer: turnProject.state === "bound" ? CHAT_PROJECT_INPUTS_POINTER : [],
265904
+ whoLines,
265355
265905
  message,
265356
- ...chatMemoryLines,
265357
- // 压在最末:这条要盖住紧邻其上的「收工前想一下」,否则记忆写入就落到正文之后(见常量注释)。
265358
- ...isolatedKnowledgeTurn ? [] : chatFinalReplyRule({ memoryEnabled: chatMemoryEnabled })
265359
- ].join("\n")
265906
+ memoryLines: chatMemoryLines,
265907
+ finalReplyLines: isolatedKnowledgeTurn ? [] : chatFinalReplyRule({ memoryEnabled: chatMemoryEnabled })
265908
+ })
265360
265909
  };
265361
265910
  Object.assign(files, attachmentFiles);
265362
265911
  Object.assign(files, parentConversationBundle.files);
@@ -265472,6 +266021,8 @@ async function startServe(opts) {
265472
266021
  handle.onLiveEvent?.((event) => {
265473
266022
  if (liveEventCbs.length === 0) bufferedLiveEvents.push(event);
265474
266023
  for (const cb of liveEventCbs) cb(event);
266024
+ const subagentEvent = subagentRunEventFromLive(event);
266025
+ if (subagentEvent) enqueueTrace(() => traceAppender.append([subagentEvent]).then(() => void 0));
265475
266026
  });
265476
266027
  let capturedNativeSessionId;
265477
266028
  const done = new Promise((resolve10, reject) => {
@@ -265498,6 +266049,7 @@ async function startServe(opts) {
265498
266049
  durationMs: durationMs3,
265499
266050
  exitCode: info.code,
265500
266051
  exitReason: info.reason ?? null,
266052
+ ...!isHealthySessionExit(info) && info.reason !== "cancelled" ? { errorCode: info.reason ?? "error", errorMessage: formatExitError(info) } : {},
265501
266053
  transcriptRef: info.transcriptRef ?? null,
265502
266054
  usage,
265503
266055
  effectiveModel,
@@ -267202,28 +267754,28 @@ async function startServe(opts) {
267202
267754
  const isDefault = cid === defaultCompanyId;
267203
267755
  const cacheKey = `${cid}::${workspace}`;
267204
267756
  if (initiatorCache.has(cacheKey)) return initiatorCache.get(cacheKey);
267205
- let found = null;
267757
+ let found2 = null;
267206
267758
  const kernelToRead = engine2?.kernel ?? newEngine.kernel;
267207
267759
  const store = kernelToRead.getStore?.();
267208
267760
  if (store) {
267209
267761
  try {
267210
267762
  const events = await store.transaction((tx) => tx.listEvents(workspace));
267211
267763
  const spawn10 = events.find((e) => e.event.kind === "plan.changed");
267212
- found = spawn10?.actorId ?? null;
267764
+ found2 = spawn10?.actorId ?? null;
267213
267765
  } catch {
267214
267766
  }
267215
267767
  }
267216
- if (found === null && isDefault) {
267768
+ if (found2 === null && isDefault) {
267217
267769
  for await (const entry of oplog.readAll()) {
267218
267770
  const op = entry.op;
267219
267771
  if (op.kind === "spawn_artifact" && op.payload.workspace === workspace) {
267220
- found = op.actor;
267772
+ found2 = op.actor;
267221
267773
  break;
267222
267774
  }
267223
267775
  }
267224
267776
  }
267225
- initiatorCache.set(cacheKey, found);
267226
- return found;
267777
+ initiatorCache.set(cacheKey, found2);
267778
+ return found2;
267227
267779
  };
267228
267780
  const leadOf = async (actor, registry2) => {
267229
267781
  const reg = registry2 ?? registryStore;
@@ -267407,7 +267959,7 @@ async function startServe(opts) {
267407
267959
  },
267408
267960
  log: (message) => console.warn(message)
267409
267961
  });
267410
- if (process.env["OASIS_NOTIFICATION_LEDGER"] !== "off") {
267962
+ if (notificationLedgerEnabled) {
267411
267963
  const ledgerPeople = /* @__PURE__ */ new Map();
267412
267964
  const worker = new NotificationLedgerWorker({
267413
267965
  listCompanies: async () => engineRouter.liveEntries().map((e) => e.companyId),
@@ -268731,7 +269283,7 @@ var import_node_fs20 = __toESM(require("node:fs"), 1);
268731
269283
  var import_node_path26 = __toESM(require("node:path"), 1);
268732
269284
  init_src6();
268733
269285
  init_src();
268734
- var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
269286
+ var WORKDIR_READ_MAX_BYTES = 2 * 1024 * 1024;
268735
269287
  var WORKDIR_LIST_MAX_ENTRIES = 500;
268736
269288
  var WORKDIR_WALK_MAX_ENTRIES2 = 1e4;
268737
269289
  var WORKDIR_WALK_BUDGET_MS2 = 1e4;
@@ -268909,7 +269461,7 @@ async function verifyOpenedFd(fh, base, fallback) {
268909
269461
  return { anchor: `/proc/self/fd/${fh.fd}` };
268910
269462
  }
268911
269463
  async function handleWorkdirRead(req) {
268912
- const maxBytes = req.maxBytes ?? WORKDIR_READ_MAX_BYTES2;
269464
+ const maxBytes = req.maxBytes ?? WORKDIR_READ_MAX_BYTES;
268913
269465
  try {
268914
269466
  const resolved = await resolveWithinWorkdir(req);
268915
269467
  if (!resolved.ok) return resolved;
@@ -275308,20 +275860,20 @@ function instancePaths(name) {
275308
275860
  };
275309
275861
  }
275310
275862
  function listInstances() {
275311
- const found = [];
275312
- if (fs43.existsSync(instancePaths(DEFAULT_INSTANCE).configFile)) found.push(DEFAULT_INSTANCE);
275863
+ const found2 = [];
275864
+ if (fs43.existsSync(instancePaths(DEFAULT_INSTANCE).configFile)) found2.push(DEFAULT_INSTANCE);
275313
275865
  let entries = [];
275314
275866
  try {
275315
275867
  entries = fs43.readdirSync(instancesRoot());
275316
275868
  } catch {
275317
- return found;
275869
+ return found2;
275318
275870
  }
275319
275871
  for (const name of entries.sort()) {
275320
275872
  if (name === DEFAULT_INSTANCE) continue;
275321
275873
  if (!isValidInstanceName(name)) continue;
275322
- if (fs43.existsSync(instancePaths(name).configFile)) found.push(name);
275874
+ if (fs43.existsSync(instancePaths(name).configFile)) found2.push(name);
275323
275875
  }
275324
- return found;
275876
+ return found2;
275325
275877
  }
275326
275878
  function referencedVersionLabels() {
275327
275879
  const labels = /* @__PURE__ */ new Set();
@@ -275480,7 +276032,7 @@ function shimScript() {
275480
276032
  }
275481
276033
 
275482
276034
  // src/index.ts
275483
- var PKG_VERSION = true ? "2.2.33" : "dev";
276035
+ var PKG_VERSION = true ? "2.2.34" : "dev";
275484
276036
  var LOCAL_BIN = localBin();
275485
276037
  var NPM_PREFIX = npmPrefix();
275486
276038
  var INSTANCE = DEFAULT_INSTANCE;