oasis_test_v2 2.2.33 → 2.2.35

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 +1279 -692
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -149,6 +149,9 @@ function normalizeGitRemote(remote) {
149
149
  function projectActorKind(actorId) {
150
150
  return actorId.startsWith("actor:human:") ? "human" : "agent";
151
151
  }
152
+ function projectIdFromUuid(uuid2) {
153
+ return `proj_${uuid2.replace(/-/g, "").slice(0, 8)}`;
154
+ }
152
155
  var init_project = __esm({
153
156
  "../contract/src/project.ts"() {
154
157
  "use strict";
@@ -370,10 +373,11 @@ function commentThreadSessionId(a, b2) {
370
373
  function reviewThreadSessionId(a, b2) {
371
374
  return `${REVIEW_THREAD_SESSION_PREFIX}${[a, b2].sort().join("|")}`;
372
375
  }
373
- var GAP_THREAD_SESSION_PREFIX, COMMENT_THREAD_SESSION_PREFIX, REVIEW_THREAD_SESSION_PREFIX;
376
+ var WORKORDER_HOLD_AT_CREATE_REASON, GAP_THREAD_SESSION_PREFIX, COMMENT_THREAD_SESSION_PREFIX, REVIEW_THREAD_SESSION_PREFIX;
374
377
  var init_collab_view = __esm({
375
378
  "../contract/src/collab-view.ts"() {
376
379
  "use strict";
380
+ WORKORDER_HOLD_AT_CREATE_REASON = "\u5EFA\u5355\u65F6\u9009\u62E9\u7A0D\u540E\u6D3E\u53D1";
377
381
  GAP_THREAD_SESSION_PREFIX = "gapline:";
378
382
  COMMENT_THREAD_SESSION_PREFIX = "commentline:";
379
383
  REVIEW_THREAD_SESSION_PREFIX = "reviewline:";
@@ -406,9 +410,22 @@ var init_inbox_read = __esm({
406
410
  });
407
411
 
408
412
  // ../contract/src/notifications.ts
413
+ var LEDGER_INBOX_KINDS;
409
414
  var init_notifications = __esm({
410
415
  "../contract/src/notifications.ts"() {
411
416
  "use strict";
417
+ LEDGER_INBOX_KINDS = /* @__PURE__ */ new Set([
418
+ "review-request",
419
+ "rework",
420
+ "stale",
421
+ "pushback",
422
+ "assigned",
423
+ "needs-staffing",
424
+ "gap-escalation",
425
+ "change-confirm",
426
+ "review-waiting",
427
+ "work-waiting"
428
+ ]);
412
429
  }
413
430
  });
414
431
 
@@ -1647,7 +1664,7 @@ var init_file_preview = __esm({
1647
1664
  "../contract/src/file-preview.ts"() {
1648
1665
  "use strict";
1649
1666
  OFFICE_PREVIEW_MAX_BYTES = 20 * 1024 * 1024;
1650
- FILE_CONTENT_MAX_BYTES = 50 * 1024 * 1024;
1667
+ FILE_CONTENT_MAX_BYTES = 200 * 1024 * 1024;
1651
1668
  }
1652
1669
  });
1653
1670
 
@@ -21293,14 +21310,17 @@ var init_kernel_bridge = __esm({
21293
21310
  });
21294
21311
  }
21295
21312
  }
21296
- /** 工单级暂停:workorder.paused handler 杀全部在途会话 + 挡新派发。 */
21297
- async pauseWorkorder(workorderId, actor) {
21313
+ /**
21314
+ * 工单级暂停:workorder.paused handler 杀全部在途会话 + 挡新派发。
21315
+ * `reason` 缺省「暂停派发」(人点暂停);建单即挂起的入口传 `WORKORDER_HOLD_AT_CREATE_REASON`。
21316
+ */
21317
+ async pauseWorkorder(workorderId, actor, reason = "\u6682\u505C\u6D3E\u53D1") {
21298
21318
  this.trackedWorkorders.add(workorderId);
21299
21319
  await this.commit({
21300
21320
  companyId: "",
21301
21321
  workorderId,
21302
21322
  actorId: actor,
21303
- event: { kind: "workorder.paused", workorderId, by: actor, reason: "\u6682\u505C\u6D3E\u53D1" }
21323
+ event: { kind: "workorder.paused", workorderId, by: actor, reason }
21304
21324
  });
21305
21325
  }
21306
21326
  /**
@@ -22033,12 +22053,12 @@ async function readArtifactFile(kernel, blobs, artifactId, path41, expectedRef,
22033
22053
  if (!entry) throw new ApiError(404, "NOT_FOUND", "\u5305\u5185\u6587\u4EF6\u4E0D\u5B58\u5728");
22034
22054
  hash2 = entry.hash;
22035
22055
  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");
22056
+ 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
22057
  } else if (output.contentKind !== "inline-blob") {
22038
22058
  throw new ApiError(404, "NOT_FOUND", "\u6B64\u4EA7\u7269\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u539F\u6587\u4EF6");
22039
22059
  }
22040
22060
  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");
22061
+ 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
22062
  try {
22043
22063
  return { bytes: await blobs.get(hash2), name, contentType: meta?.contentType };
22044
22064
  } catch {
@@ -22065,7 +22085,7 @@ async function serveFileRequest(req, res, url, engine) {
22065
22085
  url.searchParams.get("ref"),
22066
22086
  url.searchParams.get("revisionId")
22067
22087
  );
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");
22088
+ 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
22089
  res.writeHead(200, {
22070
22090
  "content-type": contentType ?? sniffContentType(bytes2) ?? "application/octet-stream",
22071
22091
  "content-length": String(bytes2.length),
@@ -25413,7 +25433,7 @@ async function runProtocolSession(job, cfg) {
25413
25433
  let appendChain = Promise.resolve();
25414
25434
  let eventSeq = 0;
25415
25435
  let runtimeSessionId;
25416
- let turnCompletePending = false;
25436
+ const pendingTurnMessages = [];
25417
25437
  const stderrTail = [];
25418
25438
  const finish = (info) => {
25419
25439
  if (exited) return;
@@ -25452,7 +25472,15 @@ async function runProtocolSession(job, cfg) {
25452
25472
  }
25453
25473
  });
25454
25474
  };
25475
+ const completeTurn = (message) => {
25476
+ if (exited || killReason) return;
25477
+ const result = cfg.turnResult(message, state);
25478
+ if (!result) return;
25479
+ finish(result);
25480
+ child.kill("SIGTERM");
25481
+ };
25455
25482
  const handle = (message) => {
25483
+ if (exited || killReason) return;
25456
25484
  const responseId = commandId(message.id);
25457
25485
  if (isResponse(message) && responseId !== void 0 && pending.has(responseId)) {
25458
25486
  const waiter = pending.get(responseId);
@@ -25468,13 +25496,15 @@ async function runProtocolSession(job, cfg) {
25468
25496
  const event = { kind: line.kind, payload: line.payload, seq: ++eventSeq, ts: (/* @__PURE__ */ new Date()).toISOString() };
25469
25497
  for (const cb of telemetryCbs) cb(event);
25470
25498
  }
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
- }
25499
+ if (!started) pendingTurnMessages.push(message);
25500
+ else completeTurn(message);
25501
+ };
25502
+ const consumeLine = (line) => {
25503
+ if (!line) return;
25504
+ transcript.write(line + "\n");
25505
+ try {
25506
+ handle(JSON.parse(line));
25507
+ } catch {
25478
25508
  }
25479
25509
  };
25480
25510
  let buffer = "";
@@ -25484,13 +25514,7 @@ async function runProtocolSession(job, cfg) {
25484
25514
  while (newline >= 0) {
25485
25515
  const line = buffer.slice(0, newline).replace(/\r$/, "");
25486
25516
  buffer = buffer.slice(newline + 1);
25487
- if (line) {
25488
- transcript.write(line + "\n");
25489
- try {
25490
- handle(JSON.parse(line));
25491
- } catch {
25492
- }
25493
- }
25517
+ consumeLine(line);
25494
25518
  newline = buffer.indexOf("\n");
25495
25519
  }
25496
25520
  });
@@ -25504,12 +25528,23 @@ async function runProtocolSession(job, cfg) {
25504
25528
  killReason ??= "timeout";
25505
25529
  child.kill("SIGKILL");
25506
25530
  }, job.limits.wallClockMs);
25507
- child.on("error", (error2) => finish({ code: null, reason: "error", transcriptRef, errorMessage: error2.message }));
25508
- child.on("exit", (code2) => {
25531
+ child.on("error", (error2) => {
25532
+ clearTimeout(timer);
25533
+ finish({ code: null, reason: "error", transcriptRef, errorMessage: error2.message });
25534
+ });
25535
+ child.on("close", (code2) => {
25509
25536
  clearTimeout(timer);
25537
+ consumeLine(buffer.trim());
25538
+ buffer = "";
25539
+ transcript.end();
25510
25540
  if (!exited) {
25511
25541
  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) } : {} });
25542
+ const info = {
25543
+ code: killReason ? null : code2,
25544
+ reason,
25545
+ ...reason === "error" && stderrTail.length ? { errorMessage: stderrTail.join("\n").slice(-4e3) } : {}
25546
+ };
25547
+ finish(!killReason && cfg.processExitInfo ? cfg.processExitInfo(info, state) : info);
25513
25548
  }
25514
25549
  });
25515
25550
  void (async () => {
@@ -25523,10 +25558,7 @@ async function runProtocolSession(job, cfg) {
25523
25558
  await cfg.start(send, job, dir, task, state);
25524
25559
  runtimeSessionId ??= cfg.runtimeSessionId?.(state);
25525
25560
  started = true;
25526
- if (turnCompletePending && !exited) {
25527
- finish({ code: 0, reason: "clean", transcriptRef, ...runtimeSessionId ? { runtimeSessionId } : {} });
25528
- child.kill("SIGTERM");
25529
- }
25561
+ for (const message of pendingTurnMessages.splice(0)) completeTurn(message);
25530
25562
  } catch (error2) {
25531
25563
  if (!exited) {
25532
25564
  const msg = error2 instanceof Error ? error2.message : String(error2);
@@ -25735,7 +25767,7 @@ function codexSessionsRoot() {
25735
25767
  }
25736
25768
  function listRecentRollouts(root, sinceMs) {
25737
25769
  const margin = sinceMs - 6e4;
25738
- const found = [];
25770
+ const found2 = [];
25739
25771
  const walk = (d) => {
25740
25772
  let ents;
25741
25773
  try {
@@ -25752,16 +25784,16 @@ function listRecentRollouts(root, sinceMs) {
25752
25784
  m2 = fs8.statSync(p2).mtimeMs;
25753
25785
  } catch {
25754
25786
  }
25755
- if (m2 >= margin) found.push({ f: p2, m: m2 });
25787
+ if (m2 >= margin) found2.push({ f: p2, m: m2 });
25756
25788
  }
25757
25789
  }
25758
25790
  };
25759
25791
  walk(root);
25760
- return found.sort((a, b2) => b2.m - a.m).map((x2) => x2.f);
25792
+ return found2.sort((a, b2) => b2.m - a.m).map((x2) => x2.f);
25761
25793
  }
25762
25794
  function findRolloutsBySessionId(root, sessionId) {
25763
25795
  const suffix = `-${sessionId}.jsonl`;
25764
- const found = [];
25796
+ const found2 = [];
25765
25797
  const walk = (d) => {
25766
25798
  let ents;
25767
25799
  try {
@@ -25772,11 +25804,11 @@ function findRolloutsBySessionId(root, sessionId) {
25772
25804
  for (const ent of ents) {
25773
25805
  const p2 = path10.join(d, ent.name);
25774
25806
  if (ent.isDirectory()) walk(p2);
25775
- else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(suffix)) found.push(p2);
25807
+ else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(suffix)) found2.push(p2);
25776
25808
  }
25777
25809
  };
25778
25810
  walk(root);
25779
- return found;
25811
+ return found2;
25780
25812
  }
25781
25813
  function telemetryFromRolloutLines(lines, sinceMs) {
25782
25814
  const usage = parseCodexTokenUsage(lines, sinceMs);
@@ -26036,6 +26068,15 @@ function normalizeCodexConsoleLine(line, state = createCodexNormalizeState()) {
26036
26068
  tool.output.push(line);
26037
26069
  return [];
26038
26070
  }
26071
+ function codexTurnErrorMessage(value2) {
26072
+ const error2 = record2(value2);
26073
+ const code2 = error2?.codexErrorInfo;
26074
+ const label = typeof code2 === "string" ? code2 : code2 ? JSON.stringify(code2) : "unknown";
26075
+ const message = typeof error2?.message === "string" && error2.message.trim() ? error2.message.trim() : "Codex turn failed without error details";
26076
+ const summary = code2 === "cyberPolicy" || code2 === "misalignmentPolicyViolation" ? `Codex \u5B89\u5168\u7B56\u7565\u62D2\u7EDD [${label}]\uFF1A${message}` : `Codex [${label}]: ${message}`;
26077
+ const detail = typeof error2?.additionalDetails === "string" ? error2.additionalDetails.trim() : "";
26078
+ return [summary, detail].filter(Boolean).join("\n").slice(0, 4e3);
26079
+ }
26039
26080
  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
26081
  var init_codex = __esm({
26041
26082
  "../adapters/src/codex/index.ts"() {
@@ -26119,6 +26160,7 @@ var init_codex = __esm({
26119
26160
  threadId: void 0,
26120
26161
  turnId: void 0,
26121
26162
  active: false,
26163
+ errorsByTurn: /* @__PURE__ */ new Map(),
26122
26164
  pendingInputs: /* @__PURE__ */ new Set(),
26123
26165
  codex: createCodexNormalizeState(),
26124
26166
  // 本次 run 的用量:`thread/tokenUsage/updated` 的 `last`(见该解析函数注释)按轮次记下,
@@ -26202,6 +26244,9 @@ ${task}` : task;
26202
26244
  }
26203
26245
  }
26204
26246
  if (method === "turn/completed") {
26247
+ const turn = record2(params?.turn);
26248
+ const failure = typeof turn?.id === "string" ? state.errorsByTurn.get(turn.id) : void 0;
26249
+ if (params?.threadId !== state.threadId || turn?.id !== state.turnId || turn?.status !== "completed" || turn.error || failure && !failure.willRetry) return [];
26205
26250
  return [{ ...base, itemId: "turn-end", itemType: "control", operation: "completed", payload: { code: "output_block_completed" } }];
26206
26251
  }
26207
26252
  return [];
@@ -26209,10 +26254,15 @@ ${task}` : task;
26209
26254
  normalize(message, state) {
26210
26255
  const params = message.params;
26211
26256
  const method = typeof message.method === "string" ? message.method : "";
26257
+ if (method === "error" && params?.threadId === state.threadId && typeof params?.turnId === "string") {
26258
+ state.errorsByTurn.set(params.turnId, { error: params.error, willRetry: params.willRetry === true });
26259
+ }
26212
26260
  if (method === "thread/started") {
26213
26261
  const thread = params?.thread;
26214
- if (typeof thread?.id === "string") state.threadId = thread.id;
26215
- if (typeof thread?.path === "string") state.rolloutPath = thread.path;
26262
+ if (typeof thread?.id === "string" && (!state.threadId || state.threadId === thread.id)) {
26263
+ state.threadId = thread.id;
26264
+ if (typeof thread.path === "string") state.rolloutPath = thread.path;
26265
+ }
26216
26266
  }
26217
26267
  if (method === "thread/tokenUsage/updated") {
26218
26268
  const turn = parseCodexAppServerTurnUsage(params);
@@ -26220,7 +26270,7 @@ ${task}` : task;
26220
26270
  }
26221
26271
  if (method === "turn/started") {
26222
26272
  const turn = params?.turn;
26223
- if (typeof turn?.id === "string") {
26273
+ if (params?.threadId === state.threadId && state.turnRequested && typeof turn?.id === "string" && (!state.turnId || state.turnId === turn.id)) {
26224
26274
  state.turnId = turn.id;
26225
26275
  state.active = true;
26226
26276
  if (state.turnRequested) state.runTurnIds.add(turn.id);
@@ -26241,10 +26291,29 @@ ${task}` : task;
26241
26291
  }
26242
26292
  return [];
26243
26293
  },
26244
- isTurnComplete(message, state) {
26245
- if (message.method !== "turn/completed" || !state.active) return false;
26294
+ turnResult(message, state) {
26295
+ if (message.method !== "turn/completed" || !state.active) return void 0;
26296
+ const params = record2(message.params);
26297
+ const turn = record2(params?.turn);
26298
+ if (params?.threadId !== state.threadId || !state.turnId || turn?.id !== state.turnId) return void 0;
26246
26299
  state.active = false;
26247
- return true;
26300
+ if (turn.status === "interrupted") return { code: null, reason: "cancelled" };
26301
+ const failure = state.errorsByTurn.get(state.turnId);
26302
+ const error2 = turn.error ?? (turn.status === "failed" || !failure?.willRetry ? failure?.error : void 0);
26303
+ if (turn.status === "completed" && !error2) return { code: 0, reason: "clean" };
26304
+ return { code: 1, reason: "error", errorMessage: codexTurnErrorMessage(error2) };
26305
+ },
26306
+ processExitInfo(info, state) {
26307
+ const error2 = state.turnId ? state.errorsByTurn.get(state.turnId)?.error : void 0;
26308
+ return {
26309
+ code: info.code === 0 ? 1 : info.code,
26310
+ reason: "error",
26311
+ errorMessage: [
26312
+ error2 ? codexTurnErrorMessage(error2) : void 0,
26313
+ info.errorMessage,
26314
+ "Codex app-server exited before reporting a terminal result"
26315
+ ].filter(Boolean).join("\n").slice(0, 4e3)
26316
+ };
26248
26317
  },
26249
26318
  runtimeSessionId: (state) => state.threadId,
26250
26319
  exitExtras: (state) => {
@@ -28304,8 +28373,8 @@ ${task}` : task;
28304
28373
  normalize(message) {
28305
28374
  return normalizePi(JSON.stringify(message));
28306
28375
  },
28307
- isTurnComplete(message) {
28308
- return message.type === "agent_settled";
28376
+ turnResult(message) {
28377
+ return message.type === "agent_settled" ? { code: 0, reason: "clean" } : void 0;
28309
28378
  },
28310
28379
  runtimeSessionId(state) {
28311
28380
  return state.sessionId;
@@ -30815,8 +30884,26 @@ function runBackgroundAssistantChatTurn(deps) {
30815
30884
  }
30816
30885
  };
30817
30886
  void Promise.resolve(deps.session.done).then(() => finalize("done").catch(() => void 0)).catch((err) => {
30887
+ const chatExit = err instanceof Error ? err.chatExit : void 0;
30888
+ const text5 = chatExit?.summary ?? (err instanceof Error ? err.message : String(err));
30889
+ const extra = {
30890
+ ...chatExit?.reason ? { reason: chatExit.reason } : {},
30891
+ ...chatExit?.detail ? { detail: chatExit.detail } : {}
30892
+ };
30893
+ const part = { type: "error", seq: ++localSeq, text: text5, ...extra };
30894
+ localParts.push(part);
30895
+ liveTurn?.emit(part);
30818
30896
  try {
30819
- itemLedger.recordError(err instanceof Error ? err.message : String(err));
30897
+ const outcome = itemLedger.recordError(text5, extra);
30898
+ liveTurn?.publishServerItem({
30899
+ itemId: outcome.itemId,
30900
+ itemType: "control",
30901
+ startedVersion: outcome.startedVersion,
30902
+ version: outcome.version,
30903
+ ord: outcome.ord,
30904
+ status: "failed",
30905
+ payload: { code: "error", note: text5, ...extra }
30906
+ });
30820
30907
  } catch {
30821
30908
  }
30822
30909
  return finalize("error").catch(() => void 0);
@@ -178838,14 +178925,14 @@ var require_Alias = __commonJS({
178838
178925
  if (ctx)
178839
178926
  ctx.aliasResolveCache = nodes;
178840
178927
  }
178841
- let found = void 0;
178928
+ let found2 = void 0;
178842
178929
  for (const node2 of nodes) {
178843
178930
  if (node2 === this)
178844
178931
  break;
178845
178932
  if (node2.anchor === this.source)
178846
- found = node2;
178933
+ found2 = node2;
178847
178934
  }
178848
- return found;
178935
+ return found2;
178849
178936
  }
178850
178937
  toJSON(_arg, ctx) {
178851
178938
  if (!ctx)
@@ -181970,7 +182057,7 @@ var require_resolve_props = __commonJS({
181970
182057
  let tag = null;
181971
182058
  let newlineAfterProp = null;
181972
182059
  let comma = null;
181973
- let found = null;
182060
+ let found2 = null;
181974
182061
  let start = null;
181975
182062
  for (const token of tokens) {
181976
182063
  if (reqSpace) {
@@ -182007,7 +182094,7 @@ var require_resolve_props = __commonJS({
182007
182094
  if (atNewline) {
182008
182095
  if (comment)
182009
182096
  comment += token.source;
182010
- else if (!found || indicator !== "seq-item-ind")
182097
+ else if (!found2 || indicator !== "seq-item-ind")
182011
182098
  spaceBefore = true;
182012
182099
  } else
182013
182100
  commentSep += token.source;
@@ -182041,9 +182128,9 @@ var require_resolve_props = __commonJS({
182041
182128
  case indicator:
182042
182129
  if (anchor || tag)
182043
182130
  onError(token, "BAD_PROP_ORDER", `Anchors and tags must be after the ${token.source} indicator`);
182044
- if (found)
182131
+ if (found2)
182045
182132
  onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.source} in ${flow3 ?? "collection"}`);
182046
- found = token;
182133
+ found2 = token;
182047
182134
  atNewline = indicator === "seq-item-ind" || indicator === "explicit-key-ind";
182048
182135
  hasSpace = false;
182049
182136
  break;
@@ -182072,7 +182159,7 @@ var require_resolve_props = __commonJS({
182072
182159
  onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation");
182073
182160
  return {
182074
182161
  comma,
182075
- found,
182162
+ found: found2,
182076
182163
  spaceBefore,
182077
182164
  comment,
182078
182165
  hasNewline,
@@ -201214,8 +201301,8 @@ async function resolveOutlineKnowledgeBinding(credentials, input) {
201214
201301
  const collections = (await api.listCollections()).filter(isWritableCollection);
201215
201302
  for (const collection of collections) {
201216
201303
  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);
201304
+ const found2 = documents.find((document4) => string5(document4["url"]) === pasted);
201305
+ if (found2) return bindingFromDocument(api, found2, collection, identity);
201219
201306
  }
201220
201307
  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
201308
  }
@@ -204172,10 +204259,10 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
204172
204259
  }
204173
204260
  let artifactId;
204174
204261
  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;
204262
+ const found2 = escalationById(kernel.model, escalationId);
204263
+ 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`);
204264
+ if (found2.resolved) throw new Error(`\u4E0A\u62A5 ${escalationId} \u5DF2\u7ECF\u5173\u95ED\uFF0C\u65E0\u9700\u518D\u5173\u3002`);
204265
+ artifactId = found2.artifactId;
204179
204266
  } else {
204180
204267
  const argArtifactId = optStr(args, "artifactId");
204181
204268
  if (argArtifactId === void 0) {
@@ -204457,7 +204544,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
204457
204544
  }
204458
204545
  if (args["dispatch"] === false) {
204459
204546
  await ctx.artifactState?.setWorkspaceDispatchHold(workspace, true);
204460
- await kernel.pauseWorkorder?.(workspace, actor);
204547
+ await kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
204461
204548
  }
204462
204549
  return {
204463
204550
  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`,
@@ -205650,10 +205737,10 @@ async function startOasisServer(opts) {
205650
205737
  ...fp(p2)
205651
205738
  }))
205652
205739
  ] : [];
205653
- const { randomUUID: randomUUID41 } = await import("node:crypto");
205740
+ const { randomUUID: randomUUID42 } = await import("node:crypto");
205654
205741
  try {
205655
205742
  await store.enqueuePendingMessage({
205656
- id: randomUUID41(),
205743
+ id: randomUUID42(),
205657
205744
  chatSessionId: input.chatSessionId,
205658
205745
  text: input.text,
205659
205746
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -205686,11 +205773,11 @@ async function startOasisServer(opts) {
205686
205773
  ...f2?.mtime ? { srcMtime: f2.mtime } : {}
205687
205774
  };
205688
205775
  });
205689
- const { randomUUID: randomUUID41 } = await import("node:crypto");
205776
+ const { randomUUID: randomUUID42 } = await import("node:crypto");
205690
205777
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
205691
205778
  try {
205692
205779
  await store.enqueuePendingMessage({
205693
- id: randomUUID41(),
205780
+ id: randomUUID42(),
205694
205781
  chatSessionId: input.chatSessionId,
205695
205782
  text: "",
205696
205783
  createdAt: nowIso,
@@ -206700,7 +206787,7 @@ async function startOasisServer(opts) {
206700
206787
  }
206701
206788
  if (body2.dispatch === false) {
206702
206789
  await opts.artifactState?.setWorkspaceDispatchHold(workspace, true);
206703
- await engine.kernel.pauseWorkorder?.(workspace, actor);
206790
+ await engine.kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
206704
206791
  }
206705
206792
  await wodraftPlannerIssues.remove(workspace);
206706
206793
  if (opts.chatSession && body2.chatSessionId && workspace !== "ws:default") {
@@ -207018,7 +207105,7 @@ async function startOasisServer(opts) {
207018
207105
  const openRoom = async () => {
207019
207106
  let chatSessionId2;
207020
207107
  if (chatStore) {
207021
- const { randomUUID: randomUUID41 } = await import("node:crypto");
207108
+ const { randomUUID: randomUUID42 } = await import("node:crypto");
207022
207109
  const known = discussSessions.get(itemKey);
207023
207110
  const knownSession = known ? await chatStore.getSession(known) : null;
207024
207111
  if (knownSession && knownSession.workspace === ctx.workspace && knownSession.humanActorId === ctx.ownerId && (!body2.escalationId || knownSession.escalationId === body2.escalationId)) chatSessionId2 = known;
@@ -207027,7 +207114,7 @@ async function startOasisServer(opts) {
207027
207114
  chatSessionId2 = existing[0]?.id;
207028
207115
  }
207029
207116
  if (!chatSessionId2) {
207030
- chatSessionId2 = randomUUID41();
207117
+ chatSessionId2 = randomUUID42();
207031
207118
  const now = (/* @__PURE__ */ new Date()).toISOString();
207032
207119
  const title = `\u89E3\u51B3\uFF1A${ctx.seed.slice(ctx.seed.indexOf("\uFF1A") + 1, ctx.seed.indexOf("\uFF1A") + 25) || "\u5DE5\u5355\u5361\u70B9"}`;
207033
207120
  const reg0 = (await resolveEngine(currentCompanyId).catch(() => null))?.registry ?? opts.registry;
@@ -207896,14 +207983,14 @@ ${composed}`;
207896
207983
  runtimeSessionId: session.nativeSessionId ?? session.id
207897
207984
  }).catch(() => void 0);
207898
207985
  }
207899
- const { randomUUID: randomUUID42 } = await import("node:crypto");
207986
+ const { randomUUID: randomUUID43 } = await import("node:crypto");
207900
207987
  const persistAttachments = effectiveAttachments.map((a) => ({
207901
207988
  name: a.name,
207902
207989
  ...a.blobRef !== void 0 ? { blobRef: a.blobRef } : {},
207903
207990
  ...a.contentType !== void 0 ? { contentType: a.contentType } : {}
207904
207991
  }));
207905
207992
  await chatStore.appendMessage({
207906
- id: randomUUID42(),
207993
+ id: randomUUID43(),
207907
207994
  sessionId: persistTarget.id,
207908
207995
  role: "user",
207909
207996
  content: persistedUserMessage,
@@ -208378,8 +208465,8 @@ ${composed}`;
208378
208465
  }
208379
208466
  }
208380
208467
  const { spawn: spawn10 } = await import("node:child_process");
208381
- const { randomUUID: randomUUID41 } = await import("node:crypto");
208382
- const sessionId = body2.sessionId ?? randomUUID41();
208468
+ const { randomUUID: randomUUID42 } = await import("node:crypto");
208469
+ const sessionId = body2.sessionId ?? randomUUID42();
208383
208470
  const args = [
208384
208471
  "-p",
208385
208472
  body2.message,
@@ -208675,8 +208762,8 @@ ${composed}`;
208675
208762
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: `\u56DE\u8C03\u5730\u5740\u4E0D\u53EF\u7528\uFF0C\u5DF2\u4E2D\u6B62\uFF08\u907F\u514D\u5728 GitHub \u4E0A\u7559\u4E0B\u5B64\u513F App\uFF09\uFF1A${e.message}` }));
208676
208763
  return;
208677
208764
  }
208678
- const { randomUUID: randomUUID41 } = await import("node:crypto");
208679
- const state = randomUUID41();
208765
+ const { randomUUID: randomUUID42 } = await import("node:crypto");
208766
+ const state = randomUUID42();
208680
208767
  const nameSlug = (actorName || actorId?.split(":").pop() || owner?.login || "bot").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "bot";
208681
208768
  const manifest = buildAppManifest({
208682
208769
  employeeSlug: nameSlug,
@@ -212740,6 +212827,19 @@ var init_stats = __esm({
212740
212827
  }
212741
212828
  });
212742
212829
 
212830
+ // ../server/src/domains/projects/project-id.ts
212831
+ function randomProjectId() {
212832
+ return projectIdFromUuid((0, import_node_crypto41.randomUUID)());
212833
+ }
212834
+ var import_node_crypto41;
212835
+ var init_project_id = __esm({
212836
+ "../server/src/domains/projects/project-id.ts"() {
212837
+ "use strict";
212838
+ import_node_crypto41 = require("node:crypto");
212839
+ init_src();
212840
+ }
212841
+ });
212842
+
212743
212843
  // ../server/src/domains/projects/service.ts
212744
212844
  function sanitizeCreatorActorId(raw) {
212745
212845
  const id = raw?.trim();
@@ -212881,10 +212981,6 @@ function namedError(name, message) {
212881
212981
  err.name = name;
212882
212982
  return err;
212883
212983
  }
212884
- function slugProjectId(name) {
212885
- const slug6 = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
212886
- return `proj_${slug6 || Date.now()}`;
212887
- }
212888
212984
  function dirname24(path41) {
212889
212985
  const norm = path41.replace(/^\/+|\/+$/g, "");
212890
212986
  const idx = norm.lastIndexOf("/");
@@ -213081,6 +213177,7 @@ var init_service4 = __esm({
213081
213177
  init_uncategorized_project();
213082
213178
  init_workorders();
213083
213179
  init_stats();
213180
+ init_project_id();
213084
213181
  init_node_state();
213085
213182
  DEFAULT_AGENT_ACTOR = "actor:agent:system";
213086
213183
  CHAT_FOLDER_TASK_LABEL = "\u5BF9\u8BDD";
@@ -213283,6 +213380,15 @@ var init_service4 = __esm({
213283
213380
  async getWorkspaceBinding(workOrderId) {
213284
213381
  return this.artifacts.getWorkspaceBinding(workOrderId);
213285
213382
  }
213383
+ /**
213384
+ * 工单负责人(改挂项目门禁的「条目本人」一支)。与列表「负责人」列**同一个出口**:
213385
+ * `buildWorkorderSummaries` 的 `owner`(页头总纲根的 owner)——判权限与界面上显示的负责人不许是两套。
213386
+ * 内核里没有这张工单 → null。
213387
+ */
213388
+ workorderOwnerOf(workOrderId) {
213389
+ const summary = buildWorkorderSummaries(this.kernel.model).find((w2) => w2.id === workOrderId);
213390
+ return summary?.owner.id ?? null;
213391
+ }
213286
213392
  /** 上传一个项目普通文件:内容入 blob 库,记一条元信息旁存(非 oplog 事实)。 */
213287
213393
  async addProjectFile(input) {
213288
213394
  if (!this.blobs) throw new Error("\u9879\u76EE\u6587\u4EF6\u4E0A\u4F20\u9700\u8981 blob \u5E93\uFF08domain \u672A\u6CE8\u5165 blobs\uFF09");
@@ -213361,9 +213467,14 @@ var init_service4 = __esm({
213361
213467
  async createProject(input) {
213362
213468
  if (!input.name.trim()) throw new Error("project name is required");
213363
213469
  const now = (/* @__PURE__ */ new Date()).toISOString();
213364
- const id = input.id ?? slugProjectId(input.name);
213365
- const existing = await this.projects.getProject(id);
213366
- if (existing) return this.withProjectDerivedState(existing);
213470
+ let id;
213471
+ if (input.id !== void 0 && input.id.trim() !== "") {
213472
+ id = input.id;
213473
+ const existing = await this.projects.getProject(id);
213474
+ if (existing) return this.withProjectDerivedState(existing);
213475
+ } else {
213476
+ id = await this.allocateProjectId();
213477
+ }
213367
213478
  const createdById = sanitizeCreatorActorId(input.createdBy);
213368
213479
  const project = {
213369
213480
  id,
@@ -213399,6 +213510,14 @@ var init_service4 = __esm({
213399
213510
  }
213400
213511
  return await this.getProject(id) ?? project;
213401
213512
  }
213513
+ /** 给「没带 id 的新建」挑一个本公司还没用过的随机 id(`randomProjectId`);连撞几次说明生成器坏了,直接报错。 */
213514
+ async allocateProjectId() {
213515
+ for (let attempt = 0; attempt < 5; attempt++) {
213516
+ const id = randomProjectId();
213517
+ if (!await this.projects.getProject(id)) return id;
213518
+ }
213519
+ throw new Error("project id allocation failed: random ids kept colliding");
213520
+ }
213402
213521
  async getProject(id) {
213403
213522
  if (isUncategorizedProjectId(id)) return this.buildUncategorizedProject();
213404
213523
  const project = await this.projects.getProject(id);
@@ -214741,12 +214860,12 @@ var init_service4 = __esm({
214741
214860
  });
214742
214861
 
214743
214862
  // ../server/src/dev-store.ts
214744
- var import_node_crypto41, fs20, path21, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
214863
+ var import_node_crypto42, fs20, path21, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
214745
214864
  var init_dev_store = __esm({
214746
214865
  "../server/src/dev-store.ts"() {
214747
214866
  "use strict";
214748
214867
  init_src();
214749
- import_node_crypto41 = require("node:crypto");
214868
+ import_node_crypto42 = require("node:crypto");
214750
214869
  fs20 = __toESM(require("node:fs"), 1);
214751
214870
  path21 = __toESM(require("node:path"), 1);
214752
214871
  init_src();
@@ -214800,7 +214919,7 @@ var init_dev_store = __esm({
214800
214919
  return path21.join(this.dir, hash2);
214801
214920
  }
214802
214921
  async put(bytes2) {
214803
- const hash2 = (0, import_node_crypto41.createHash)("sha256").update(bytes2).digest("hex");
214922
+ const hash2 = (0, import_node_crypto42.createHash)("sha256").update(bytes2).digest("hex");
214804
214923
  const file = this.fileOf(hash2);
214805
214924
  if (!fs20.existsSync(file)) fs20.writeFileSync(file, bytes2);
214806
214925
  return hash2;
@@ -215385,6 +215504,10 @@ var init_dev_store = __esm({
215385
215504
  async listSessionsByWorkspace(workspace) {
215386
215505
  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
215506
  }
215507
+ /** 语义同 postgres 版:项目「关联对话」,不按人过滤;排除分析 / 内部 / 挂在任务上的会话,created_at 倒序。 */
215508
+ async listSessionsByProject(projectId2) {
215509
+ 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));
215510
+ }
215388
215511
  async listAllAnalysisSessions(opts = {}) {
215389
215512
  const limit = opts.limit ?? 100;
215390
215513
  const sorted = [...this.sessions.values()].filter((s2) => !!s2.analyzedRunId).sort((a, b2) => b2.touchedAt.localeCompare(a.touchedAt) || b2.id.localeCompare(a.id));
@@ -215996,7 +216119,7 @@ var init_company_engine_router = __esm({
215996
216119
 
215997
216120
  // ../server/src/skill-market-client.ts
215998
216121
  function digestOf2(bytes2) {
215999
- return `sha256:${(0, import_node_crypto42.createHash)("sha256").update(bytes2).digest("hex")}`;
216122
+ return `sha256:${(0, import_node_crypto43.createHash)("sha256").update(bytes2).digest("hex")}`;
216000
216123
  }
216001
216124
  function createSkillMarketClientFromEnv(env = process.env) {
216002
216125
  const base = env["SKILL_MARKET_BASE_URL"];
@@ -216007,11 +216130,11 @@ function createSkillMarketClientFromEnv(env = process.env) {
216007
216130
  function sleep4(ms) {
216008
216131
  return new Promise((r) => setTimeout(r, ms));
216009
216132
  }
216010
- var import_node_crypto42, DisabledSkillMarketClient, HttpSkillMarketClient;
216133
+ var import_node_crypto43, DisabledSkillMarketClient, HttpSkillMarketClient;
216011
216134
  var init_skill_market_client = __esm({
216012
216135
  "../server/src/skill-market-client.ts"() {
216013
216136
  "use strict";
216014
- import_node_crypto42 = require("node:crypto");
216137
+ import_node_crypto43 = require("node:crypto");
216015
216138
  init_src();
216016
216139
  DisabledSkillMarketClient = class {
216017
216140
  async listSkills(_input) {
@@ -216933,11 +217056,11 @@ function typeDefError(def, mode) {
216933
217056
  }
216934
217057
  return null;
216935
217058
  }
216936
- var import_node_crypto43, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
217059
+ var import_node_crypto44, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
216937
217060
  var init_types_admin = __esm({
216938
217061
  "../server/src/types-admin.ts"() {
216939
217062
  "use strict";
216940
- import_node_crypto43 = require("node:crypto");
217063
+ import_node_crypto44 = require("node:crypto");
216941
217064
  init_zod();
216942
217065
  init_src5();
216943
217066
  init_config_audit();
@@ -217064,7 +217187,7 @@ var init_types_admin = __esm({
217064
217187
  async trace(change, target, by, detail) {
217065
217188
  try {
217066
217189
  await this.audit?.({
217067
- id: `reg_${(0, import_node_crypto43.randomUUID)()}`,
217190
+ id: `reg_${(0, import_node_crypto44.randomUUID)()}`,
217068
217191
  actor: by ?? SYSTEM_CONFIG_ACTOR,
217069
217192
  kind: "registry_change",
217070
217193
  target,
@@ -217212,11 +217335,11 @@ function roleDefError(def, mode) {
217212
217335
  }
217213
217336
  return null;
217214
217337
  }
217215
- var import_node_crypto44, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
217338
+ var import_node_crypto45, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
217216
217339
  var init_roles_admin = __esm({
217217
217340
  "../server/src/roles-admin.ts"() {
217218
217341
  "use strict";
217219
- import_node_crypto44 = require("node:crypto");
217342
+ import_node_crypto45 = require("node:crypto");
217220
217343
  init_zod();
217221
217344
  init_src5();
217222
217345
  init_config_audit();
@@ -217241,7 +217364,7 @@ var init_roles_admin = __esm({
217241
217364
  async trace(change, target, by, detail) {
217242
217365
  try {
217243
217366
  await this.deps.audit?.({
217244
- id: `reg_${(0, import_node_crypto44.randomUUID)()}`,
217367
+ id: `reg_${(0, import_node_crypto45.randomUUID)()}`,
217245
217368
  actor: by ?? SYSTEM_CONFIG_ACTOR,
217246
217369
  kind: "registry_change",
217247
217370
  target,
@@ -219169,7 +219292,9 @@ var init_sink = __esm({
219169
219292
  st.openTools.set(callId, {
219170
219293
  toolName: typeof p2["name"] === "string" ? p2["name"] : "tool",
219171
219294
  input: p2["input"] ?? null,
219172
- startedAt: event.ts
219295
+ startedAt: event.ts,
219296
+ // 子 Agent 里的调用:adapter 给 payload 标了派出它的那次调用(Claude 的 parent_tool_use_id)。
219297
+ parentCallId: typeof p2["parentToolCallId"] === "string" ? p2["parentToolCallId"] : null
219173
219298
  });
219174
219299
  return;
219175
219300
  }
@@ -219185,7 +219310,7 @@ var init_sink = __esm({
219185
219310
  id: `tc_${st.runId}_${callId}`,
219186
219311
  runId: st.runId,
219187
219312
  callId,
219188
- parentCallId: null,
219313
+ parentCallId: open3.parentCallId,
219189
219314
  toolName: open3.toolName,
219190
219315
  toolKind: toolKindOf(open3.toolName),
219191
219316
  status: isErr ? "failed" : "succeeded",
@@ -219490,7 +219615,7 @@ function wireRecoveredChatTurn(deps) {
219490
219615
  const segText = itemLedger.projectContent();
219491
219616
  const segParts = collectedParts();
219492
219617
  const closingId = currentMsgId;
219493
- const nextId = (0, import_node_crypto45.randomUUID)();
219618
+ const nextId = (0, import_node_crypto46.randomUUID)();
219494
219619
  currentMsgId = nextId;
219495
219620
  itemLedger.beginSegment();
219496
219621
  live.setAssistantMessageId(nextId);
@@ -219504,7 +219629,7 @@ function wireRecoveredChatTurn(deps) {
219504
219629
  ...segParts.length ? { parts: segParts } : {}
219505
219630
  }).catch(() => void 0);
219506
219631
  await store.appendMessage({
219507
- id: (0, import_node_crypto45.randomUUID)(),
219632
+ id: (0, import_node_crypto46.randomUUID)(),
219508
219633
  sessionId: plan.chatSessionId,
219509
219634
  role: "user",
219510
219635
  content: text5,
@@ -219943,11 +220068,11 @@ async function recoverChatTurnsAcrossCompanies(deps) {
219943
220068
  }
219944
220069
  return { ...result, recovered };
219945
220070
  }
219946
- var import_node_crypto45, asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
220071
+ var import_node_crypto46, asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
219947
220072
  var init_chat_recovery = __esm({
219948
220073
  "../server/src/chat-recovery.ts"() {
219949
220074
  "use strict";
219950
- import_node_crypto45 = require("node:crypto");
220075
+ import_node_crypto46 = require("node:crypto");
219951
220076
  init_chat_parts();
219952
220077
  init_sink();
219953
220078
  init_resilient_appender();
@@ -219965,6 +220090,7 @@ var init_chat_recovery = __esm({
219965
220090
  durationMs: Date.parse(endedAt) - Date.parse(startedAt),
219966
220091
  exitCode: info.code,
219967
220092
  exitReason: info.reason ?? null,
220093
+ ...exitFailed(info) && info.reason !== "cancelled" ? { errorCode: info.reason ?? "error", errorMessage: formatExitError(info) } : {},
219968
220094
  transcriptRef: info.transcriptRef ?? null,
219969
220095
  usage: info.usage ?? null,
219970
220096
  // 重启前 create/dispatch 路已把 actor/runtime fallback 模型写进 run;退出帧没报 model 时必须保留,
@@ -219976,8 +220102,8 @@ var init_chat_recovery = __esm({
219976
220102
 
219977
220103
  // ../server/src/auth/crypto.ts
219978
220104
  function hashPassword(password) {
219979
- const salt = (0, import_node_crypto46.randomBytes)(16);
219980
- const dk = (0, import_node_crypto46.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
220105
+ const salt = (0, import_node_crypto47.randomBytes)(16);
220106
+ const dk = (0, import_node_crypto47.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
219981
220107
  return `scrypt$${SCRYPT_N}$${salt.toString("base64url")}$${dk.toString("base64url")}`;
219982
220108
  }
219983
220109
  function verifyPassword(password, stored) {
@@ -219994,8 +220120,8 @@ function verifyPassword(password, stored) {
219994
220120
  return false;
219995
220121
  }
219996
220122
  if (expected.length === 0) return false;
219997
- const dk = (0, import_node_crypto46.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
219998
- return dk.length === expected.length && (0, import_node_crypto46.timingSafeEqual)(dk, expected);
220123
+ const dk = (0, import_node_crypto47.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
220124
+ return dk.length === expected.length && (0, import_node_crypto47.timingSafeEqual)(dk, expected);
219999
220125
  }
220000
220126
  function b64urlJson(value2) {
220001
220127
  return Buffer.from(JSON.stringify(value2)).toString("base64url");
@@ -220003,17 +220129,17 @@ function b64urlJson(value2) {
220003
220129
  function signSession(claims, secret) {
220004
220130
  const head = b64urlJson({ alg: "HS256", typ: "JWT" });
220005
220131
  const body2 = b64urlJson(claims);
220006
- const sig = (0, import_node_crypto46.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
220132
+ const sig = (0, import_node_crypto47.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
220007
220133
  return `${head}.${body2}.${sig}`;
220008
220134
  }
220009
220135
  function verifySession(token, secret, nowMs) {
220010
220136
  const parts = token.split(".");
220011
220137
  if (parts.length !== 3) return null;
220012
220138
  const [head, body2, sig] = parts;
220013
- const expected = (0, import_node_crypto46.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
220139
+ const expected = (0, import_node_crypto47.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
220014
220140
  const got = Buffer.from(sig);
220015
220141
  const exp = Buffer.from(expected);
220016
- if (got.length !== exp.length || !(0, import_node_crypto46.timingSafeEqual)(got, exp)) return null;
220142
+ if (got.length !== exp.length || !(0, import_node_crypto47.timingSafeEqual)(got, exp)) return null;
220017
220143
  let claims;
220018
220144
  try {
220019
220145
  claims = JSON.parse(Buffer.from(body2, "base64url").toString("utf8"));
@@ -220025,21 +220151,21 @@ function verifySession(token, secret, nowMs) {
220025
220151
  return claims;
220026
220152
  }
220027
220153
  function generateCode() {
220028
- return String((0, import_node_crypto46.randomInt)(0, 1e6)).padStart(6, "0");
220154
+ return String((0, import_node_crypto47.randomInt)(0, 1e6)).padStart(6, "0");
220029
220155
  }
220030
220156
  function hashCode(code2, secret) {
220031
- return (0, import_node_crypto46.createHmac)("sha256", secret).update(`code:${code2}`).digest("base64url");
220157
+ return (0, import_node_crypto47.createHmac)("sha256", secret).update(`code:${code2}`).digest("base64url");
220032
220158
  }
220033
220159
  function safeEqualHash(a, b2) {
220034
220160
  const ba = Buffer.from(a);
220035
220161
  const bb = Buffer.from(b2);
220036
- return ba.length === bb.length && (0, import_node_crypto46.timingSafeEqual)(ba, bb);
220162
+ return ba.length === bb.length && (0, import_node_crypto47.timingSafeEqual)(ba, bb);
220037
220163
  }
220038
- var import_node_crypto46, SCRYPT_N, SCRYPT_KEYLEN;
220164
+ var import_node_crypto47, SCRYPT_N, SCRYPT_KEYLEN;
220039
220165
  var init_crypto2 = __esm({
220040
220166
  "../server/src/auth/crypto.ts"() {
220041
220167
  "use strict";
220042
- import_node_crypto46 = require("node:crypto");
220168
+ import_node_crypto47 = require("node:crypto");
220043
220169
  SCRYPT_N = 16384;
220044
220170
  SCRYPT_KEYLEN = 32;
220045
220171
  }
@@ -220262,6 +220388,9 @@ var init_daemon_hub = __esm({
220262
220388
  this.wss = new import_websocket_server.default({
220263
220389
  server,
220264
220390
  path: path41,
220391
+ // workdir_read returns base64 plus a JSON envelope. ws defaults to 100 MiB,
220392
+ // which would disconnect the node before a 200 MiB original reaches HTTP.
220393
+ maxPayload: 4 * Math.ceil(FILE_CONTENT_MAX_BYTES / 3) + 64 * 1024,
220265
220394
  ...resolveNode ? {
220266
220395
  // 异步校验:verifyClient 允许延迟回调,故可在此 await 一次 DB 查询后再放行/拒绝。
220267
220396
  verifyClient: (info, cb) => {
@@ -221186,16 +221315,16 @@ function variableKeyFromEnv(env = process.env) {
221186
221315
  return buf;
221187
221316
  }
221188
221317
  if (env.NODE_ENV === "production") throw new Error("OASIS_VAR_KEY is required in production");
221189
- return (0, import_node_crypto47.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
221318
+ return (0, import_node_crypto48.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
221190
221319
  }
221191
221320
  function dedupe(ids2) {
221192
221321
  return [...new Set(ids2.filter((x2) => typeof x2 === "string" && x2.length > 0))];
221193
221322
  }
221194
- var import_node_crypto47, REDACTED_MARKER, ActorsService, mask, changedKeys;
221323
+ var import_node_crypto48, REDACTED_MARKER, ActorsService, mask, changedKeys;
221195
221324
  var init_service5 = __esm({
221196
221325
  "../server/src/domains/actors/service.ts"() {
221197
221326
  "use strict";
221198
- import_node_crypto47 = require("node:crypto");
221327
+ import_node_crypto48 = require("node:crypto");
221199
221328
  init_skill_fetcher();
221200
221329
  init_identity();
221201
221330
  init_migrate();
@@ -221759,7 +221888,7 @@ ${input.description}
221759
221888
  */
221760
221889
  buildSkillFile(skillId, path41, content3, now) {
221761
221890
  const bytes2 = new TextEncoder().encode(content3);
221762
- const blobHash = (0, import_node_crypto47.createHash)("sha256").update(bytes2).digest("hex");
221891
+ const blobHash = (0, import_node_crypto48.createHash)("sha256").update(bytes2).digest("hex");
221763
221892
  return { skillId, path: path41, content: content3, blobHash, size: bytes2.length, updatedAt: now };
221764
221893
  }
221765
221894
  /**
@@ -222411,15 +222540,15 @@ ${input.description}
222411
222540
  return hit ? { value: hit.value, ...hit.origin ? { origin: hit.origin } : {} } : null;
222412
222541
  }
222413
222542
  encrypt(plain) {
222414
- const iv = (0, import_node_crypto47.randomBytes)(12);
222415
- const cipher = (0, import_node_crypto47.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
222543
+ const iv = (0, import_node_crypto48.randomBytes)(12);
222544
+ const cipher = (0, import_node_crypto48.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
222416
222545
  const enc4 = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
222417
222546
  return [iv.toString("base64"), cipher.getAuthTag().toString("base64"), enc4.toString("base64")].join(".");
222418
222547
  }
222419
222548
  decrypt(packed) {
222420
222549
  const [iv, tag, data] = packed.split(".");
222421
222550
  if (!iv || !tag || typeof data !== "string") throw new Error("malformed ciphertext");
222422
- const decipher = (0, import_node_crypto47.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
222551
+ const decipher = (0, import_node_crypto48.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
222423
222552
  decipher.setAuthTag(Buffer.from(tag, "base64"));
222424
222553
  return Buffer.concat([decipher.update(Buffer.from(data, "base64")), decipher.final()]).toString("utf8");
222425
222554
  }
@@ -223037,7 +223166,7 @@ async function listSkillMetadataForActor(args) {
223037
223166
  usedDirs.add(dir);
223038
223167
  const sorted = [...metas].sort((a, b2) => a.path.localeCompare(b2.path));
223039
223168
  let latest = "1970-01-01T00:00:00.000Z";
223040
- const hasher = (0, import_node_crypto48.createHash)("sha256");
223169
+ const hasher = (0, import_node_crypto49.createHash)("sha256");
223041
223170
  for (const m2 of sorted) {
223042
223171
  if (m2.updatedAt > latest) latest = m2.updatedAt;
223043
223172
  hasher.update(m2.path);
@@ -223062,11 +223191,11 @@ async function listSkillMetadataForActor(args) {
223062
223191
  const dir = sanitizeSkillDir(c.name, c.slug);
223063
223192
  if (usedDirs.has(dir)) continue;
223064
223193
  usedDirs.add(dir);
223065
- const hasher = (0, import_node_crypto48.createHash)("sha256");
223194
+ const hasher = (0, import_node_crypto49.createHash)("sha256");
223066
223195
  for (const p2 of paths) {
223067
223196
  hasher.update(p2);
223068
223197
  hasher.update("\0");
223069
- hasher.update((0, import_node_crypto48.createHash)("sha256").update(c.files[p2]).digest("hex"));
223198
+ hasher.update((0, import_node_crypto49.createHash)("sha256").update(c.files[p2]).digest("hex"));
223070
223199
  hasher.update("\0");
223071
223200
  }
223072
223201
  out.push({
@@ -223087,11 +223216,11 @@ async function listSkillMetadataForActor(args) {
223087
223216
  const dir = sanitizeSkillDir(b2.name, b2.id);
223088
223217
  if (usedDirs.has(dir)) continue;
223089
223218
  usedDirs.add(dir);
223090
- const hasher = (0, import_node_crypto48.createHash)("sha256");
223219
+ const hasher = (0, import_node_crypto49.createHash)("sha256");
223091
223220
  for (const p2 of paths) {
223092
223221
  hasher.update(p2);
223093
223222
  hasher.update("\0");
223094
- hasher.update((0, import_node_crypto48.createHash)("sha256").update(b2.files[p2]).digest("hex"));
223223
+ hasher.update((0, import_node_crypto49.createHash)("sha256").update(b2.files[p2]).digest("hex"));
223095
223224
  hasher.update("\0");
223096
223225
  }
223097
223226
  out.push({
@@ -223111,7 +223240,7 @@ function builtinSkillToFiles(b2) {
223111
223240
  skillId: `builtin:${b2.id}`,
223112
223241
  path: path41,
223113
223242
  content: content3,
223114
- blobHash: (0, import_node_crypto48.createHash)("sha256").update(content3).digest("hex"),
223243
+ blobHash: (0, import_node_crypto49.createHash)("sha256").update(content3).digest("hex"),
223115
223244
  size: Buffer.byteLength(content3, "utf8"),
223116
223245
  updatedAt: "1970-01-01T00:00:00.000Z"
223117
223246
  }));
@@ -223135,7 +223264,7 @@ function sanitizeSkillDir(name, fallbackId) {
223135
223264
  if (byName) return byName;
223136
223265
  const byId = fallbackId ? fold2(fallbackId) : "";
223137
223266
  if (byId) return byId;
223138
- return `skill-${(0, import_node_crypto48.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
223267
+ return `skill-${(0, import_node_crypto49.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
223139
223268
  }
223140
223269
  async function materializeSkillFiles(args) {
223141
223270
  const skills = await args.service.listInstalledSkillsForActor(args.actorId);
@@ -223168,7 +223297,7 @@ function connectorSkillToFiles(c) {
223168
223297
  skillId: c.id,
223169
223298
  path: path41,
223170
223299
  content: content3,
223171
- blobHash: (0, import_node_crypto48.createHash)("sha256").update(content3).digest("hex"),
223300
+ blobHash: (0, import_node_crypto49.createHash)("sha256").update(content3).digest("hex"),
223172
223301
  size: Buffer.byteLength(content3, "utf8"),
223173
223302
  updatedAt: "1970-01-01T00:00:00.000Z"
223174
223303
  }));
@@ -223185,11 +223314,11 @@ function connectorSkillDisplayRows(connectorSkills, enabledConnectors) {
223185
223314
  ...s2.version !== void 0 ? { version: s2.version } : {}
223186
223315
  }));
223187
223316
  }
223188
- var import_node_crypto48;
223317
+ var import_node_crypto49;
223189
223318
  var init_skill_materializer = __esm({
223190
223319
  "../server/src/domains/actors/skill-materializer.ts"() {
223191
223320
  "use strict";
223192
- import_node_crypto48 = require("node:crypto");
223321
+ import_node_crypto49 = require("node:crypto");
223193
223322
  }
223194
223323
  });
223195
223324
 
@@ -224700,7 +224829,7 @@ function createActorsDomain(opts) {
224700
224829
  ...kernel !== void 0 ? { onRolesChanged: (a, r) => kernel.setActorRoles(a, r) } : {},
224701
224830
  audit: async (entry) => {
224702
224831
  await opts.audit?.({
224703
- id: `reg_${(0, import_node_crypto49.randomUUID)()}`,
224832
+ id: `reg_${(0, import_node_crypto50.randomUUID)()}`,
224704
224833
  actor: entry.by,
224705
224834
  kind: "registry_change",
224706
224835
  target: entry.actorId,
@@ -224740,11 +224869,11 @@ function createActorsDomain(opts) {
224740
224869
  register: actorsDomain({ resolveCtx, ...opts.resolveTrace ? { resolveTrace: opts.resolveTrace } : {}, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.isCompanyManager ? { isCompanyManager: opts.isCompanyManager } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {}, ...opts.skillMarket ? { skillMarket: opts.skillMarket } : {}, ...opts.resolveNodeTenancy ? { resolveNodeTenancy: opts.resolveNodeTenancy } : {} })
224741
224870
  };
224742
224871
  }
224743
- var import_node_crypto49;
224872
+ var import_node_crypto50;
224744
224873
  var init_actors = __esm({
224745
224874
  "../server/src/domains/actors/index.ts"() {
224746
224875
  "use strict";
224747
- import_node_crypto49 = require("node:crypto");
224876
+ import_node_crypto50 = require("node:crypto");
224748
224877
  init_service5();
224749
224878
  init_routes3();
224750
224879
  init_service5();
@@ -224914,6 +225043,359 @@ var init_assistant_tier_dispatch = __esm({
224914
225043
  }
224915
225044
  });
224916
225045
 
225046
+ // ../server/src/domains/chat-sessions/files-view.ts
225047
+ function reasonFromWorkdirCode(code2) {
225048
+ switch (code2) {
225049
+ case "NODE_OFFLINE":
225050
+ return "NODE_OFFLINE";
225051
+ case "TIMEOUT":
225052
+ return "TIMEOUT";
225053
+ default:
225054
+ return "ERROR";
225055
+ }
225056
+ }
225057
+ function joinRel2(dir, name) {
225058
+ return dir ? `${dir}/${name}` : name;
225059
+ }
225060
+ function workdirFileItem(rel, name, size, mtime) {
225061
+ return {
225062
+ id: `workdir:${rel}`,
225063
+ name,
225064
+ kind: "file",
225065
+ // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
225066
+ // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
225067
+ content_type: null,
225068
+ size,
225069
+ updated_at: mtime,
225070
+ source: "workdir",
225071
+ blob_ref: null,
225072
+ path: rel,
225073
+ message_id: null,
225074
+ // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
225075
+ // 这里是**按能力不渲染**的判据,不是置灰假钮。
225076
+ editable: false
225077
+ };
225078
+ }
225079
+ async function collectWorkdirFiles(bridge, loc, now = Date.now) {
225080
+ const items = [];
225081
+ if (typeof bridge.walk === "function") {
225082
+ const walked = await bridge.walk(
225083
+ loc.nodeId,
225084
+ { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: "" },
225085
+ WORKDIR_TREE_MAX_ENTRIES
225086
+ );
225087
+ if (walked.ok) {
225088
+ let truncated2 = walked.truncated === true;
225089
+ for (const entry of walked.entries) {
225090
+ if (entry.type !== "file") continue;
225091
+ if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
225092
+ truncated2 = true;
225093
+ break;
225094
+ }
225095
+ const slash = entry.path.lastIndexOf("/");
225096
+ items.push(workdirFileItem(entry.path, entry.path.slice(slash + 1), entry.size, entry.mtime));
225097
+ }
225098
+ return { ok: true, items, truncated: truncated2 };
225099
+ }
225100
+ if (walked.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
225101
+ if (walked.code !== "UNSUPPORTED") return { ok: false, reason: reasonFromWorkdirCode(walked.code) };
225102
+ }
225103
+ const deadline = now() + WORKDIR_WALK_BUDGET_MS;
225104
+ let listCalls = 0;
225105
+ let truncated = false;
225106
+ const listDir = (path41) => {
225107
+ listCalls += 1;
225108
+ return bridge.list(loc.nodeId, {
225109
+ workdirKey: loc.workdirKey,
225110
+ runtimeKind: loc.runtimeKind,
225111
+ rtId: loc.rtId,
225112
+ path: path41
225113
+ });
225114
+ };
225115
+ const root = await listDir("");
225116
+ if (!root.ok) {
225117
+ if (root.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
225118
+ return { ok: false, reason: reasonFromWorkdirCode(root.code) };
225119
+ }
225120
+ let level = [{ path: "", outcome: root }];
225121
+ for (let depth = 0; depth <= WORKDIR_WALK_MAX_DEPTH; depth += 1) {
225122
+ const nextDirs = [];
225123
+ for (const { path: dir, outcome } of level) {
225124
+ if (!outcome.ok) {
225125
+ truncated = true;
225126
+ continue;
225127
+ }
225128
+ if (outcome.truncated) truncated = true;
225129
+ for (const entry of outcome.entries) {
225130
+ if (entry.type === "dir") {
225131
+ nextDirs.push(joinRel2(dir, entry.name));
225132
+ continue;
225133
+ }
225134
+ if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
225135
+ truncated = true;
225136
+ continue;
225137
+ }
225138
+ items.push(workdirFileItem(joinRel2(dir, entry.name), entry.name, entry.size, entry.mtime));
225139
+ }
225140
+ }
225141
+ if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
225142
+ if (nextDirs.length > 0) truncated = true;
225143
+ break;
225144
+ }
225145
+ const budget = WORKDIR_WALK_MAX_LIST_CALLS - listCalls;
225146
+ if (budget <= 0 || now() >= deadline) {
225147
+ truncated = true;
225148
+ break;
225149
+ }
225150
+ const take = nextDirs.slice(0, budget);
225151
+ if (take.length < nextDirs.length) truncated = true;
225152
+ const outcomes = await Promise.all(take.map((p2) => listDir(p2)));
225153
+ level = take.map((p2, i) => ({ path: p2, outcome: outcomes[i] }));
225154
+ }
225155
+ return { ok: true, items, truncated };
225156
+ }
225157
+ async function collectMessageFiles(messages, blobs) {
225158
+ const rows = [];
225159
+ for (const message of messages) {
225160
+ if (!Array.isArray(message.attachments)) continue;
225161
+ message.attachments.forEach((attachment, index2) => {
225162
+ if (!attachment || typeof attachment.name !== "string" || !attachment.name) return;
225163
+ const blobRef = typeof attachment.blobRef === "string" && attachment.blobRef ? attachment.blobRef : null;
225164
+ rows.push({
225165
+ item: {
225166
+ id: `msg:${message.id}:${index2}`,
225167
+ name: attachment.name,
225168
+ kind: "file",
225169
+ updated_at: attachment.updatedAt ?? message.createdAt,
225170
+ source: "message",
225171
+ blob_ref: blobRef,
225172
+ path: null,
225173
+ message_id: message.id,
225174
+ // 没有 blobRef 的是**历史纯文本附件**(契约注释:legacy text attachments may only
225175
+ // contain `name`)——没有可写回的载体,一律不给编辑入口。
225176
+ editable: blobRef !== null && isEditableFileName(attachment.name)
225177
+ },
225178
+ blobRef,
225179
+ declaredType: typeof attachment.contentType === "string" && attachment.contentType ? attachment.contentType : null
225180
+ });
225181
+ });
225182
+ }
225183
+ const metaCache = /* @__PURE__ */ new Map();
225184
+ const metaOf = (ref2) => {
225185
+ if (!blobs) return Promise.resolve(null);
225186
+ let hit = metaCache.get(ref2);
225187
+ if (!hit) {
225188
+ hit = blobs.meta(ref2).catch(() => null);
225189
+ metaCache.set(ref2, hit);
225190
+ }
225191
+ return hit;
225192
+ };
225193
+ return Promise.all(rows.map(async ({ item, blobRef, declaredType }) => {
225194
+ const meta = blobRef ? await metaOf(blobRef) : null;
225195
+ return {
225196
+ ...item,
225197
+ content_type: declaredType ?? meta?.contentType ?? null,
225198
+ size: meta?.size ?? null
225199
+ };
225200
+ }));
225201
+ }
225202
+ function mergeChatSessionFiles(messageItems, workdirItems, materializedCopies = /* @__PURE__ */ new Set()) {
225203
+ const out = [];
225204
+ const seenMessageKeys = /* @__PURE__ */ new Set();
225205
+ for (const item of [...messageItems].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at))) {
225206
+ const key = `${item.name}\0${item.blob_ref ?? ""}`;
225207
+ if (seenMessageKeys.has(key)) continue;
225208
+ seenMessageKeys.add(key);
225209
+ out.push(item);
225210
+ }
225211
+ const seenWorkdirPaths = /* @__PURE__ */ new Set();
225212
+ for (const item of workdirItems) {
225213
+ if (item.path && materializedCopies.has(item.path)) continue;
225214
+ if (seenWorkdirPaths.has(item.id)) continue;
225215
+ seenWorkdirPaths.add(item.id);
225216
+ out.push(item);
225217
+ }
225218
+ out.sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.name.localeCompare(b2.name));
225219
+ return out;
225220
+ }
225221
+ async function findMaterializedAttachmentCopies(messageItems, workdirItems, read) {
225222
+ const copies = /* @__PURE__ */ new Set();
225223
+ const attachments = messageItems.filter((item) => item.blob_ref && /^[a-f0-9]{64}$/.test(item.blob_ref));
225224
+ let budget = CHAT_ATTACHMENT_MAX_BYTES;
225225
+ const deadline = Date.now() + WORKDIR_WALK_BUDGET_MS;
225226
+ let reads = 0;
225227
+ for (const file of workdirItems) {
225228
+ const rootFile = file.path === file.name;
225229
+ const inputFile = file.path?.startsWith("inputs/attachments/") && !file.path.slice("inputs/attachments/".length).includes("/");
225230
+ if (!file.path || !rootFile && !inputFile) continue;
225231
+ const candidates = attachments.filter((attachment) => {
225232
+ const safe = safeAttachmentName(attachment.name);
225233
+ const numbered = file.name.endsWith(`-${safe}`) && /^\d+$/.test(file.name.slice(0, -(safe.length + 1)));
225234
+ return (rootFile ? file.name === attachment.name : file.name === safe || numbered) && attachment.size !== null && attachment.size === file.size;
225235
+ });
225236
+ if (Date.now() >= deadline) break;
225237
+ if (!candidates.length || file.size === null || file.size > budget || reads >= 10) continue;
225238
+ reads += 1;
225239
+ budget -= file.size;
225240
+ let timer;
225241
+ try {
225242
+ const result = await Promise.race([
225243
+ read(file.path, file.size),
225244
+ new Promise((resolve10) => {
225245
+ timer = setTimeout(() => resolve10(null), Math.max(0, deadline - Date.now()));
225246
+ })
225247
+ ]);
225248
+ if (!result?.ok) continue;
225249
+ const bytes2 = Buffer.from(result.contentBase64, "base64");
225250
+ if (bytes2.length !== file.size) continue;
225251
+ const hash2 = (0, import_node_crypto51.createHash)("sha256").update(bytes2).digest("hex");
225252
+ if (candidates.some((attachment) => attachment.blob_ref === hash2)) copies.add(file.path);
225253
+ } catch {
225254
+ } finally {
225255
+ if (timer) clearTimeout(timer);
225256
+ }
225257
+ }
225258
+ return copies;
225259
+ }
225260
+ var import_node_crypto51, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS, WORKDIR_TREE_MAX_ENTRIES;
225261
+ var init_files_view = __esm({
225262
+ "../server/src/domains/chat-sessions/files-view.ts"() {
225263
+ "use strict";
225264
+ import_node_crypto51 = require("node:crypto");
225265
+ init_chat_attachment_files();
225266
+ init_src();
225267
+ WORKDIR_WALK_MAX_DEPTH = 3;
225268
+ WORKDIR_WALK_MAX_ENTRIES = 200;
225269
+ WORKDIR_WALK_MAX_LIST_CALLS = 20;
225270
+ WORKDIR_WALK_BUDGET_MS = 1e4;
225271
+ WORKDIR_TREE_MAX_ENTRIES = 2e3;
225272
+ }
225273
+ });
225274
+
225275
+ // ../server/src/domains/projects/chat-files.ts
225276
+ function isProjectChatSession(session) {
225277
+ if (session.visibility === "internal") return false;
225278
+ if (session.analyzedRunId) return false;
225279
+ if (session.workspace) return false;
225280
+ return true;
225281
+ }
225282
+ function chatFolderTitle(session) {
225283
+ const title = session.title?.trim();
225284
+ return title ? title : "\u672A\u547D\u540D\u5BF9\u8BDD";
225285
+ }
225286
+ function outputDisplayName(path41) {
225287
+ return path41.startsWith("outputs/") ? path41.slice("outputs/".length) : path41;
225288
+ }
225289
+ async function collectProjectChatFiles(chat, projectId2, meta, options = {}) {
225290
+ const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
225291
+ const [attachmentMessages, outputSessions] = await Promise.all([
225292
+ chat.listAttachmentMessages ? chat.listAttachmentMessages(scope) : Promise.resolve([]),
225293
+ chat.listOutputCopies ? chat.listOutputCopies(scope) : Promise.resolve([])
225294
+ ]);
225295
+ const sessionIds = [.../* @__PURE__ */ new Set([
225296
+ ...attachmentMessages.map((m2) => m2.sessionId),
225297
+ ...outputSessions.map((s2) => s2.sessionId)
225298
+ ])];
225299
+ const sessions = /* @__PURE__ */ new Map();
225300
+ await Promise.all(sessionIds.map(async (id) => {
225301
+ const session = await chat.getSession(id);
225302
+ if (!session) return;
225303
+ if ((session.projectId ?? null) !== scope) return;
225304
+ if (!isProjectChatSession(session)) return;
225305
+ if (options.isDelegationChild && await options.isDelegationChild(id).catch(() => false)) return;
225306
+ sessions.set(id, session);
225307
+ }));
225308
+ const out = [];
225309
+ const base = (session) => ({
225310
+ projectId: projectId2,
225311
+ chatSessionId: session.id,
225312
+ chatTitle: chatFolderTitle(session),
225313
+ chatOwnerId: session.humanActorId
225314
+ });
225315
+ const bySession = /* @__PURE__ */ new Map();
225316
+ for (const message of attachmentMessages) {
225317
+ if (!sessions.has(message.sessionId)) continue;
225318
+ const list2 = bySession.get(message.sessionId) ?? [];
225319
+ list2.push(message);
225320
+ bySession.set(message.sessionId, list2);
225321
+ }
225322
+ for (const [sessionId, messages] of bySession) {
225323
+ const session = sessions.get(sessionId);
225324
+ const rows = await collectMessageFiles(messages.map((m2) => ({ ...m2, id: m2.messageId })), { meta });
225325
+ for (const row of mergeChatSessionFiles(rows, [])) {
225326
+ if (row.blob_ref === null) continue;
225327
+ out.push({
225328
+ ...base(session),
225329
+ fileId: `chat:${sessionId}:${row.id}`,
225330
+ name: row.name,
225331
+ blobId: row.blob_ref,
225332
+ ...row.content_type ? { contentType: row.content_type } : {},
225333
+ size: row.size,
225334
+ uploadedBy: session.humanActorId,
225335
+ uploadedAt: row.updated_at,
225336
+ chatSource: "attachment"
225337
+ });
225338
+ }
225339
+ }
225340
+ for (const record8 of outputSessions) {
225341
+ const session = sessions.get(record8.sessionId);
225342
+ if (!session) continue;
225343
+ const seen = new Set(out.filter((file) => file.chatSessionId === session.id).map((file) => `${file.name}\0${file.blobId}`));
225344
+ const copies = record8.copies ?? {};
225345
+ for (const path41 of Object.keys(copies).sort((a, b2) => a.localeCompare(b2))) {
225346
+ const copy = copies[path41];
225347
+ if (!copy?.blobRef) continue;
225348
+ if (seen.has(`${copy.name}\0${copy.blobRef}`)) continue;
225349
+ out.push({
225350
+ ...base(session),
225351
+ fileId: `chat:${session.id}:out:${copy.path}`,
225352
+ name: outputDisplayName(copy.path),
225353
+ blobId: copy.blobRef,
225354
+ ...copy.contentType ? { contentType: copy.contentType } : {},
225355
+ size: copy.size,
225356
+ uploadedBy: session.aiActorId,
225357
+ uploadedAt: Number.isNaN(Date.parse(copy.mtime)) ? copy.copiedAt : new Date(copy.mtime).toISOString(),
225358
+ chatSource: "output"
225359
+ });
225360
+ }
225361
+ }
225362
+ return out;
225363
+ }
225364
+ var init_chat_files2 = __esm({
225365
+ "../server/src/domains/projects/chat-files.ts"() {
225366
+ "use strict";
225367
+ init_files_view();
225368
+ init_uncategorized_project();
225369
+ }
225370
+ });
225371
+
225372
+ // ../server/src/domains/projects/chat-sessions.ts
225373
+ async function listProjectChatSessions(chat, projectId2, delegationChildIds) {
225374
+ if (!chat.listSessionsByProject) return [];
225375
+ const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
225376
+ const sessions = (await chat.listSessionsByProject(scope)).filter((s2) => (s2.projectId ?? null) === scope && isProjectChatSession(s2));
225377
+ const children = delegationChildIds && sessions.length > 0 ? await delegationChildIds(sessions.map((s2) => s2.id)).catch(() => /* @__PURE__ */ new Set()) : /* @__PURE__ */ new Set();
225378
+ return sessions.filter((s2) => !children.has(s2.id)).map(toRow);
225379
+ }
225380
+ function toRow(s2) {
225381
+ const title = s2.title?.trim();
225382
+ return {
225383
+ id: s2.id,
225384
+ title: title ? title : null,
225385
+ humanActorId: s2.humanActorId,
225386
+ aiActorId: s2.aiActorId,
225387
+ createdAt: s2.createdAt,
225388
+ projectId: s2.projectId ?? null
225389
+ };
225390
+ }
225391
+ var init_chat_sessions = __esm({
225392
+ "../server/src/domains/projects/chat-sessions.ts"() {
225393
+ "use strict";
225394
+ init_chat_files2();
225395
+ init_uncategorized_project();
225396
+ }
225397
+ });
225398
+
224917
225399
  // ../server/src/domains/projects/routes.ts
224918
225400
  function projectsDomain(opts) {
224919
225401
  const svc = async (req) => opts.resolveService ? opts.resolveService(req.auth.companyId) : opts.service;
@@ -224927,6 +225409,28 @@ function projectsDomain(opts) {
224927
225409
  if (role === "owner" || role === "admin") return;
224928
225410
  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
225411
  };
225412
+ const mayReassignProject = async (req, item) => {
225413
+ if (item.actorId && item.actorId === req.auth.actor) return true;
225414
+ if (item.currentProjectId && !isUncategorizedProjectId(item.currentProjectId)) {
225415
+ const project = await (await svc(req)).getProject(item.currentProjectId);
225416
+ if (project?.createdBy && project.createdBy.id === req.auth.actor) return true;
225417
+ }
225418
+ if (!opts.resolveMemberRole) return false;
225419
+ const role = await opts.resolveMemberRole(req.auth.companyId, req.auth.actor);
225420
+ return role === "owner" || role === "admin";
225421
+ };
225422
+ const requireWorkorderReassignPermission = async (req, workOrderId) => {
225423
+ if (!opts.resolveMemberRole) return;
225424
+ const service = await svc(req);
225425
+ const binding = await service.getWorkspaceBinding(workOrderId).catch(() => null);
225426
+ const allowed = await mayReassignProject(req, {
225427
+ actorId: service.workorderOwnerOf(workOrderId),
225428
+ currentProjectId: binding?.projectId ?? null
225429
+ });
225430
+ if (!allowed) {
225431
+ 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");
225432
+ }
225433
+ };
224930
225434
  return (router) => {
224931
225435
  router.post("/api/projects", async (req) => {
224932
225436
  const body2 = req.body;
@@ -225044,6 +225548,45 @@ function projectsDomain(opts) {
225044
225548
  const items = await (await svc(req)).listWorkordersForProject(req.params.id);
225045
225549
  return { status: 200, body: { items } };
225046
225550
  });
225551
+ router.get("/api/projects/:id/chat-sessions", async (req) => {
225552
+ const project = await (await svc(req)).getProject(req.params.id);
225553
+ if (!project) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u9879\u76EE\uFF1A${req.params.id}`);
225554
+ if (!opts.chatSessionsFor) return { status: 200, body: { items: [] } };
225555
+ const companyId = req.auth.companyId;
225556
+ const rows = await listProjectChatSessions(
225557
+ await opts.chatSessionsFor(companyId),
225558
+ req.params.id,
225559
+ opts.delegationChildIds ? (ids2) => opts.delegationChildIds(companyId, ids2) : void 0
225560
+ );
225561
+ return { status: 200, body: { items: rows.map(toApiProjectChatSession) } };
225562
+ });
225563
+ router.patch("/api/chat-sessions/:id/project", async (req) => {
225564
+ if (!opts.chatSessionsFor) throw new ApiError(404, "NOT_FOUND", "session not found");
225565
+ const store = await opts.chatSessionsFor(req.auth.companyId);
225566
+ const session = await store.getSession(req.params.id);
225567
+ if (!session || !isProjectChatSession(session)) throw new ApiError(404, "NOT_FOUND", "session not found");
225568
+ if (opts.delegationChildIds && (await opts.delegationChildIds(req.auth.companyId, [session.id])).has(session.id)) {
225569
+ throw new ApiError(404, "NOT_FOUND", "session not found");
225570
+ }
225571
+ const allowed = await mayReassignProject(req, {
225572
+ actorId: session.humanActorId,
225573
+ currentProjectId: session.projectId ?? null
225574
+ });
225575
+ if (!allowed) {
225576
+ 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");
225577
+ }
225578
+ const b2 = req.body ?? {};
225579
+ if (!("project_id" in b2) && !("projectId" in b2)) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 project_id");
225580
+ const raw = "project_id" in b2 ? b2.project_id : b2.projectId;
225581
+ const target = raw === null || raw === "" || raw === "null" || typeof raw === "string" && isUncategorizedProjectId(raw) ? null : raw;
225582
+ if (target !== null) {
225583
+ if (typeof target !== "string") throw new ApiError(400, "BAD_REQUEST", "project_id \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216 null");
225584
+ const project = await (await svc(req)).getProject(target);
225585
+ if (!project) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u9879\u76EE\uFF1A${target}`);
225586
+ }
225587
+ await store.updateSession(session.id, { projectId: target });
225588
+ return { status: 200, body: { chat_session_id: session.id, project_id: target } };
225589
+ });
225047
225590
  router.post("/api/projects/:id/files", async (req) => {
225048
225591
  const b2 = req.body ?? {};
225049
225592
  const base642 = b2.content_base64 ?? b2.contentBase64;
@@ -225250,6 +225793,7 @@ ${errs.join("\n")}`);
225250
225793
  return { status: 200, body: toApiNodeOutputValidation(validation) };
225251
225794
  });
225252
225795
  router.patch("/api/work-orders/:workOrderId/project", async (req) => {
225796
+ await requireWorkorderReassignPermission(req, req.params.workOrderId);
225253
225797
  const b2 = req.body ?? {};
225254
225798
  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
225799
  if (nulledPatch) {
@@ -225270,6 +225814,7 @@ ${errs.join("\n")}`);
225270
225814
  }
225271
225815
  });
225272
225816
  router.delete("/api/work-orders/:workOrderId/project", async (req) => {
225817
+ await requireWorkorderReassignPermission(req, req.params.workOrderId);
225273
225818
  try {
225274
225819
  await (await svc(req)).unlinkWorkorder(req.params.workOrderId);
225275
225820
  return { status: 200, body: { work_order_id: req.params.workOrderId, project_id: null } };
@@ -225743,6 +226288,16 @@ function toApiWorkOrderDetail(detail) {
225743
226288
  }))
225744
226289
  };
225745
226290
  }
226291
+ function toApiProjectChatSession(row) {
226292
+ return {
226293
+ id: row.id,
226294
+ title: row.title,
226295
+ human_actor_id: row.humanActorId,
226296
+ ai_actor_id: row.aiActorId,
226297
+ created_at: row.createdAt,
226298
+ project_id: row.projectId
226299
+ };
226300
+ }
225746
226301
  function toApiProjectFilesView(view) {
225747
226302
  return {
225748
226303
  total: view.total,
@@ -225801,6 +226356,8 @@ var init_routes5 = __esm({
225801
226356
  "use strict";
225802
226357
  init_router();
225803
226358
  init_src();
226359
+ init_chat_files2();
226360
+ init_chat_sessions();
225804
226361
  init_uncategorized_project();
225805
226362
  }
225806
226363
  });
@@ -228095,11 +228652,11 @@ function fromRow(row) {
228095
228652
  for (const [k2, v2] of opt) if (v2 !== void 0 && v2 !== null) r[k2] = v2;
228096
228653
  return r;
228097
228654
  }
228098
- var import_node_crypto50, import_node_fs14, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
228655
+ var import_node_crypto52, import_node_fs14, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
228099
228656
  var init_notification_store = __esm({
228100
228657
  "../server/src/domains/collab/notification-store.ts"() {
228101
228658
  "use strict";
228102
- import_node_crypto50 = require("node:crypto");
228659
+ import_node_crypto52 = require("node:crypto");
228103
228660
  import_node_fs14 = __toESM(require("node:fs"), 1);
228104
228661
  DEFAULT_PAGE = 50;
228105
228662
  MAX_PAGE = 200;
@@ -228132,7 +228689,7 @@ var init_notification_store = __esm({
228132
228689
  async insert(n) {
228133
228690
  const existing = this.findLive(n.recipient, n.sourceKey, n.category);
228134
228691
  if (existing) return existing;
228135
- const row = { ...n, id: `ntf:${(0, import_node_crypto50.randomUUID)()}`, updatedAt: n.enteredAt };
228692
+ const row = { ...n, id: `ntf:${(0, import_node_crypto52.randomUUID)()}`, updatedAt: n.enteredAt };
228136
228693
  this.rows.set(row.id, row);
228137
228694
  this.save();
228138
228695
  return row;
@@ -228151,13 +228708,22 @@ var init_notification_store = __esm({
228151
228708
  this.rows.set(id, next);
228152
228709
  this.save();
228153
228710
  }
228711
+ async rekey(id, sourceKey, patch, at) {
228712
+ const r = this.rows.get(id);
228713
+ if (!r || r.status !== "pending" || this.findLive(r.recipient, sourceKey, r.category)) return false;
228714
+ this.rows.set(id, { ...r, sourceKey, ...patch.nodeId ? { nodeId: patch.nodeId } : {} });
228715
+ await this.refresh(id, patch, at);
228716
+ return true;
228717
+ }
228154
228718
  async close(id, closure) {
228155
228719
  const r = this.rows.get(id);
228156
228720
  if (!r || r.status !== "pending") return false;
228157
228721
  const next = { ...r, status: closure.status, closedAt: closure.closedAt, updatedAt: closure.closedAt };
228158
228722
  if (closure.closedBy) next.closedBy = closure.closedBy;
228159
- if (closure.status === "handled") next.result = closure.result;
228160
- else next.invalidReason = closure.invalidReason;
228723
+ if (closure.status === "handled") {
228724
+ next.result = closure.result;
228725
+ next.readAt ??= closure.closedAt;
228726
+ } else next.invalidReason = closure.invalidReason;
228161
228727
  this.rows.set(id, next);
228162
228728
  this.save();
228163
228729
  return true;
@@ -228176,6 +228742,16 @@ var init_notification_store = __esm({
228176
228742
  }
228177
228743
  if (changed) this.save();
228178
228744
  }
228745
+ async countUnread(recipient) {
228746
+ let pending = 0;
228747
+ let all2 = 0;
228748
+ for (const r of this.rows.values()) {
228749
+ if (r.recipient !== recipient || r.readAt) continue;
228750
+ all2++;
228751
+ if (r.status === "pending") pending++;
228752
+ }
228753
+ return { pending, all: all2 };
228754
+ }
228179
228755
  async purgeBefore(cutoff) {
228180
228756
  let n = 0;
228181
228757
  for (const [id, r] of this.rows) {
@@ -228236,7 +228812,7 @@ var init_notification_store = __esm({
228236
228812
  return new _PostgresNotificationStore(sql, schema);
228237
228813
  }
228238
228814
  async insert(n) {
228239
- const id = `ntf:${(0, import_node_crypto50.randomUUID)()}`;
228815
+ const id = `ntf:${(0, import_node_crypto52.randomUUID)()}`;
228240
228816
  const r = await this.sql.query(
228241
228817
  `INSERT INTO ${this.t} (id, recipient, category, source_key, kind, workorder_id, node_id, card_id, actor_id,
228242
228818
  title, workspace_title, entered_at, updated_at, status, read_at, item)
@@ -228289,9 +228865,34 @@ var init_notification_store = __esm({
228289
228865
  ]
228290
228866
  );
228291
228867
  }
228868
+ async rekey(id, sourceKey, patch, at) {
228869
+ try {
228870
+ const r = await this.sql.query(
228871
+ `UPDATE ${this.t} SET source_key = $2, node_id = COALESCE($3, node_id), title = $4, workspace_title = $5,
228872
+ actor_id = $6, card_id = $7, item = $8, updated_at = $9
228873
+ WHERE id = $1 AND status = 'pending'`,
228874
+ [
228875
+ id,
228876
+ sourceKey,
228877
+ patch.nodeId ?? null,
228878
+ patch.title,
228879
+ patch.workspaceTitle ?? null,
228880
+ patch.actorId ?? null,
228881
+ patch.cardId ?? null,
228882
+ patch.item ? JSON.stringify(patch.item) : null,
228883
+ at
228884
+ ]
228885
+ );
228886
+ return (r.rowCount ?? 0) > 0;
228887
+ } catch (err) {
228888
+ if (err.code === "23505") return false;
228889
+ throw err;
228890
+ }
228891
+ }
228292
228892
  async close(id, closure) {
228293
228893
  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
228894
+ `UPDATE ${this.t} SET status = $2, closed_at = $3, closed_by = $4, result = $5, invalid_reason = $6, updated_at = $3,
228895
+ read_at = CASE WHEN $2 = 'handled' THEN COALESCE(read_at, $3::timestamptz) ELSE read_at END
228295
228896
  WHERE id = $1 AND status = 'pending'`,
228296
228897
  [
228297
228898
  id,
@@ -228331,6 +228932,15 @@ var init_notification_store = __esm({
228331
228932
  [recipient, [...ids2], at]
228332
228933
  );
228333
228934
  }
228935
+ async countUnread(recipient) {
228936
+ const r = await this.sql.query(
228937
+ `SELECT count(*) FILTER (WHERE status = 'pending')::int AS pending, count(*)::int AS "all"
228938
+ FROM ${this.t} WHERE recipient = $1 AND read_at IS NULL`,
228939
+ [recipient]
228940
+ );
228941
+ const row = r.rows[0] ?? {};
228942
+ return { pending: Number(row["pending"] ?? 0), all: Number(row["all"] ?? 0) };
228943
+ }
228334
228944
  async purgeBefore(cutoff) {
228335
228945
  const r = await this.sql.query(
228336
228946
  `DELETE FROM ${this.t}
@@ -229153,13 +229763,13 @@ function nameKey(name) {
229153
229763
  function cleanName(name) {
229154
229764
  return name.trim().replace(/\s+/g, " ");
229155
229765
  }
229156
- var import_node_fs15, import_node_path20, import_node_crypto51, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
229766
+ var import_node_fs15, import_node_path20, import_node_crypto53, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
229157
229767
  var init_tags = __esm({
229158
229768
  "../server/src/domains/collab/tags.ts"() {
229159
229769
  "use strict";
229160
229770
  import_node_fs15 = __toESM(require("node:fs"), 1);
229161
229771
  import_node_path20 = __toESM(require("node:path"), 1);
229162
- import_node_crypto51 = __toESM(require("node:crypto"), 1);
229772
+ import_node_crypto53 = __toESM(require("node:crypto"), 1);
229163
229773
  SEP = "::";
229164
229774
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
229165
229775
  prefixOf = (companyId) => `${companyId}${SEP}`;
@@ -229177,7 +229787,7 @@ var init_tags = __esm({
229177
229787
  if (!name) return null;
229178
229788
  if (this.entries(companyId).some((t) => nameKey(t.name) === nameKey(name))) return null;
229179
229789
  const tag = {
229180
- id: `tag-${import_node_crypto51.default.randomBytes(6).toString("hex")}`,
229790
+ id: `tag-${import_node_crypto53.default.randomBytes(6).toString("hex")}`,
229181
229791
  name,
229182
229792
  ...input.color?.trim() ? { color: input.color.trim() } : {},
229183
229793
  ...input.description?.trim() ? { description: input.description.trim() } : {}
@@ -230028,7 +230638,27 @@ function collabDomain(opts) {
230028
230638
  limit
230029
230639
  });
230030
230640
  const last = items.at(-1);
230031
- return { status: 200, body: { items, cursor: last && items.length === limit ? notificationCursor(last) : null } };
230641
+ const unread = await store.countUnread(req.auth.actor);
230642
+ const body2 = { items, cursor: last && items.length === limit ? notificationCursor(last) : null, unread };
230643
+ return { status: 200, body: body2 };
230644
+ });
230645
+ router.post("/api/notifications/read", async (req) => {
230646
+ if (!opts.resolveNotificationLedger) {
230647
+ return { status: 501, body: { error: { code: "not_implemented", message: "\u672C\u90E8\u7F72\u672A\u542F\u7528\u901A\u77E5\u53F0\u8D26" } } };
230648
+ }
230649
+ const ids2 = req.body?.ids;
230650
+ if (!Array.isArray(ids2) || ids2.length === 0 || ids2.length > 200 || !ids2.every((id) => typeof id === "string" && id.length > 0)) {
230651
+ return { status: 400, body: { error: { code: "bad_request", message: "ids \u5FC5\u987B\u662F 1\u2013200 \u4E2A\u901A\u77E5 id" } } };
230652
+ }
230653
+ let store;
230654
+ try {
230655
+ store = await opts.resolveNotificationLedger(req.auth.companyId);
230656
+ } catch (err) {
230657
+ 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)}`);
230658
+ }
230659
+ if (!store) return { status: 503, body: { error: { code: "unavailable", message: "\u901A\u77E5\u53F0\u8D26\u6682\u4E0D\u53EF\u7528" } } };
230660
+ await store.markRead(req.auth.actor, ids2, (/* @__PURE__ */ new Date()).toISOString());
230661
+ return { status: 200, body: { ok: true } };
230032
230662
  });
230033
230663
  const companyOf = (req) => req.auth.companyId ?? "company:default";
230034
230664
  const noTagStore = { status: 501, body: { error: { code: "not_implemented", message: "\u672C\u90E8\u7F72\u672A\u542F\u7528\u5DE5\u5355\u6807\u7B7E" } } };
@@ -230316,326 +230946,6 @@ var init_collab = __esm({
230316
230946
  }
230317
230947
  });
230318
230948
 
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
230949
  // ../server/src/domains/projects/index.ts
230640
230950
  function createProjectsDomain(opts) {
230641
230951
  const build = (companyId, deps) => {
@@ -230694,7 +231004,9 @@ function createProjectsDomain(opts) {
230694
231004
  service,
230695
231005
  ...opts.resolveCompany ? { resolveService } : {},
230696
231006
  ...opts.defaultBranchResolver ? { defaultBranchResolver: opts.defaultBranchResolver } : {},
230697
- ...opts.resolveMemberRole ? { resolveMemberRole: opts.resolveMemberRole } : {}
231007
+ ...opts.resolveMemberRole ? { resolveMemberRole: opts.resolveMemberRole } : {},
231008
+ ...opts.chatSessionsFor ? { chatSessionsFor: opts.chatSessionsFor } : {},
231009
+ ...opts.delegationChildIds ? { delegationChildIds: opts.delegationChildIds } : {}
230698
231010
  }),
230699
231011
  ...opts.resolveCompany ? { resolveService } : {}
230700
231012
  };
@@ -230721,11 +231033,11 @@ async function isEmployedElsewhere(store, companyId, accountId) {
230721
231033
  const memberships = await store.listMembershipsForAccount(accountId);
230722
231034
  return memberships.some((m2) => m2.companyId !== companyId);
230723
231035
  }
230724
- var import_node_crypto53, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
231036
+ var import_node_crypto54, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
230725
231037
  var init_service6 = __esm({
230726
231038
  "../server/src/domains/companies/service.ts"() {
230727
231039
  "use strict";
230728
- import_node_crypto53 = require("node:crypto");
231040
+ import_node_crypto54 = require("node:crypto");
230729
231041
  init_console_url();
230730
231042
  ROLES = ["owner", "admin", "member"];
230731
231043
  INVITABLE_ROLES = ["admin", "member"];
@@ -230817,7 +231129,7 @@ var init_service6 = __esm({
230817
231129
  const existing = await this.store.getAccountByEmail(normalized4);
230818
231130
  if (existing) return existing;
230819
231131
  const account = {
230820
- id: `actor:human:${(0, import_node_crypto53.randomUUID)()}`,
231132
+ id: `actor:human:${(0, import_node_crypto54.randomUUID)()}`,
230821
231133
  email: normalized4,
230822
231134
  name: name ?? normalized4,
230823
231135
  status: "active"
@@ -231026,7 +231338,7 @@ var init_service6 = __esm({
231026
231338
  }
231027
231339
  const nowMs = Date.parse(this.now());
231028
231340
  const invitation = {
231029
- id: `invitation:${(0, import_node_crypto53.randomUUID)()}`,
231341
+ id: `invitation:${(0, import_node_crypto54.randomUUID)()}`,
231030
231342
  companyId,
231031
231343
  email: mail,
231032
231344
  role,
@@ -231157,7 +231469,7 @@ var init_service6 = __esm({
231157
231469
  const nowMs = Date.parse(this.now());
231158
231470
  const link = {
231159
231471
  companyId,
231160
- token: (0, import_node_crypto53.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
231472
+ token: (0, import_node_crypto54.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
231161
231473
  role: INVITE_LINK_ROLE,
231162
231474
  createdAt: new Date(nowMs).toISOString(),
231163
231475
  expiresAt: new Date(nowMs + INVITE_LINK_TTL_MS).toISOString(),
@@ -231214,7 +231526,7 @@ var init_service6 = __esm({
231214
231526
  const reusable = existing.find((inv) => !this.isExpired(inv));
231215
231527
  if (reusable) return reusable;
231216
231528
  const invitation = {
231217
- id: `invitation:${(0, import_node_crypto53.randomUUID)()}`,
231529
+ id: `invitation:${(0, import_node_crypto54.randomUUID)()}`,
231218
231530
  companyId: link.companyId,
231219
231531
  email: mail,
231220
231532
  role: link.role,
@@ -231452,11 +231764,11 @@ var init_service6 = __esm({
231452
231764
  });
231453
231765
 
231454
231766
  // ../server/src/domains/companies/offboarding.ts
231455
- var import_node_crypto54, MemberOffboardingService;
231767
+ var import_node_crypto55, MemberOffboardingService;
231456
231768
  var init_offboarding = __esm({
231457
231769
  "../server/src/domains/companies/offboarding.ts"() {
231458
231770
  "use strict";
231459
- import_node_crypto54 = require("node:crypto");
231771
+ import_node_crypto55 = require("node:crypto");
231460
231772
  init_service6();
231461
231773
  MemberOffboardingService = class {
231462
231774
  store;
@@ -231528,7 +231840,7 @@ var init_offboarding = __esm({
231528
231840
  const { ownedAgents, inFlight } = await this.snapshot(companyId, accountId);
231529
231841
  const at = this.now();
231530
231842
  const started = await this.store.startMemberOffboarding({
231531
- operationId: `off:${(0, import_node_crypto54.randomUUID)()}`,
231843
+ operationId: `off:${(0, import_node_crypto55.randomUUID)()}`,
231532
231844
  companyId,
231533
231845
  accountId,
231534
231846
  initiatedBy,
@@ -232038,18 +232350,18 @@ var init_routes6 = __esm({
232038
232350
 
232039
232351
  // ../server/src/domains/companies/agent-credentials.ts
232040
232352
  function hashAgentToken(token, secret) {
232041
- return HASH_PREFIX + (0, import_node_crypto55.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
232353
+ return HASH_PREFIX + (0, import_node_crypto56.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
232042
232354
  }
232043
232355
  function safeEqual(a, b2) {
232044
232356
  const ba = Buffer.from(a);
232045
232357
  const bb = Buffer.from(b2);
232046
- return ba.length === bb.length && (0, import_node_crypto55.timingSafeEqual)(ba, bb);
232358
+ return ba.length === bb.length && (0, import_node_crypto56.timingSafeEqual)(ba, bb);
232047
232359
  }
232048
- var import_node_crypto55, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
232360
+ var import_node_crypto56, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
232049
232361
  var init_agent_credentials = __esm({
232050
232362
  "../server/src/domains/companies/agent-credentials.ts"() {
232051
232363
  "use strict";
232052
- import_node_crypto55 = require("node:crypto");
232364
+ import_node_crypto56 = require("node:crypto");
232053
232365
  init_service6();
232054
232366
  HASH_PREFIX = "ac1$";
232055
232367
  TOKEN_PREFIX = "oak_";
@@ -232074,7 +232386,7 @@ var init_agent_credentials = __esm({
232074
232386
  if (employee.status !== "active") {
232075
232387
  throw new CompanyError(409, "AGENT_LEFT", `\u6570\u5B57\u5458\u5DE5\u5DF2\u79BB\u5F00\uFF0C\u4E0D\u80FD\u7B7E\u53D1\u51ED\u8BC1\uFF1A${agentId}`);
232076
232388
  }
232077
- const token = `${TOKEN_PREFIX}${(0, import_node_crypto55.randomBytes)(32).toString("base64url")}`;
232389
+ const token = `${TOKEN_PREFIX}${(0, import_node_crypto56.randomBytes)(32).toString("base64url")}`;
232078
232390
  const credential = {
232079
232391
  tokenHash: hashAgentToken(token, this.secret),
232080
232392
  companyId,
@@ -232494,7 +232806,7 @@ function gatewayInstanceSlug(gatewayUrl) {
232494
232806
  }
232495
232807
  function connectInstanceName(gatewayUrl, token, companyId) {
232496
232808
  const scope = companyId?.trim();
232497
- const suffix = scope ? `c${(0, import_node_crypto56.createHash)("sha256").update(`${gatewayUrl}\0${scope}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
232809
+ const suffix = scope ? `c${(0, import_node_crypto57.createHash)("sha256").update(`${gatewayUrl}\0${scope}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
232498
232810
  const base = gatewayInstanceSlug(gatewayUrl);
232499
232811
  return base.slice(0, Math.max(1, 64 - suffix.length - 1)) + "-" + suffix;
232500
232812
  }
@@ -232823,12 +233135,12 @@ function buildNpxCmd(p2) {
232823
233135
  const prefix = daemonNpmPrefix();
232824
233136
  return `npm install -g --prefer-online --prefix ${prefix} ${DAEMON_NPM_PACKAGE}@latest && ${prefix}/bin/oasis daemon start --instance ${shellQuote(instanceName)} --server-ws ${shellQuote(p2.gatewayUrl)}${companyPart}${namePart} --pkg ${shellQuote(DAEMON_NPM_PACKAGE)} --upgrade --enroll-token ${shellQuote(p2.token)}`;
232825
233137
  }
232826
- var import_node_crypto56;
233138
+ var import_node_crypto57;
232827
233139
  var init_connect_script = __esm({
232828
233140
  "../server/src/domains/nodes/connect-script.ts"() {
232829
233141
  "use strict";
232830
233142
  init_src();
232831
- import_node_crypto56 = require("node:crypto");
233143
+ import_node_crypto57 = require("node:crypto");
232832
233144
  }
232833
233145
  });
232834
233146
 
@@ -233389,7 +233701,7 @@ function nodesDomain(deps) {
233389
233701
  }
233390
233702
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
233391
233703
  } else {
233392
- nodeId = `node-${(0, import_node_crypto57.randomUUID)().slice(0, 8)}`;
233704
+ nodeId = `node-${(0, import_node_crypto58.randomUUID)().slice(0, 8)}`;
233393
233705
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
233394
233706
  }
233395
233707
  const existingNode = await deps.nodeStore.getNode(nodeId);
@@ -233521,21 +233833,21 @@ function nodesDomain(deps) {
233521
233833
  return { status: 200, body: { override } };
233522
233834
  });
233523
233835
  router.get("/api/runtimes/:runtimeId/activity", async (req) => {
233524
- const found = await runtimeInScope(req);
233525
- if (found.denied) return found.denied;
233836
+ const found2 = await runtimeInScope(req);
233837
+ if (found2.denied) return found2.denied;
233526
233838
  const reader = await deps.runtimeRuns?.(req.auth.companyId);
233527
- const activity = reader ? await reader.runtimeRunActivity(found.runtimeId) : null;
233839
+ const activity = reader ? await reader.runtimeRunActivity(found2.runtimeId) : null;
233528
233840
  return { status: 200, body: { activity } };
233529
233841
  });
233530
233842
  router.get("/api/runtimes/:runtimeId/usage", async (req) => {
233531
- const found = await runtimeInScope(req);
233532
- if (found.denied) return found.denied;
233843
+ const found2 = await runtimeInScope(req);
233844
+ if (found2.denied) return found2.denied;
233533
233845
  const granularity = req.query.get("granularity") === "day" ? "day" : "hour";
233534
233846
  const window2 = parseRuntimeUsageWindow(req.query, granularity, Date.now());
233535
233847
  if (typeof window2 === "string") return { status: 400, body: { error: window2 } };
233536
233848
  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) } };
233849
+ const runs = reader ? await reader.listRuntimeRuns(found2.runtimeId, window2) : null;
233850
+ return { status: 200, body: { items: bucketRuntimeRuns(found2.runtimeId, runs ?? [], granularity) } };
233539
233851
  });
233540
233852
  router.get("/api/runtimes/:runtimeId/config", async (req) => {
233541
233853
  const runtimeId = req.params["runtimeId"];
@@ -233572,13 +233884,13 @@ function normalizeGatewayUrl(raw) {
233572
233884
  }
233573
233885
  }
233574
233886
  function newNodeId() {
233575
- return "node-" + (0, import_node_crypto57.randomUUID)().replace(/-/g, "").slice(0, 12);
233887
+ return "node-" + (0, import_node_crypto58.randomUUID)().replace(/-/g, "").slice(0, 12);
233576
233888
  }
233577
- var import_node_crypto57, import_node_fs16, import_node_url6, import_node_path21;
233889
+ var import_node_crypto58, import_node_fs16, import_node_url6, import_node_path21;
233578
233890
  var init_routes7 = __esm({
233579
233891
  "../server/src/domains/nodes/routes.ts"() {
233580
233892
  "use strict";
233581
- import_node_crypto57 = require("node:crypto");
233893
+ import_node_crypto58 = require("node:crypto");
233582
233894
  import_node_fs16 = require("node:fs");
233583
233895
  import_node_url6 = require("node:url");
233584
233896
  import_node_path21 = require("node:path");
@@ -233931,9 +234243,56 @@ var init_node_store = __esm({
233931
234243
  }
233932
234244
  });
233933
234245
 
234246
+ // ../server/src/domains/collab/task-notices.ts
234247
+ function taskNoticeRecipients(model, workorderId, snapshot, members, trigger) {
234248
+ const out = /* @__PURE__ */ new Set();
234249
+ for (const a of model.artifacts.values()) {
234250
+ if (a.workspace === workorderId && isHumanActorId(a.owner)) out.add(a.owner);
234251
+ }
234252
+ for (const r of snapshot?.requirements ?? []) if (isHumanActorId(r.reviewerActorId)) out.add(r.reviewerActorId);
234253
+ for (const r of snapshot?.reviews ?? []) if (isHumanActorId(r.reviewerActorId)) out.add(r.reviewerActorId);
234254
+ if (trigger) out.delete(trigger);
234255
+ return [...out].filter((id) => members.has(id)).sort();
234256
+ }
234257
+ function settleTrigger(events, accept) {
234258
+ const e = accept.event;
234259
+ if (e.by && isHumanActorId(e.by)) return e.by;
234260
+ const workId = e.workId;
234261
+ const before = events.filter((x2) => x2.seq <= accept.seq);
234262
+ for (let i = before.length - 1; i >= 0; i--) {
234263
+ const x2 = before[i].event;
234264
+ if (x2.kind === "review.response" && x2.targetWorkId === workId && x2.verdict === "approve" && x2.reviewerActorId && isHumanActorId(x2.reviewerActorId)) {
234265
+ return x2.reviewerActorId;
234266
+ }
234267
+ if (x2.kind === "work.conclude" && x2.workId === workId && isHumanActorId(before[i].actorId)) return before[i].actorId;
234268
+ }
234269
+ return void 0;
234270
+ }
234271
+ function isCreationHold(paused) {
234272
+ return paused.event.reason === WORKORDER_HOLD_AT_CREATE_REASON;
234273
+ }
234274
+ function endsCreationHold(pauses, resumed) {
234275
+ let last;
234276
+ for (const p2 of pauses) if (p2.seq < resumed.seq && p2.event.kind === "workorder.paused") last = p2;
234277
+ return last !== void 0 && isCreationHold(last);
234278
+ }
234279
+ var TASK_NOTICE_TITLE;
234280
+ var init_task_notices = __esm({
234281
+ "../server/src/domains/collab/task-notices.ts"() {
234282
+ "use strict";
234283
+ init_src();
234284
+ TASK_NOTICE_TITLE = {
234285
+ "workorder-settled": "\u4EFB\u52A1\u5DF2\u5B8C\u6210",
234286
+ "workorder-cancelled": "\u4EFB\u52A1\u5DF2\u53D6\u6D88",
234287
+ "workorder-paused": "\u4EFB\u52A1\u5DF2\u6682\u505C",
234288
+ "workorder-resumed": "\u4EFB\u52A1\u5DF2\u6062\u590D"
234289
+ };
234290
+ }
234291
+ });
234292
+
233934
234293
  // ../server/src/domains/collab/notification-ledger.ts
233935
234294
  function isLedgerItem(item) {
233936
- return item.actionRequired && LEDGER_KINDS.has(item.kind);
234295
+ return item.actionRequired && LEDGER_INBOX_KINDS.has(item.kind);
233937
234296
  }
233938
234297
  function generationOf(model, item) {
233939
234298
  if (item.kind === "rework") return item.revisionId;
@@ -233943,7 +234302,17 @@ function generationOf(model, item) {
233943
234302
  }
233944
234303
  return void 0;
233945
234304
  }
234305
+ function staffingRoleOf(model, item) {
234306
+ if (item.kind !== "needs-staffing" || !item.artifactId) return void 0;
234307
+ const owner = model.artifacts.get(item.artifactId)?.owner;
234308
+ return owner?.startsWith(PENDING_ROLE_PREFIX) ? owner.slice(PENDING_ROLE_PREFIX.length) : void 0;
234309
+ }
234310
+ function staffingRoleOfKey(sourceKey) {
234311
+ return STAFFING_ROLE_KEY.exec(sourceKey)?.[1];
234312
+ }
233946
234313
  function notificationSourceKey(model, item) {
234314
+ const role = staffingRoleOf(model, item);
234315
+ if (role && item.workspace) return `needs-staffing:${item.workspace}:role:${role}`;
233947
234316
  const base = inboxItemKey(item);
233948
234317
  if (!base) return void 0;
233949
234318
  const gen = generationOf(model, item);
@@ -233953,6 +234322,10 @@ function generationOfKey(sourceKey) {
233953
234322
  const at = sourceKey.lastIndexOf("@");
233954
234323
  return at >= 0 ? sourceKey.slice(at + 1) : void 0;
233955
234324
  }
234325
+ function workNumber(workId) {
234326
+ const m2 = /:(\d+)$/.exec(workId);
234327
+ return m2 ? Number(m2[1]) : void 0;
234328
+ }
233956
234329
  function nodeOfWork(workId) {
233957
234330
  if (!workId.startsWith("wk:")) return null;
233958
234331
  const body2 = workId.slice(3);
@@ -233981,38 +234354,41 @@ function classifyClosure(row, facts) {
233981
234354
  const o = facts.draftOutcome;
233982
234355
  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
234356
  }
233984
- const mine = resolvedByMe(row, evs, me, onNode);
233985
- if (mine) return mine;
234357
+ const evidence = [];
234358
+ const add = (f2) => {
234359
+ if (f2) evidence.push(f2);
234360
+ };
234361
+ add(resolvedByMe(row, evs, me, onNode));
233986
234362
  const sealCancel = evs.find((rec) => {
233987
234363
  const e = payload(rec, "plan.delete_node");
233988
234364
  return !!e && e.nodeId === node2 && e.sealReason === "cancelled";
233989
234365
  });
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);
234366
+ if (sealCancel) add({ rec: sealCancel, closure: invalidated(facts.workorderCancelled ? "workorder-cancelled" : "node-cancelled", at(sealCancel), sealCancel.actorId) });
233992
234367
  const deleted = evs.find((rec) => {
233993
234368
  const e = payload(rec, "plan.delete_node");
233994
- return !!e && e.nodeId === node2 && e.sealReason !== "accepted";
234369
+ return !!e && e.nodeId === node2 && e.sealReason !== "accepted" && e.sealReason !== "cancelled";
233995
234370
  });
233996
- if (deleted) return invalidated("node-cancelled", at(deleted), deleted.actorId);
233997
- const other = resolvedByOther(row, evs, me, onNode);
233998
- if (other) return other;
234371
+ if (deleted) add({ rec: deleted, closure: invalidated("node-cancelled", at(deleted), deleted.actorId) });
234372
+ add(resolvedByOther(row, evs, me, onNode));
233999
234373
  const gen = generationOfKey(row.sourceKey);
234000
234374
  if (gen) {
234375
+ const genNo = workNumber(gen);
234376
+ const isNewer = (workId) => workId !== gen && (genNo === void 0 || (workNumber(workId) ?? Number.POSITIVE_INFINITY) > genNo);
234001
234377
  const newer = evs.find((rec) => {
234002
234378
  const c = payload(rec, "work.create");
234003
- if (c && c.nodeId === node2 && c.workId !== gen) return true;
234379
+ if (c && c.nodeId === node2 && isNewer(c.workId)) return true;
234004
234380
  const s2 = payload(rec, "work.submit_output");
234005
- return !!s2 && nodeOfWork(s2.workId) === node2 && s2.workId !== gen;
234381
+ return !!s2 && nodeOfWork(s2.workId) === node2 && isNewer(s2.workId);
234006
234382
  });
234007
- if (newer) return invalidated("superseded", at(newer), newer.actorId);
234383
+ if (newer) add({ rec: newer, closure: invalidated("superseded", at(newer), newer.actorId) });
234008
234384
  }
234009
234385
  const reassign = evs.find((rec) => {
234010
234386
  const a = payload(rec, "plan.assign_actor");
234011
234387
  if (a && a.nodeId === node2 && a.assigneeActorId !== me && ownerKinds.has(row.kind)) return true;
234012
234388
  const r = payload(rec, "plan.update_review_requirements");
234013
- return !!r && r.nodeId === node2 && reviewKinds.has(row.kind);
234389
+ return !!r && r.nodeId === node2 && reviewKinds.has(row.kind) && !r.reviewers.some((x2) => x2.reviewerActorId === me);
234014
234390
  });
234015
- if (reassign) return invalidated("reassigned", at(reassign), reassign.actorId);
234391
+ if (reassign) add({ rec: reassign, closure: invalidated("reassigned", at(reassign), reassign.actorId) });
234016
234392
  const myReviewPrefix = gen ? `rv:${gen}:${me}:` : void 0;
234017
234393
  const withdrawn = evs.find((rec) => {
234018
234394
  const k2 = payload(rec, "review.kill") ?? payload(rec, "review.timeout");
@@ -234020,9 +234396,15 @@ function classifyClosure(row, facts) {
234020
234396
  if (item?.waitingRowId !== void 0 && k2.reviewId === item.waitingRowId) return true;
234021
234397
  return row.kind === "review-request" && !!myReviewPrefix && k2.reviewId.startsWith(myReviewPrefix);
234022
234398
  });
234023
- if (withdrawn) return invalidated("source-changed", at(withdrawn), withdrawn.actorId);
234399
+ if (withdrawn) add({ rec: withdrawn, closure: invalidated("source-changed", at(withdrawn), withdrawn.actorId) });
234400
+ const first = evidence.reduce((best, f2) => !best || f2.rec.seq < best.rec.seq ? f2 : best, null);
234401
+ if (first) return first.closure;
234402
+ if (facts.workorderCancelled) return invalidated("workorder-cancelled", facts.now);
234024
234403
  return null;
234025
234404
  }
234405
+ function staffingNodeMatches(row, nodeId) {
234406
+ return staffingRoleOfKey(row.sourceKey) !== void 0 || nodeId === row.nodeId;
234407
+ }
234026
234408
  function issueIdsOf(row) {
234027
234409
  const it = row.item;
234028
234410
  return new Set([it?.gap?.gapId, it?.escalationId, it?.annotationId].filter((x2) => !!x2));
@@ -234034,16 +234416,16 @@ function resolvedByMe(row, evs, me, onNode) {
234034
234416
  switch (row.kind) {
234035
234417
  case "review-request": {
234036
234418
  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);
234419
+ if (v2 && v2.reviewerActorId === me && onNode(rec) && (!gen || v2.targetWorkId === gen)) return found(rec, handled2(me, verdictWord(v2.verdict), rec.createdAt));
234038
234420
  const acc = payload(rec, "work.accept");
234039
- if (acc && acc.by === me && gen && acc.workId === gen) return handled2(me, "\u5DF2\u9A8C\u6536", rec.createdAt);
234421
+ if (acc && acc.by === me && gen && acc.workId === gen) return found(rec, handled2(me, "\u5DF2\u9A8C\u6536", rec.createdAt));
234040
234422
  const rej = payload(rec, "work.reject");
234041
- if (rej && rej.by === me && gen && rej.workId === gen) return handled2(me, "\u5DF2\u6253\u56DE", rec.createdAt);
234423
+ if (rej && rej.by === me && gen && rej.workId === gen) return found(rec, handled2(me, "\u5DF2\u6253\u56DE", rec.createdAt));
234042
234424
  break;
234043
234425
  }
234044
234426
  case "review-waiting": {
234045
234427
  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);
234428
+ if (v2 && v2.reviewerActorId === me && item?.waitingRowId && v2.reviewId === item.waitingRowId) return found(rec, handled2(me, verdictWord(v2.verdict), rec.createdAt));
234047
234429
  break;
234048
234430
  }
234049
234431
  case "work-waiting":
@@ -234051,24 +234433,24 @@ function resolvedByMe(row, evs, me, onNode) {
234051
234433
  case "rework":
234052
234434
  case "stale": {
234053
234435
  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);
234436
+ if (s2 && doerOf(rec) === me && onNode(rec) && !(row.kind === "rework" && s2.workId === gen)) return found(rec, handled2(me, "\u5DF2\u63D0\u4EA4", rec.createdAt));
234055
234437
  break;
234056
234438
  }
234057
234439
  case "pushback": {
234058
234440
  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);
234441
+ if (r && r.by === me && issueIdsOf(row).has(r.issueId)) return found(rec, handled2(me, "\u5DF2\u5904\u7406", rec.createdAt));
234060
234442
  const s2 = payload(rec, "work.submit_output");
234061
- if (s2 && doerOf(rec) === me && onNode(rec)) return handled2(me, "\u5DF2\u63D0\u4EA4", rec.createdAt);
234443
+ if (s2 && doerOf(rec) === me && onNode(rec)) return found(rec, handled2(me, "\u5DF2\u63D0\u4EA4", rec.createdAt));
234062
234444
  break;
234063
234445
  }
234064
234446
  case "gap-escalation": {
234065
234447
  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);
234448
+ if (r && r.by === me && issueIdsOf(row).has(r.issueId)) return found(rec, handled2(me, "\u5DF2\u89E3\u51B3", rec.createdAt));
234067
234449
  break;
234068
234450
  }
234069
234451
  case "needs-staffing": {
234070
234452
  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);
234453
+ if (a && rec.actorId === me && staffingNodeMatches(row, a.nodeId) && a.assigneeActorId) return found(rec, handled2(me, "\u5DF2\u6307\u6D3E", rec.createdAt));
234072
234454
  break;
234073
234455
  }
234074
234456
  default:
@@ -234085,21 +234467,25 @@ function resolvedByOther(row, evs, me, onNode) {
234085
234467
  switch (row.kind) {
234086
234468
  case "review-request":
234087
234469
  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
234470
  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);
234471
+ if (acc && gen && acc.workId === gen) {
234472
+ const decider = evs.filter((x2) => x2.seq <= rec.seq).reverse().find((x2) => {
234473
+ const vv = payload(x2, "review.response");
234474
+ return !!vv && vv.targetWorkId === gen && vv.reviewerActorId !== me;
234475
+ });
234476
+ return found(rec, invalidated("handled-by-other", rec.createdAt, decider ? doerOf(decider) : who));
234477
+ }
234092
234478
  break;
234093
234479
  }
234094
234480
  case "gap-escalation":
234095
234481
  case "pushback": {
234096
234482
  const r = payload(rec, "issue.resolve");
234097
- if (r && issueIdsOf(row).has(r.issueId)) return invalidated("handled-by-other", rec.createdAt, who);
234483
+ if (r && issueIdsOf(row).has(r.issueId)) return found(rec, invalidated("handled-by-other", rec.createdAt, who));
234098
234484
  break;
234099
234485
  }
234100
234486
  case "needs-staffing": {
234101
234487
  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);
234488
+ if (a && staffingNodeMatches(row, a.nodeId) && a.assigneeActorId) return found(rec, invalidated("handled-by-other", rec.createdAt, rec.actorId));
234103
234489
  break;
234104
234490
  }
234105
234491
  case "assigned":
@@ -234107,7 +234493,7 @@ function resolvedByOther(row, evs, me, onNode) {
234107
234493
  case "stale":
234108
234494
  case "work-waiting": {
234109
234495
  const acc = payload(rec, "work.accept");
234110
- if (acc && onNode(rec)) return invalidated("handled-by-other", rec.createdAt, who);
234496
+ if (acc && onNode(rec)) return found(rec, invalidated("handled-by-other", rec.createdAt, who));
234111
234497
  break;
234112
234498
  }
234113
234499
  default:
@@ -234119,24 +234505,13 @@ function resolvedByOther(row, evs, me, onNode) {
234119
234505
  function needsGrace(missingSinceMs, nowMs, graceMs) {
234120
234506
  return nowMs - missingSinceMs < graceMs;
234121
234507
  }
234122
- var LEDGER_KINDS, CLOSURE_EVENT_KINDS, payload, handled2, invalidated, ownerKinds, reviewKinds, verdictWord;
234508
+ var STAFFING_ROLE_KEY, CLOSURE_EVENT_KINDS, payload, handled2, invalidated, found, ownerKinds, reviewKinds, verdictWord;
234123
234509
  var init_notification_ledger = __esm({
234124
234510
  "../server/src/domains/collab/notification-ledger.ts"() {
234125
234511
  "use strict";
234126
234512
  init_src5();
234127
234513
  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
- ]);
234514
+ STAFFING_ROLE_KEY = /^needs-staffing:.+:role:([^:@]+)$/;
234140
234515
  CLOSURE_EVENT_KINDS = [
234141
234516
  "review.response",
234142
234517
  "review.kill",
@@ -234155,6 +234530,7 @@ var init_notification_ledger = __esm({
234155
234530
  payload = (rec, kind) => rec.event.kind === kind ? rec.event : null;
234156
234531
  handled2 = (closedBy, result, at) => ({ status: "handled", closedAt: at, closedBy, result });
234157
234532
  invalidated = (invalidReason, at, closedBy) => ({ status: "invalidated", closedAt: at, invalidReason, ...closedBy ? { closedBy } : {} });
234533
+ found = (rec, closure) => ({ rec, closure });
234158
234534
  ownerKinds = /* @__PURE__ */ new Set(["assigned", "rework", "stale", "pushback", "work-waiting"]);
234159
234535
  reviewKinds = /* @__PURE__ */ new Set(["review-request", "review-waiting"]);
234160
234536
  verdictWord = (verdict) => verdict === "approve" ? "\u5BA1\u6838\u901A\u8FC7" : verdict === "request_changes" || verdict === "reject" ? "\u8981\u6C42\u4FEE\u6539" : "\u5DF2\u5BA1\u6838";
@@ -234218,15 +234594,18 @@ function displayOf(model, item, recipient, reviewOf) {
234218
234594
  function sameDisplay(row, d) {
234219
234595
  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
234596
  }
234221
- var DAY2, EMPTY, EVENT_WINDOW_SLACK_MS, CLOSURE_EVENT_LIMIT, DRAFT_OUTCOME_TTL_MS, SLOW_RECONCILE_MS, rowKey, NotificationLedgerWorker;
234597
+ 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
234598
  var init_notification_reconciler = __esm({
234223
234599
  "../server/src/domains/collab/notification-reconciler.ts"() {
234224
234600
  "use strict";
234225
234601
  init_src5();
234602
+ init_src();
234226
234603
  init_inbox();
234227
234604
  init_waiting_inbox();
234228
234605
  init_node_state();
234229
234606
  init_review_activity();
234607
+ init_workorder_terminal();
234608
+ init_task_notices();
234230
234609
  init_notification_ledger();
234231
234610
  DAY2 = 24 * 36e5;
234232
234611
  EMPTY = { inserted: 0, refreshed: 0, closed: 0, pending: 0 };
@@ -234234,6 +234613,8 @@ var init_notification_reconciler = __esm({
234234
234613
  CLOSURE_EVENT_LIMIT = 400;
234235
234614
  DRAFT_OUTCOME_TTL_MS = 36e5;
234236
234615
  SLOW_RECONCILE_MS = 2e3;
234616
+ TASK_NOTICE_FRESH_MS = 15 * 6e4;
234617
+ TASK_NOTICE_EVENT_LIMIT = 500;
234237
234618
  rowKey = (recipient, sourceKey) => `${recipient}\0${sourceKey}`;
234238
234619
  NotificationLedgerWorker = class {
234239
234620
  constructor(deps) {
@@ -234256,7 +234637,7 @@ var init_notification_reconciler = __esm({
234256
234637
  state(companyId) {
234257
234638
  let st = this.states.get(companyId);
234258
234639
  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 };
234640
+ 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
234641
  this.states.set(companyId, st);
234261
234642
  }
234262
234643
  return st;
@@ -234375,6 +234756,14 @@ var init_notification_reconciler = __esm({
234375
234756
  const pending = await ctx.store.listPending();
234376
234757
  const byKey = new Map(pending.map((r) => [rowKey(r.recipient, r.sourceKey), r]));
234377
234758
  const markersOf = /* @__PURE__ */ new Map();
234759
+ const adopted = /* @__PURE__ */ new Set();
234760
+ const roleOfRow = (r) => {
234761
+ const fromKey = staffingRoleOfKey(r.sourceKey);
234762
+ if (fromKey) return fromKey;
234763
+ const owner = r.nodeId ? model.artifacts.get(r.nodeId)?.owner : void 0;
234764
+ return owner?.startsWith(PENDING_ROLE_PREFIX) ? owner.slice(PENDING_ROLE_PREFIX.length) : void 0;
234765
+ };
234766
+ const staffingOrphans = pending.filter((r) => r.kind === "needs-staffing" && !current.has(rowKey(r.recipient, r.sourceKey)));
234378
234767
  for (const [k2, c] of current) {
234379
234768
  const display = displayOf(model, c.item, c.recipient, reviewOf);
234380
234769
  const row = byKey.get(k2);
@@ -234387,6 +234776,15 @@ var init_notification_reconciler = __esm({
234387
234776
  }
234388
234777
  continue;
234389
234778
  }
234779
+ const role = staffingRoleOf(model, c.item);
234780
+ const orphan = role ? staffingOrphans.find((r) => !adopted.has(r.id) && r.recipient === c.recipient && r.workorderId === c.item.workspace && roleOfRow(r) === role) : void 0;
234781
+ if (orphan && await ctx.store.rekey(orphan.id, c.sourceKey, { ...display, ...c.item.artifactId ? { nodeId: c.item.artifactId } : {} }, nowIso)) {
234782
+ adopted.add(orphan.id);
234783
+ st.missingSince.delete(orphan.id);
234784
+ st.lastSeenAt.set(orphan.id, nowIso);
234785
+ stats.refreshed++;
234786
+ continue;
234787
+ }
234390
234788
  const readAt = st.primed ? void 0 : await this.alreadyReadAt(ctx, c.recipient, c.item, markersOf, nowIso);
234391
234789
  const sinceOk = Number.isFinite(Date.parse(c.item.since));
234392
234790
  const enteredAt = st.primed || !sinceOk ? nowIso : new Date(c.item.since).toISOString();
@@ -234409,7 +234807,7 @@ var init_notification_reconciler = __esm({
234409
234807
  const cancelledOf = /* @__PURE__ */ new Map();
234410
234808
  let waitingForGrace = false;
234411
234809
  for (const row of pending) {
234412
- if (current.has(rowKey(row.recipient, row.sourceKey))) continue;
234810
+ if (current.has(rowKey(row.recipient, row.sourceKey)) || adopted.has(row.id)) continue;
234413
234811
  const closure = await this.judge(st, ctx, model, row, nowMs, nowIso, eventsOf, cancelledOf);
234414
234812
  if (!closure) {
234415
234813
  waitingForGrace = true;
@@ -234420,6 +234818,7 @@ var init_notification_reconciler = __esm({
234420
234818
  st.lastSeenAt.delete(row.id);
234421
234819
  }
234422
234820
  stats.pending = current.size;
234821
+ stats.notices = await this.reconcileTaskNotices(st, ctx, model, nowMs, loadSnapshot2);
234423
234822
  if (nowMs - st.lastPurgeAt > (this.deps.purgeEveryMs ?? 6 * 36e5)) {
234424
234823
  st.lastPurgeAt = nowMs;
234425
234824
  const purged = await ctx.store.purgeBefore(new Date(nowMs - (this.deps.retentionMs ?? 90 * DAY2)).toISOString());
@@ -234458,6 +234857,74 @@ var init_notification_reconciler = __esm({
234458
234857
  if (needsGrace(since, nowMs, this.graceMs)) return null;
234459
234858
  return { status: "invalidated", closedAt: nowIso, invalidReason: "source-changed" };
234460
234859
  }
234860
+ /**
234861
+ * 任务告知(第 2 步 B)。完成 / 取消从当前状态现算(与结案播报、任务列表同一判据),暂停 / 恢复读引擎事件。
234862
+ *
234863
+ * **不补历史**:进程第一次对账时(上线当天、重启后)只发最近 {@link TASK_NOTICE_FRESH_MS} 内发生的变化——
234864
+ * 早就结束的任务不能在上线那一刻集体「任务已完成」一遍。之后每拍新看到的变化都发。
234865
+ * 来源键 = `<种类>:<工单>@<那件事的 seq>`:同一件事重启后再算一遍撞唯一约束即收手;任务重开后再完成是新的一件事。
234866
+ */
234867
+ async reconcileTaskNotices(st, ctx, model, nowMs, loadSnapshot2) {
234868
+ const store = ctx.engineStore;
234869
+ if (!store) return 0;
234870
+ const members = new Set(ctx.humans);
234871
+ const titleOf = workspaceTitleMap(model);
234872
+ const fresh = (iso7) => nowMs - Date.parse(iso7) <= TASK_NOTICE_FRESH_MS;
234873
+ let sent = 0;
234874
+ const emit = async (workorderId, kind, rec, trigger) => {
234875
+ const recipients = taskNoticeRecipients(model, workorderId, await loadSnapshot2(workorderId), members, trigger);
234876
+ const title = titleOf.get(workorderId);
234877
+ for (const recipient of recipients) {
234878
+ await ctx.store.insert({
234879
+ recipient,
234880
+ category: "task-notice",
234881
+ sourceKey: `${kind}:${workorderId}@${rec.seq}`,
234882
+ kind,
234883
+ workorderId,
234884
+ title: TASK_NOTICE_TITLE[kind],
234885
+ ...title ? { workspaceTitle: title } : {},
234886
+ ...trigger ? { actorId: trigger } : {},
234887
+ enteredAt: new Date(rec.createdAt).toISOString()
234888
+ });
234889
+ sent++;
234890
+ }
234891
+ };
234892
+ const pauses = await store.transaction((tx) => tx.listEvents(void 0, st.noticeSeq, TASK_NOTICE_EVENT_LIMIT, ["workorder.paused", "workorder.resumed"]));
234893
+ for (const rec of pauses) {
234894
+ st.noticeSeq = Math.max(st.noticeSeq, rec.seq);
234895
+ if (!st.primed && !fresh(rec.createdAt)) continue;
234896
+ const e = rec.event;
234897
+ const paused = e.kind === "workorder.paused";
234898
+ if (paused ? isCreationHold(rec) : endsCreationHold(
234899
+ await store.transaction((tx) => tx.listEvents(rec.workorderId, 0, TASK_NOTICE_EVENT_LIMIT, ["workorder.paused"])),
234900
+ rec
234901
+ )) continue;
234902
+ await emit(rec.workorderId, paused ? "workorder-paused" : "workorder-resumed", rec, e.by ?? rec.actorId);
234903
+ }
234904
+ const terminals = computeWorkorderTerminals(model);
234905
+ const now = /* @__PURE__ */ new Set();
234906
+ for (const t of terminals) {
234907
+ const kind = t.kind;
234908
+ now.add(t.workOrderId);
234909
+ if (st.terminals.get(t.workOrderId) === kind) continue;
234910
+ st.terminals.set(t.workOrderId, kind);
234911
+ if (!st.primed && !(t.terminalAt && fresh(t.terminalAt))) continue;
234912
+ const cancelled = kind === "workorder-cancelled";
234913
+ const evs = await store.transaction((tx) => tx.listEvents(
234914
+ t.workOrderId,
234915
+ 0,
234916
+ TASK_NOTICE_EVENT_LIMIT,
234917
+ cancelled ? ["plan.delete_node"] : ["work.accept", "review.response", "work.conclude"]
234918
+ ));
234919
+ const decisive = [...evs].reverse().find((rec) => cancelled ? rec.event.sealReason === "cancelled" : rec.event.kind === "work.accept");
234920
+ if (!decisive) continue;
234921
+ if (!st.primed && !fresh(decisive.createdAt)) continue;
234922
+ const trigger = cancelled ? decisive.actorId : settleTrigger(evs, decisive);
234923
+ await emit(t.workOrderId, kind, decisive, trigger && !isSystemActorId(trigger) ? trigger : void 0);
234924
+ }
234925
+ for (const wid of [...st.terminals.keys()]) if (!now.has(wid)) st.terminals.delete(wid);
234926
+ return sent;
234927
+ }
234461
234928
  /** 台账上线前已经看过的待办不重新亮红点:沿用 `/api/inbox` 的未读判据(基线 + 逐条水位)。 */
234462
234929
  async alreadyReadAt(ctx, recipient, item, cache, nowIso) {
234463
234930
  if (!ctx.markers) return void 0;
@@ -234614,7 +235081,7 @@ function createBundle(root, target, input) {
234614
235081
  }
234615
235082
  }
234616
235083
  const bytes2 = fs26.statSync(absBundle).size;
234617
- const digest = (0, import_node_crypto58.createHash)("sha256").update(fs26.readFileSync(absBundle)).digest("hex");
235084
+ const digest = (0, import_node_crypto59.createHash)("sha256").update(fs26.readFileSync(absBundle)).digest("hex");
234618
235085
  const relBase = `inputs/git-bundles/${safeRepo}-${commit.slice(0, 12)}`;
234619
235086
  return {
234620
235087
  absoluteBundlePath: absBundle,
@@ -234710,12 +235177,12 @@ function classifyGitError(fallback, err, where) {
234710
235177
  function oneLine(msg) {
234711
235178
  return msg.split("\n").map((s2) => s2.trim()).filter(Boolean).slice(0, 3).join(" ");
234712
235179
  }
234713
- var import_node_child_process17, import_node_crypto58, fs26, os7, path25, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
235180
+ var import_node_child_process17, import_node_crypto59, fs26, os7, path25, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
234714
235181
  var init_readonly_bundle = __esm({
234715
235182
  "../server/src/git/readonly-bundle.ts"() {
234716
235183
  "use strict";
234717
235184
  import_node_child_process17 = require("node:child_process");
234718
- import_node_crypto58 = require("node:crypto");
235185
+ import_node_crypto59 = require("node:crypto");
234719
235186
  fs26 = __toESM(require("node:fs"), 1);
234720
235187
  os7 = __toESM(require("node:os"), 1);
234721
235188
  path25 = __toESM(require("node:path"), 1);
@@ -234735,7 +235202,7 @@ var init_readonly_bundle = __esm({
234735
235202
 
234736
235203
  // ../server/src/domains/collab/create-seeded-workorder.ts
234737
235204
  function makeSeededWorkorderCreator(deps) {
234738
- const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto59.randomUUID)().slice(0, 8)}`);
235205
+ const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto60.randomUUID)().slice(0, 8)}`);
234739
235206
  return async (input) => {
234740
235207
  const company = input.companyId && deps.resolveCompany ? await deps.resolveCompany(input.companyId) : void 0;
234741
235208
  const kernel = company?.kernel ?? deps.kernel;
@@ -234822,13 +235289,13 @@ function makeSeededWorkorderCreator(deps) {
234822
235289
  }
234823
235290
  if (input.dispatch === false && artifactState) {
234824
235291
  await artifactState.setWorkspaceDispatchHold(workspace, true);
234825
- await kernel.pauseWorkorder?.(workspace, input.actor);
235292
+ await kernel.pauseWorkorder?.(workspace, input.actor, WORKORDER_HOLD_AT_CREATE_REASON);
234826
235293
  }
234827
235294
  return { workspace, rootArtifactId: briefId, spawned: planned.spawned };
234828
235295
  };
234829
235296
  }
234830
235297
  function workspaceForKey(key) {
234831
- return `ws:wo-${(0, import_node_crypto59.createHash)("sha256").update(key).digest("hex").slice(0, 8)}`;
235298
+ return `ws:wo-${(0, import_node_crypto60.createHash)("sha256").update(key).digest("hex").slice(0, 8)}`;
234832
235299
  }
234833
235300
  function existingWorkorder(kernel, workspace) {
234834
235301
  const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === workspace);
@@ -234841,12 +235308,13 @@ function existingWorkorder(kernel, workspace) {
234841
235308
  spawned: arts.map((a) => ({ id: a.id, type: a.type, owner: a.owner }))
234842
235309
  };
234843
235310
  }
234844
- var import_node_crypto59, enc2;
235311
+ var import_node_crypto60, enc2;
234845
235312
  var init_create_seeded_workorder = __esm({
234846
235313
  "../server/src/domains/collab/create-seeded-workorder.ts"() {
234847
235314
  "use strict";
234848
- import_node_crypto59 = require("node:crypto");
235315
+ import_node_crypto60 = require("node:crypto");
234849
235316
  init_uncategorized_project();
235317
+ init_src();
234850
235318
  init_planner();
234851
235319
  enc2 = (s2) => new TextEncoder().encode(s2);
234852
235320
  }
@@ -235703,6 +236171,37 @@ var init_trace_health = __esm({
235703
236171
  }
235704
236172
  });
235705
236173
 
236174
+ // ../server/src/domains/trace/subagent-events.ts
236175
+ function subagentRunEventFromLive(event) {
236176
+ if (event.itemType !== "control") return null;
236177
+ const payload2 = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : void 0;
236178
+ if (payload2?.code !== SUBAGENT_TASK_CODE || typeof payload2.toolCallId !== "string") return null;
236179
+ const raw = payload2.task;
236180
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
236181
+ const task = raw;
236182
+ const cost = task.estimatedCostUsdMicros ?? estimateSubagentCostUsdMicros(task.usage);
236183
+ const enriched = cost !== void 0 ? { ...task, estimatedCostUsdMicros: cost } : task;
236184
+ const eventType = task.status === "running" ? "sub_agent.started" : "sub_agent.finished";
236185
+ return {
236186
+ eventType,
236187
+ stream: "tool",
236188
+ ...task.status === "failed" ? { level: "error" } : {},
236189
+ ...task.description ? { message: task.description } : {},
236190
+ payload: {
236191
+ kind: "subagent_task",
236192
+ raw: { code: SUBAGENT_TASK_CODE, toolCallId: payload2.toolCallId, task: enriched }
236193
+ },
236194
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
236195
+ };
236196
+ }
236197
+ var init_subagent_events = __esm({
236198
+ "../server/src/domains/trace/subagent-events.ts"() {
236199
+ "use strict";
236200
+ init_src();
236201
+ init_subagent_cost();
236202
+ }
236203
+ });
236204
+
235706
236205
  // ../server/src/domains/trace/index.ts
235707
236206
  function createTraceDomain(opts) {
235708
236207
  const service = new TraceService({ store: opts.store });
@@ -235735,6 +236234,7 @@ var init_trace2 = __esm({
235735
236234
  init_chat_parts();
235736
236235
  init_trace_health();
235737
236236
  init_resilient_appender();
236237
+ init_subagent_events();
235738
236238
  }
235739
236239
  });
235740
236240
 
@@ -236242,12 +236742,12 @@ function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, ope
236242
236742
  if (attempt.status === "no-output") return handoff?.unresolvedIssues.length ? "blocked" : "no_change";
236243
236743
  return "failed";
236244
236744
  }
236245
- var import_node_crypto60, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
236745
+ var import_node_crypto61, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
236246
236746
  var init_service8 = __esm({
236247
236747
  "../server/src/domains/execution-continuity/service.ts"() {
236248
236748
  "use strict";
236249
236749
  init_src();
236250
- import_node_crypto60 = require("node:crypto");
236750
+ import_node_crypto61 = require("node:crypto");
236251
236751
  init_src5();
236252
236752
  BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
236253
236753
  HANDOFF_OUTCOMES2 = /* @__PURE__ */ new Set(["succeeded", "no-output", "failed", "cancelled", "timeout", "orphaned"]);
@@ -236478,7 +236978,7 @@ var init_service8 = __esm({
236478
236978
  if (typeof expectedHash !== "string" || !this.readBundleFile) return null;
236479
236979
  const injectedResume = this.readBundleFile(attempt.id, "execution/RESUME.md");
236480
236980
  if (!injectedResume) return null;
236481
- const actualHash = (0, import_node_crypto60.createHash)("sha256").update(injectedResume).digest("hex");
236981
+ const actualHash = (0, import_node_crypto61.createHash)("sha256").update(injectedResume).digest("hex");
236482
236982
  if (actualHash !== expectedHash) return null;
236483
236983
  const recoverySnapshot = parseResumePackSnapshot(injectedResume);
236484
236984
  if (!recoverySnapshot || recoverySnapshot.scenario !== "redispatch" || !recoverySnapshot.previousAttempt || recoverySnapshot.node.nodeId !== attempt.nodeId || recoverySnapshot.jobKey !== attempt.jobKey || recoverySnapshot.part !== attempt.part) return null;
@@ -237385,7 +237885,12 @@ async function buildChatProjectBundle(projectId2, projects, readBlob) {
237385
237885
  "",
237386
237886
  "\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
237887
  ].join("\n");
237388
- return { files, binaryFiles, contextLines };
237888
+ return {
237889
+ files,
237890
+ binaryFiles,
237891
+ contextLines,
237892
+ project: { id: project.id, name: project.name, ...project.description ? { description: project.description } : {} }
237893
+ };
237389
237894
  }
237390
237895
  var textFile, safeProjectPath;
237391
237896
  var init_project_bundle = __esm({
@@ -237400,6 +237905,10 @@ var init_project_bundle = __esm({
237400
237905
  });
237401
237906
 
237402
237907
  // ../server/src/domains/chat-sessions/index.ts
237908
+ function sessionProjectIdToStore(projectId2) {
237909
+ if (typeof projectId2 !== "string" || projectId2.trim() === "") return null;
237910
+ return isUncategorizedProjectId(projectId2) ? null : projectId2;
237911
+ }
237403
237912
  function parseDurationMs(raw) {
237404
237913
  if (!raw) return null;
237405
237914
  const m2 = /^(\d+)\s*(ms|s|m|h|d)?$/i.exec(raw.trim());
@@ -237573,7 +238082,7 @@ function createChatSessionsDomain(opts) {
237573
238082
  if (!text5.trim()) throw new ApiError(400, "BAD_REQUEST", "text required");
237574
238083
  const now = Date.now();
237575
238084
  sweepDrafts(now);
237576
- const id = `cd_${(0, import_node_crypto61.randomUUID)()}`;
238085
+ const id = `cd_${(0, import_node_crypto62.randomUUID)()}`;
237577
238086
  drafts.set(id, { text: text5, actor: req.auth.actor, expiresAt: now + DRAFT_TTL_MS });
237578
238087
  return { status: 201, body: { id, expiresInMs: DRAFT_TTL_MS } };
237579
238088
  });
@@ -237625,7 +238134,7 @@ function createChatSessionsDomain(opts) {
237625
238134
  const runtimeId = await currentRuntimeId(body2.aiActorId) ?? "unknown";
237626
238135
  const now = (/* @__PURE__ */ new Date()).toISOString();
237627
238136
  const session = {
237628
- id: (0, import_node_crypto61.randomUUID)(),
238137
+ id: (0, import_node_crypto62.randomUUID)(),
237629
238138
  humanActorId: req.auth.actor,
237630
238139
  // 注:这里**刻意不写 companyId**。新会话要不要带公司归属,按 ws:wo-6d588f8e 的 ADR
237631
238140
  // (每公司 schema、最终移除表内 company 列)统一定,不在本节点自行发明。
@@ -237638,7 +238147,7 @@ function createChatSessionsDomain(opts) {
237638
238147
  // 决策 0030 缝合:绑定是会话的语义属性(工单频道/问题会话给、脑暴省略)——发消息端据它带 workspace,
237639
238148
  // 两个入口页面行为一致。区别于 linkWorkOrder(出处关联,建单脑暴也有、不构成绑定)。
237640
238149
  workspace: body2.workspace ?? null,
237641
- projectId: body2.projectId ?? null,
238150
+ projectId: sessionProjectIdToStore(body2.projectId),
237642
238151
  // 会话级模型覆盖(口径 A):建会话时就带上选择;空串/空白归 null = 沿用旧解析,不写员工全局配置。
237643
238152
  model: normalizeModel(body2.model),
237644
238153
  // ADR-0058:分析会话指向被分析 run;非空即"这是分析会话"(不进主聊天列表、进 trace 分析视图)。
@@ -237707,7 +238216,7 @@ function createChatSessionsDomain(opts) {
237707
238216
  ...body2.runtimeId !== void 0 ? { runtimeId: body2.runtimeId } : {},
237708
238217
  ...Object.prototype.hasOwnProperty.call(body2, "runtimeSessionId") ? { runtimeSessionId: body2.runtimeSessionId } : {},
237709
238218
  ...body2.title !== void 0 ? { title: body2.title } : {},
237710
- ...Object.prototype.hasOwnProperty.call(body2, "projectId") ? { projectId: body2.projectId } : {},
238219
+ ...Object.prototype.hasOwnProperty.call(body2, "projectId") ? { projectId: sessionProjectIdToStore(body2.projectId) } : {},
237711
238220
  // 会话级模型覆盖:显式传 null / 空串 = 清除覆盖(回到旧解析);不传这一项 = 不动它。
237712
238221
  ...Object.prototype.hasOwnProperty.call(body2, "model") ? { model: normalizeModel(body2.model) } : {},
237713
238222
  // 只有显式传了才写——改标题 / 换项目 / 换模型都不是「有新动静」,不该顶掉列表顺序与已读水位。
@@ -237731,7 +238240,7 @@ function createChatSessionsDomain(opts) {
237731
238240
  if (body2.role !== "user" && body2.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
237732
238241
  const role = body2.role;
237733
238242
  const msg = await store2.appendMessage({
237734
- id: (0, import_node_crypto61.randomUUID)(),
238243
+ id: (0, import_node_crypto62.randomUUID)(),
237735
238244
  sessionId: req.params.id,
237736
238245
  role,
237737
238246
  content: role === "user" ? stripInjectedChatContext(body2.content) : body2.content,
@@ -237772,7 +238281,7 @@ function createChatSessionsDomain(opts) {
237772
238281
  const known = key ? cur.find((x2) => x2.clientKey === key) : void 0;
237773
238282
  if (!known && cur.length >= QUEUE_MAX) throw new ApiError(409, "QUEUE_FULL", `\u961F\u5217\u5DF2\u6EE1\uFF08\u4E0A\u9650 ${QUEUE_MAX} \u6761\uFF09`);
237774
238283
  const item = await store.enqueuePendingMessage({
237775
- id: (0, import_node_crypto61.randomUUID)(),
238284
+ id: (0, import_node_crypto62.randomUUID)(),
237776
238285
  chatSessionId: sid,
237777
238286
  text: text5,
237778
238287
  ...attachments.length ? { attachments } : {},
@@ -238095,7 +238604,7 @@ function createChatSessionsDomain(opts) {
238095
238604
  const outcome = await bridge.read(
238096
238605
  loc.nodeId,
238097
238606
  { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: req.query?.get("path") ?? "" },
238098
- WORKDIR_READ_MAX_BYTES
238607
+ FILE_CONTENT_MAX_BYTES
238099
238608
  );
238100
238609
  if (outcome.ok) {
238101
238610
  const buf = Buffer.from(outcome.contentBase64, "base64");
@@ -238223,11 +238732,11 @@ function createChatSessionsDomain(opts) {
238223
238732
  });
238224
238733
  };
238225
238734
  }
238226
- var import_node_crypto61, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS, WORKDIR_READ_MAX_BYTES;
238227
- var init_chat_sessions = __esm({
238735
+ var import_node_crypto62, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS;
238736
+ var init_chat_sessions2 = __esm({
238228
238737
  "../server/src/domains/chat-sessions/index.ts"() {
238229
238738
  "use strict";
238230
- import_node_crypto61 = require("node:crypto");
238739
+ import_node_crypto62 = require("node:crypto");
238231
238740
  init_src();
238232
238741
  init_chat_session();
238233
238742
  init_files_view();
@@ -238244,10 +238753,10 @@ var init_chat_sessions = __esm({
238244
238753
  init_company_data_store_router();
238245
238754
  init_workorder_terminal();
238246
238755
  init_session_workdir();
238756
+ init_uncategorized_project();
238247
238757
  init_project_bundle();
238248
238758
  DEFAULT_SNAPSHOT_ITEMS = 400;
238249
238759
  MAX_SNAPSHOT_ITEMS = 1e3;
238250
- WORKDIR_READ_MAX_BYTES = 2 * 1024 * 1024;
238251
238760
  }
238252
238761
  });
238253
238762
 
@@ -239632,7 +240141,7 @@ function busyError(chatSessionId, blockedBy) {
239632
240141
  return `\u76EE\u6807\u4F1A\u8BDD ${chatSessionId} \u6B63\u6709\u4E00\u8F6E\u5728\u8DD1${blockedBy ? `\uFF08${blockedBy}\uFF09` : ""}\u2014\u2014\u672C\u6B21\u89E6\u53D1\u5DF2\u4E22\u5F03\uFF0C\u4E0D\u6392\u961F\u3001\u4E0D\u8865\u8DD1`;
239633
240142
  }
239634
240143
  function genWebhookToken() {
239635
- return `owt_${(0, import_node_crypto62.randomBytes)(32).toString("base64url")}`;
240144
+ return `owt_${(0, import_node_crypto63.randomBytes)(32).toString("base64url")}`;
239636
240145
  }
239637
240146
  function validateTitleTemplate(tpl) {
239638
240147
  const residue = tpl.replace(DATE_TOKEN, "");
@@ -239688,11 +240197,11 @@ function appendSourceNote(brief, automation, source, atIso) {
239688
240197
  ---
239689
240198
  \u672C\u5DE5\u5355\u7531\u81EA\u52A8\u5316\u300C${automation.name}\u300D\u4E8E ${atIso} \u89E6\u53D1\uFF08\u6765\u6E90\uFF1A${source}\uFF09\u3002\u5F00\u5DE5\u540E\u8BF7\u628A\u5DE5\u5355\u6807\u9898\u6539\u6210\u51C6\u786E\u53CD\u6620\u5B9E\u9645\u5DE5\u4F5C\u7684\u63CF\u8FF0\u3002`;
239690
240199
  }
239691
- var import_node_crypto62, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
240200
+ var import_node_crypto63, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
239692
240201
  var init_service9 = __esm({
239693
240202
  "../server/src/domains/automations/service.ts"() {
239694
240203
  "use strict";
239695
- import_node_crypto62 = require("node:crypto");
240204
+ import_node_crypto63 = require("node:crypto");
239696
240205
  init_src();
239697
240206
  init_registry3();
239698
240207
  init_attribution();
@@ -240506,11 +241015,11 @@ function verifySignature(rawBody, headers, secret) {
240506
241015
  if (!secret) return "not_required";
240507
241016
  const given = header1(headers, "x-hub-signature-256");
240508
241017
  if (!given) return "missing";
240509
- const expected = `sha256=${(0, import_node_crypto63.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
241018
+ const expected = `sha256=${(0, import_node_crypto64.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
240510
241019
  const a = Buffer.from(given);
240511
241020
  const b2 = Buffer.from(expected);
240512
241021
  if (a.length !== b2.length) return "invalid";
240513
- return (0, import_node_crypto63.timingSafeEqual)(a, b2) ? "valid" : "invalid";
241022
+ return (0, import_node_crypto64.timingSafeEqual)(a, b2) ? "valid" : "invalid";
240514
241023
  }
240515
241024
  function eventAllowed(filters, envelope) {
240516
241025
  if (!filters || filters.length === 0) return true;
@@ -240540,11 +241049,11 @@ function splitEvent(envelope) {
240540
241049
  }
240541
241050
  return { eventName, actionCandidates: candidates };
240542
241051
  }
240543
- var import_node_crypto63, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
241052
+ var import_node_crypto64, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
240544
241053
  var init_webhook = __esm({
240545
241054
  "../server/src/domains/automations/webhook.ts"() {
240546
241055
  "use strict";
240547
- import_node_crypto63 = require("node:crypto");
241056
+ import_node_crypto64 = require("node:crypto");
240548
241057
  MAX_BODY_BYTES = 256 * 1024;
240549
241058
  AutomationWebhookService = class {
240550
241059
  store;
@@ -241140,11 +241649,11 @@ function automationsDomain(opts) {
241140
241649
  });
241141
241650
  router.get("/api/automations/:id/runs/:runId", async (req) => {
241142
241651
  const automation = await mustGet(req);
241143
- const found = await service.getRun(req.params.runId);
241144
- if (!found || found.automationId !== automation.id) {
241652
+ const found2 = await service.getRun(req.params.runId);
241653
+ if (!found2 || found2.automationId !== automation.id) {
241145
241654
  throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u6761\u8FD0\u884C\u8BB0\u5F55\uFF1A${req.params.runId}`);
241146
241655
  }
241147
- return { status: 200, body: found };
241656
+ return { status: 200, body: found2 };
241148
241657
  });
241149
241658
  router.get("/api/automations/:id/deliveries", async (req) => {
241150
241659
  const automation = await mustGet(req);
@@ -241700,7 +242209,7 @@ function playbooksDomain(opts) {
241700
242209
  opts.overrides.set(companyOf(req), ref2, nodeKey, nextOverride);
241701
242210
  try {
241702
242211
  await opts.audit?.({
241703
- id: `reg_${(0, import_node_crypto64.randomUUID)()}`,
242212
+ id: `reg_${(0, import_node_crypto65.randomUUID)()}`,
241704
242213
  actor: req.auth.actor,
241705
242214
  kind: "registry_change",
241706
242215
  target: `playbook:${ref2}#${nodeKey}`,
@@ -241717,11 +242226,11 @@ function playbooksDomain(opts) {
241717
242226
  });
241718
242227
  };
241719
242228
  }
241720
- var import_node_crypto64;
242229
+ var import_node_crypto65;
241721
242230
  var init_routes12 = __esm({
241722
242231
  "../server/src/domains/playbooks/routes.ts"() {
241723
242232
  "use strict";
241724
- import_node_crypto64 = require("node:crypto");
242233
+ import_node_crypto65 = require("node:crypto");
241725
242234
  init_router();
241726
242235
  init_registry3();
241727
242236
  init_planner();
@@ -241835,17 +242344,17 @@ function completionFingerprint(workspace, members) {
241835
242344
  workspace,
241836
242345
  members: [...members].sort((a, b2) => a.artifactId < b2.artifactId ? -1 : a.artifactId > b2.artifactId ? 1 : 0).map((m2) => ({ artifactId: m2.artifactId, currentRevisionId: m2.currentRevisionId ?? null }))
241837
242346
  });
241838
- return (0, import_node_crypto65.createHash)("sha256").update(canonical).digest("hex");
242347
+ return (0, import_node_crypto66.createHash)("sha256").update(canonical).digest("hex");
241839
242348
  }
241840
242349
  function eventConsumptionKey(args) {
241841
242350
  const raw = `project-event:${EVENT_KEY_VERSION}:${args.triggerId}:${args.event}:${args.workspace}:${args.fingerprint}`;
241842
- return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto65.createHash)("sha256").update(raw).digest("hex")}`;
242351
+ return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto66.createHash)("sha256").update(raw).digest("hex")}`;
241843
242352
  }
241844
- var import_node_crypto65, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
242353
+ var import_node_crypto66, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
241845
242354
  var init_project_event_poller = __esm({
241846
242355
  "../server/src/automations/project-event-poller.ts"() {
241847
242356
  "use strict";
241848
- import_node_crypto65 = require("node:crypto");
242357
+ import_node_crypto66 = require("node:crypto");
241849
242358
  EVENT_KEY_VERSION = "v1";
241850
242359
  EMPTY_METRICS = () => ({
241851
242360
  scanDurationMs: 0,
@@ -242056,7 +242565,7 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
242056
242565
  if (input.requireConsumptionReceipt && !hub.supportsInputConsumptionReceipts?.(dispatchId)) {
242057
242566
  return Promise.resolve({ accepted: false, reason: "consumption-unsupported" });
242058
242567
  }
242059
- const requestId = (0, import_node_crypto66.randomUUID)();
242568
+ const requestId = (0, import_node_crypto67.randomUUID)();
242060
242569
  const sent = routeToSession(hub, nodeId, dispatchId, { type: "append_input", dispatchId, input, requestId });
242061
242570
  if (!sent) return Promise.resolve({ accepted: false, reason: "session-closing", ...unknownIfInterrupting });
242062
242571
  return new Promise((resolve10) => {
@@ -242069,11 +242578,11 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
242069
242578
  entry.appendWaiters.push({ requestId, requireConsumptionReceipt: input.requireConsumptionReceipt, resolve: resolve10, timer });
242070
242579
  });
242071
242580
  }
242072
- var import_node_crypto66, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
242581
+ var import_node_crypto67, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
242073
242582
  var init_daemon_adapter = __esm({
242074
242583
  "../server/src/daemon-adapter.ts"() {
242075
242584
  "use strict";
242076
- import_node_crypto66 = require("node:crypto");
242585
+ import_node_crypto67 = require("node:crypto");
242077
242586
  init_src();
242078
242587
  STASH_TTL_MS = 5 * 6e4;
242079
242588
  STASH_MAX_FRAMES_PER_ID = 500;
@@ -242423,7 +242932,7 @@ var init_daemon_adapter = __esm({
242423
242932
  async spawn(job) {
242424
242933
  const nodeId = job.binding?.nodeId;
242425
242934
  if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
242426
- const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto66.randomUUID)()}`;
242935
+ const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto67.randomUUID)()}`;
242427
242936
  if (job.dispatchId && this.pending.has(dispatchId)) {
242428
242937
  return this.recover(dispatchId, nodeId, job.binding?.runtimeKind, job.actor);
242429
242938
  }
@@ -242660,7 +243169,7 @@ var init_daemon_adapter = __esm({
242660
243169
  return daemon?.meta.supportsWorkdirWalk === true;
242661
243170
  }
242662
243171
  request(nodeId, buildFrame) {
242663
- const requestId = (0, import_node_crypto66.randomUUID)();
243172
+ const requestId = (0, import_node_crypto67.randomUUID)();
242664
243173
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
242665
243174
  if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
242666
243175
  return new Promise((resolve10) => {
@@ -242768,13 +243277,13 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
242768
243277
  if (sha(body2) === lastPushedHash) return { kind: "echo" };
242769
243278
  return { kind: "human-edit", content: body2, updatedBy: page.updatedBy };
242770
243279
  }
242771
- var import_node_crypto67, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
243280
+ var import_node_crypto68, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
242772
243281
  var init_engine = __esm({
242773
243282
  "../server/src/mirror/engine.ts"() {
242774
243283
  "use strict";
242775
- import_node_crypto67 = require("node:crypto");
243284
+ import_node_crypto68 = require("node:crypto");
242776
243285
  init_src();
242777
- sha = (s2) => (0, import_node_crypto67.createHash)("sha256").update(s2, "utf8").digest("hex");
243286
+ sha = (s2) => (0, import_node_crypto68.createHash)("sha256").update(s2, "utf8").digest("hex");
242778
243287
  enc3 = (s2) => new TextEncoder().encode(s2);
242779
243288
  dec = (b2) => new TextDecoder().decode(b2);
242780
243289
  MirrorEngine = class {
@@ -243021,11 +243530,11 @@ function humanExitCause(exit3) {
243021
243530
  const base = (exit3.reason ? label[exit3.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit3.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
243022
243531
  return exit3.errorMessage ? `${base}\uFF1A${exit3.errorMessage.slice(0, 200)}` : base;
243023
243532
  }
243024
- var import_node_crypto68, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
243533
+ var import_node_crypto69, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
243025
243534
  var init_worker = __esm({
243026
243535
  "../server/src/coordinator/worker.ts"() {
243027
243536
  "use strict";
243028
- import_node_crypto68 = require("node:crypto");
243537
+ import_node_crypto69 = require("node:crypto");
243029
243538
  init_src5();
243030
243539
  init_identity();
243031
243540
  AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
@@ -243294,7 +243803,7 @@ ${HIGH_RISK_COMMANDS}`;
243294
243803
  return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
243295
243804
  }
243296
243805
  evaluationKey(kind, artifactId, evidence) {
243297
- const fingerprint2 = (0, import_node_crypto68.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
243806
+ const fingerprint2 = (0, import_node_crypto69.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
243298
243807
  return `${kind}:${artifactId}:${fingerprint2}`;
243299
243808
  }
243300
243809
  artifactEvidence(id) {
@@ -243673,8 +244182,8 @@ ${ctx.nodeFault}
243673
244182
  this.deps.log?.(`[coordinator] ${workspace} \u5168\u5C40\u4E0A\u4E0B\u6587\u8BFB\u53D6\u5931\u8D25\uFF0C\u7EE7\u7EED\u6CBF\u7528\u539F\u534F\u8C03\u8005\u4EFB\u52A1\uFF1A${String(error2)}`);
243674
244183
  }
243675
244184
  }
243676
- const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto68.randomUUID)();
243677
- const coordinatorRunId = `coordinate:${(0, import_node_crypto68.randomUUID)()}`;
244185
+ const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto69.randomUUID)();
244186
+ const coordinatorRunId = `coordinate:${(0, import_node_crypto69.randomUUID)()}`;
243678
244187
  const taskWithContext = coordinatorContext ? [
243679
244188
  task,
243680
244189
  ``,
@@ -243718,7 +244227,7 @@ ${ctx.nodeFault}
243718
244227
  const startedAtMs = Date.now();
243719
244228
  let trajectoryStarted = false;
243720
244229
  if (this.deps.trajectory) {
243721
- const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto68.createHash)("sha256").update(content3).digest("hex")]));
244230
+ const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto69.createHash)("sha256").update(content3).digest("hex")]));
243722
244231
  const session = {
243723
244232
  runId: coordinatorRunId,
243724
244233
  runtimeSessionId,
@@ -243923,11 +244432,11 @@ var init_pg_ident = __esm({
243923
244432
  });
243924
244433
 
243925
244434
  // ../storage/src/postgres.ts
243926
- var import_node_crypto69, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
244435
+ var import_node_crypto70, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
243927
244436
  var init_postgres = __esm({
243928
244437
  "../storage/src/postgres.ts"() {
243929
244438
  "use strict";
243930
- import_node_crypto69 = require("node:crypto");
244439
+ import_node_crypto70 = require("node:crypto");
243931
244440
  init_pg_ident();
243932
244441
  init_esm();
243933
244442
  init_src();
@@ -244107,7 +244616,7 @@ var init_postgres = __esm({
244107
244616
  return new _PostgresBlobStore(pool, schema);
244108
244617
  }
244109
244618
  async put(bytes2) {
244110
- const hash2 = (0, import_node_crypto69.createHash)("sha256").update(bytes2).digest("hex");
244619
+ const hash2 = (0, import_node_crypto70.createHash)("sha256").update(bytes2).digest("hex");
244111
244620
  await this.pool.query(
244112
244621
  `INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
244113
244622
  [hash2, Buffer.from(bytes2), bytes2.byteLength, sniffContentType(bytes2) ?? null]
@@ -250504,6 +251013,20 @@ var init_postgres_chat_sessions = __esm({
250504
251013
  );
250505
251014
  return r.rows.map(rowToSessionWithQuality);
250506
251015
  }
251016
+ async listSessionsByProject(projectId2) {
251017
+ const r = await this.pool.query(
251018
+ `SELECT s.*
251019
+ FROM ${this.s}.chat_sessions s
251020
+ WHERE s.company_id = $2
251021
+ AND s.project_id IS NOT DISTINCT FROM $1
251022
+ AND s.analyzed_run_id IS NULL
251023
+ AND s.visibility IS DISTINCT FROM 'internal'
251024
+ AND COALESCE(s.workspace, '') = ''
251025
+ ORDER BY s.created_at DESC, s.id`,
251026
+ [projectId2, this.companyId]
251027
+ );
251028
+ return r.rows.map(rowToSession);
251029
+ }
250507
251030
  async listSessionsByAnalyzedRun(analyzedRunId) {
250508
251031
  const r = await this.pool.query(
250509
251032
  `SELECT s.*, lm.status AS last_assistant_status, lm.content AS last_assistant_content
@@ -252720,11 +253243,11 @@ var init_postgres_delegations = __esm({
252720
253243
  });
252721
253244
 
252722
253245
  // ../storage/src/postgres-nodes.ts
252723
- var import_node_crypto70, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
253246
+ var import_node_crypto71, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
252724
253247
  var init_postgres_nodes = __esm({
252725
253248
  "../storage/src/postgres-nodes.ts"() {
252726
253249
  "use strict";
252727
- import_node_crypto70 = require("node:crypto");
253250
+ import_node_crypto71 = require("node:crypto");
252728
253251
  init_pg_ident();
252729
253252
  init_esm();
252730
253253
  ident13 = (s2) => {
@@ -252941,7 +253464,7 @@ var init_postgres_nodes = __esm({
252941
253464
  return store;
252942
253465
  }
252943
253466
  issue(nodeId) {
252944
- const token = `ont_${(0, import_node_crypto70.randomBytes)(24).toString("base64url")}`;
253467
+ const token = `ont_${(0, import_node_crypto71.randomBytes)(24).toString("base64url")}`;
252945
253468
  this.cache.set(token, nodeId);
252946
253469
  void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
252947
253470
  return token;
@@ -253207,11 +253730,11 @@ var init_postgres_type_registry = __esm({
253207
253730
  });
253208
253731
 
253209
253732
  // ../storage/src/postgres-actor-memory.ts
253210
- var import_node_crypto71, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
253733
+ var import_node_crypto72, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
253211
253734
  var init_postgres_actor_memory = __esm({
253212
253735
  "../storage/src/postgres-actor-memory.ts"() {
253213
253736
  "use strict";
253214
- import_node_crypto71 = require("node:crypto");
253737
+ import_node_crypto72 = require("node:crypto");
253215
253738
  init_pg_ident();
253216
253739
  init_src();
253217
253740
  matchClause = (q2) => q2.requireMatch && q2.keywords.length > 0 ? " WHERE m > 0" : "";
@@ -253421,7 +253944,7 @@ var init_postgres_actor_memory = __esm({
253421
253944
  }
253422
253945
  async write(input, now) {
253423
253946
  if (input.memId === void 0) {
253424
- const memId = `mem:${(0, import_node_crypto71.randomUUID)()}`;
253947
+ const memId = `mem:${(0, import_node_crypto72.randomUUID)()}`;
253425
253948
  const r2 = await this.pool.query(
253426
253949
  `INSERT INTO ${this.s}.actor_memories
253427
253950
  (mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
@@ -254868,14 +255391,14 @@ function companySchemaName(companyId) {
254868
255391
  if (typeof companyId !== "string" || !companyId.trim()) {
254869
255392
  throw new Error("company schema mapping \u9700\u8981\u975E\u7A7A companyId\uFF08fail-closed\uFF1A\u4E0D\u56DE\u9000\u9ED8\u8BA4\u516C\u53F8\uFF09");
254870
255393
  }
254871
- const hex = (0, import_node_crypto72.createHash)("sha256").update(companyId, "utf8").digest("hex");
255394
+ const hex = (0, import_node_crypto73.createHash)("sha256").update(companyId, "utf8").digest("hex");
254872
255395
  return `${COMPANY_SCHEMA_PREFIX}${hex.slice(0, DIGEST_HEX_LEN)}`;
254873
255396
  }
254874
- var import_node_crypto72, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
255397
+ var import_node_crypto73, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
254875
255398
  var init_tenant_schema_mapping = __esm({
254876
255399
  "../storage/src/tenant-schema-mapping.ts"() {
254877
255400
  "use strict";
254878
- import_node_crypto72 = require("node:crypto");
255401
+ import_node_crypto73 = require("node:crypto");
254879
255402
  init_esm();
254880
255403
  init_pg_ident();
254881
255404
  COMPANY_SCHEMA_PREFIX = "c_";
@@ -254949,7 +255472,7 @@ var init_tenant_schema_mapping = __esm({
254949
255472
 
254950
255473
  // ../storage/src/company-data-plane-schema.ts
254951
255474
  function canonicalTypeDefHash(def) {
254952
- return `sha256:${(0, import_node_crypto73.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
255475
+ return `sha256:${(0, import_node_crypto74.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
254953
255476
  }
254954
255477
  function defaultArtifactTypeTemplate() {
254955
255478
  return [
@@ -255131,11 +255654,11 @@ async function seedDefaultArtifactTypes(pool, schema, templates = defaultArtifac
255131
255654
  }
255132
255655
  return { inserted };
255133
255656
  }
255134
- var import_node_crypto73, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
255657
+ var import_node_crypto74, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
255135
255658
  var init_company_data_plane_schema = __esm({
255136
255659
  "../storage/src/company-data-plane-schema.ts"() {
255137
255660
  "use strict";
255138
- import_node_crypto73 = require("node:crypto");
255661
+ import_node_crypto74 = require("node:crypto");
255139
255662
  init_esm();
255140
255663
  init_pg_ident();
255141
255664
  DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION = "4c368e5c-types-v1";
@@ -255215,11 +255738,11 @@ function rowToFile2(row) {
255215
255738
  updatedAt: new Date(row.updated_at).toISOString()
255216
255739
  };
255217
255740
  }
255218
- var import_node_crypto74, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
255741
+ var import_node_crypto75, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
255219
255742
  var init_company_owned_registry_store = __esm({
255220
255743
  "../storage/src/company-owned-registry-store.ts"() {
255221
255744
  "use strict";
255222
- import_node_crypto74 = require("node:crypto");
255745
+ import_node_crypto75 = require("node:crypto");
255223
255746
  init_esm();
255224
255747
  init_src();
255225
255748
  init_pg_ident();
@@ -255605,7 +256128,7 @@ var init_company_owned_registry_store = __esm({
255605
256128
  enabled: skill.enabled,
255606
256129
  files: files.map((f2) => ({ path: f2.path, blobHash: f2.blobHash }))
255607
256130
  });
255608
- return (0, import_node_crypto74.createHash)("sha256").update(body2).digest("hex");
256131
+ return (0, import_node_crypto75.createHash)("sha256").update(body2).digest("hex");
255609
256132
  }
255610
256133
  };
255611
256134
  }
@@ -258202,7 +258725,7 @@ function sourceSetConservation(input) {
258202
258725
  return input.sourceFinal === input.copied + input.approvedRuntimeConversion + input.approvedArchive + input.approvedDiscard;
258203
258726
  }
258204
258727
  function payloadChecksum(value2) {
258205
- return (0, import_node_crypto75.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
258728
+ return (0, import_node_crypto76.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
258206
258729
  }
258207
258730
  async function copyWorkorderDraftsIdempotent(pool, companyId, companySchema, manifestId, opts) {
258208
258731
  const cs = quoteIdent2(companySchema);
@@ -258590,11 +259113,11 @@ async function copyTraceFamilyIdempotent(pool, companyId, companySchema, manifes
258590
259113
  sequences
258591
259114
  };
258592
259115
  }
258593
- var import_node_crypto75, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
259116
+ var import_node_crypto76, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
258594
259117
  var init_tenant_migration_operator = __esm({
258595
259118
  "../storage/src/tenant-migration-operator.ts"() {
258596
259119
  "use strict";
258597
- import_node_crypto75 = require("node:crypto");
259120
+ import_node_crypto76 = require("node:crypto");
258598
259121
  init_esm();
258599
259122
  init_pg_ident();
258600
259123
  init_tenancy_attribution();
@@ -258826,8 +259349,8 @@ var init_tenant_new_company_provision = __esm({
258826
259349
  // ../storage/src/knowledge-snapshot-crypto.ts
258827
259350
  function sealSnapshot(snapshot, key) {
258828
259351
  if (snapshot.content.startsWith(PREFIX3)) return structuredClone(snapshot);
258829
- const iv = (0, import_node_crypto76.randomBytes)(12);
258830
- const cipher = (0, import_node_crypto76.createCipheriv)("aes-256-gcm", bytes(key), iv);
259352
+ const iv = (0, import_node_crypto77.randomBytes)(12);
259353
+ const cipher = (0, import_node_crypto77.createCipheriv)("aes-256-gcm", bytes(key), iv);
258831
259354
  cipher.setAAD(aad2(snapshot));
258832
259355
  const encrypted = Buffer.concat([cipher.update(snapshot.content, "utf8"), cipher.final()]);
258833
259356
  return { ...structuredClone(snapshot), content: `${PREFIX3}${iv.toString("base64url")}.${cipher.getAuthTag().toString("base64url")}.${encrypted.toString("base64url")}` };
@@ -258837,7 +259360,7 @@ function openSnapshot(snapshot, key) {
258837
259360
  const packed = snapshot.content.slice(PREFIX3.length).split(".");
258838
259361
  if (packed.length !== 3 || packed.some((part) => !part)) throw new Error(`knowledge snapshot ${snapshot.ref} ciphertext is malformed`);
258839
259362
  const [iv, tag, data] = packed;
258840
- const decipher = (0, import_node_crypto76.createDecipheriv)("aes-256-gcm", bytes(key), Buffer.from(iv, "base64url"));
259363
+ const decipher = (0, import_node_crypto77.createDecipheriv)("aes-256-gcm", bytes(key), Buffer.from(iv, "base64url"));
258841
259364
  decipher.setAAD(aad2(snapshot));
258842
259365
  decipher.setAuthTag(Buffer.from(tag, "base64url"));
258843
259366
  return {
@@ -258845,11 +259368,11 @@ function openSnapshot(snapshot, key) {
258845
259368
  content: Buffer.concat([decipher.update(Buffer.from(data, "base64url")), decipher.final()]).toString("utf8")
258846
259369
  };
258847
259370
  }
258848
- var import_node_crypto76, PREFIX3, bytes, aad2;
259371
+ var import_node_crypto77, PREFIX3, bytes, aad2;
258849
259372
  var init_knowledge_snapshot_crypto = __esm({
258850
259373
  "../storage/src/knowledge-snapshot-crypto.ts"() {
258851
259374
  "use strict";
258852
- import_node_crypto76 = require("node:crypto");
259375
+ import_node_crypto77 = require("node:crypto");
258853
259376
  PREFIX3 = "oasis-knowledge:v1:";
258854
259377
  bytes = (key) => {
258855
259378
  const value2 = Buffer.from(key);
@@ -260921,7 +261444,7 @@ var init_src11 = __esm({
260921
261444
  init_experiments2();
260922
261445
  init_trace2();
260923
261446
  init_execution_continuity2();
260924
- init_chat_sessions();
261447
+ init_chat_sessions2();
260925
261448
  init_dev_store();
260926
261449
  init_continuation3();
260927
261450
  init_knowledge2();
@@ -261096,20 +261619,20 @@ var fs45 = __toESM(require("node:fs"));
261096
261619
  var os14 = __toESM(require("node:os"));
261097
261620
  var path40 = __toESM(require("node:path"));
261098
261621
  var import_node_child_process23 = require("node:child_process");
261099
- var import_node_crypto83 = require("node:crypto");
261622
+ var import_node_crypto84 = require("node:crypto");
261100
261623
 
261101
261624
  // ../cli/src/cli.ts
261102
261625
  var fs40 = __toESM(require("node:fs"), 1);
261103
261626
  var os12 = __toESM(require("node:os"), 1);
261104
261627
  var path35 = __toESM(require("node:path"), 1);
261105
- var import_node_crypto81 = require("node:crypto");
261628
+ var import_node_crypto82 = require("node:crypto");
261106
261629
 
261107
261630
  // ../cli/src/serve.ts
261108
261631
  var fs34 = __toESM(require("node:fs"), 1);
261109
261632
  var os8 = __toESM(require("node:os"), 1);
261110
261633
  var path29 = __toESM(require("node:path"), 1);
261111
261634
  var import_node_child_process18 = require("node:child_process");
261112
- var import_node_crypto77 = require("node:crypto");
261635
+ var import_node_crypto78 = require("node:crypto");
261113
261636
  var import_node_url7 = require("node:url");
261114
261637
 
261115
261638
  // ../cli/src/artifact-discovery-source.ts
@@ -261326,6 +261849,54 @@ function buildChatTurnPrompts(input) {
261326
261849
  };
261327
261850
  }
261328
261851
 
261852
+ // ../cli/src/chat-task-md.ts
261853
+ var CHAT_PROJECT_SECTION_TITLE = "## \u5F53\u524D\u9879\u76EE\uFF08\u4EE5\u672C\u8F6E\u4E3A\u51C6\uFF09";
261854
+ var CHAT_PROJECT_INPUTS_POINTER = [
261855
+ "",
261856
+ "## \u5F53\u524D\u9879\u76EE\u516C\u5F00\u8F93\u5165",
261857
+ "",
261858
+ "\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"
261859
+ ];
261860
+ function buildChatProjectSection(project, opts) {
261861
+ if (project.state === "unknown") return [];
261862
+ if (project.state === "none") {
261863
+ return opts.announceNone ? [CHAT_PROJECT_SECTION_TITLE, "", "\u672C\u5BF9\u8BDD\u672A\u5F52\u5C5E\u4EFB\u4F55\u9879\u76EE\u3002"] : [];
261864
+ }
261865
+ const description = project.description?.trim();
261866
+ 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}`];
261867
+ return [
261868
+ CHAT_PROJECT_SECTION_TITLE,
261869
+ "",
261870
+ `project_id: ${project.id}`,
261871
+ `\u9879\u76EE\u540D\u79F0: ${project.name}`,
261872
+ ...descriptionLines
261873
+ ];
261874
+ }
261875
+ function buildChatTaskMd(p2) {
261876
+ return [
261877
+ ...p2.headerLines,
261878
+ // 项目段紧跟抬头:这一轮在哪个项目里,是读后面所有内容的前提。
261879
+ ...p2.projectSection.length ? ["", ...p2.projectSection] : [],
261880
+ ...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}`)] : [],
261881
+ // 委派子会话的主会话对话快照(提案 §6.1.4):一句固定说明 + 一份清单;没有这个文件时整段不拼。
261882
+ // 指针与文件由 delegationBundleAdditions **一处**给出,拆开迟早漂成「有指针没文件」。
261883
+ ...p2.parentConversationLines,
261884
+ // 委派两条路搬过来的文件(去程 `--file` / 回程专家产物):**不能复用「## 用户附件」的措辞**
261885
+ // (专家的产物会被介绍成用户附带的)。
261886
+ ...p2.workspaceLines,
261887
+ ...p2.projectInputsPointer,
261888
+ // 诊断 1be339c7:身份段紧贴在「## 用户消息」之前——让 agent 先知道「你在跟谁说话」,再读那条消息。
261889
+ ...p2.whoLines.length ? ["", ...p2.whoLines] : [],
261890
+ "",
261891
+ "## \u7528\u6237\u6D88\u606F",
261892
+ "",
261893
+ p2.message,
261894
+ ...p2.memoryLines,
261895
+ // 压在最末:这条要盖住紧邻其上的「收工前想一下」,否则记忆写入就落到正文之后(见常量注释)。
261896
+ ...p2.finalReplyLines
261897
+ ].join("\n");
261898
+ }
261899
+
261329
261900
  // ../cli/src/company-dispatch-bridge.ts
261330
261901
  var CompanyDispatchBridges = class {
261331
261902
  constructor(opts) {
@@ -261802,11 +262373,12 @@ init_src10();
261802
262373
  init_src11();
261803
262374
  init_src11();
261804
262375
  init_src11();
262376
+ init_src11();
261805
262377
  init_src9();
261806
262378
  init_src();
261807
262379
  init_trajectory();
261808
262380
  function deterministicUuid2(seed) {
261809
- const h = (0, import_node_crypto77.createHash)("sha256").update(seed).digest();
262381
+ const h = (0, import_node_crypto78.createHash)("sha256").update(seed).digest();
261810
262382
  const b2 = Buffer.from(h.subarray(0, 16));
261811
262383
  b2[6] = b2[6] & 15 | 80;
261812
262384
  b2[8] = b2[8] & 63 | 128;
@@ -262006,7 +262578,7 @@ function oasisWrapperScript() {
262006
262578
  const repoRoot = (0, import_node_url7.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
262007
262579
  const tsx = path29.join(repoRoot, "node_modules/.bin/tsx");
262008
262580
  const main = path29.join(repoRoot, "packages/cli/src/main.ts");
262009
- const key = (0, import_node_crypto77.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
262581
+ const key = (0, import_node_crypto78.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
262010
262582
  const uid = typeof process.getuid === "function" ? process.getuid() : "na";
262011
262583
  const stable = path29.join(os8.tmpdir(), `oasis-cli-wrapper-${uid}-${key}`);
262012
262584
  let dir;
@@ -262387,7 +262959,7 @@ async function startServe(opts) {
262387
262959
  if (!hub) return Promise.resolve({ ok: false, reason: "\u672C\u8FDB\u7A0B\u6CA1\u6709\u8282\u70B9\u7F51\u5173" });
262388
262960
  const online = hub.connectedDaemons().some((d) => d.daemonId === nodeId);
262389
262961
  if (!online) return Promise.resolve({ ok: false, reason: "\u8282\u70B9\u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u7528\u7684\u662F\u5B83\u4E0A\u6B21\u62A5\u7684\u6E05\u5355" });
262390
- const requestId = (0, import_node_crypto77.randomUUID)();
262962
+ const requestId = (0, import_node_crypto78.randomUUID)();
262391
262963
  const task = new Promise((resolve10) => {
262392
262964
  const timer = setTimeout(() => {
262393
262965
  modelRefreshWaiters.delete(requestId);
@@ -262696,6 +263268,15 @@ async function startServe(opts) {
262696
263268
  const members = await listCompanyMembers(companyId);
262697
263269
  return members.find((m2) => m2.actorId === actorId)?.role;
262698
263270
  },
263271
+ // 项目「关联对话」列表与对话改挂项目(2026-09-14)。闭包在请求时才读 chatStoreFor /
263272
+ // delegationStoreFor,避开它们在下方声明的 TDZ(与 listChatFiles 同法)。
263273
+ chatSessionsFor: (companyId) => chatStoreFor(companyId),
263274
+ // 委派子会话不进项目(ADR 0555);一次台账查询批量判,不逐条点查。
263275
+ delegationChildIds: async (companyId, sessionIds) => {
263276
+ const ledger = await delegationStoreFor(companyId).catch(() => null);
263277
+ if (!ledger) return /* @__PURE__ */ new Set();
263278
+ return new Set(await ledger.listChildSessionIds(sessionIds));
263279
+ },
262699
263280
  resolveActorContext: (actorId) => buildActorContext(actors.service, actorId),
262700
263281
  defaultBranchResolver,
262701
263282
  // 展示:GET content-locations 给 git 仓补 resolvedDefaultBranch
@@ -263130,7 +263711,7 @@ async function startServe(opts) {
263130
263711
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
263131
263712
  if (buf || session.runId) {
263132
263713
  await turnStore.appendMessage({
263133
- id: (0, import_node_crypto77.randomUUID)(),
263714
+ id: (0, import_node_crypto78.randomUUID)(),
263134
263715
  sessionId: chatSessionId,
263135
263716
  role: "assistant",
263136
263717
  content: buf,
@@ -263176,7 +263757,7 @@ async function startServe(opts) {
263176
263757
  chatTargets: {
263177
263758
  inspect: inspectChatTarget,
263178
263759
  createTargetSession: async ({ companyId, agentId, ownerHumanActorId, title }) => {
263179
- const sessionId = (0, import_node_crypto77.randomUUID)();
263760
+ const sessionId = (0, import_node_crypto78.randomUUID)();
263180
263761
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
263181
263762
  const binding = await registryStore.getBinding(agentId).catch(() => null);
263182
263763
  await (await chatStoreFor(companyId)).createSession({
@@ -263651,6 +264232,7 @@ async function startServe(opts) {
263651
264232
  };
263652
264233
  const readMarkerStoreFor = async (companyId) => pgPool && companyId ? (await openCompanyPlane(companyId)).readMarkers : readMarkerStore;
263653
264234
  const fileNotificationStore = pgPool ? void 0 : new MemoryNotificationStore(path29.join(opts.dir, "notifications.json"));
264235
+ const notificationLedgerEnabled = process.env["OASIS_NOTIFICATION_LEDGER"] !== "off";
263654
264236
  const notificationStoreFor = async (companyId) => pgPool ? (await openCompanyPlane(companyId ?? defaultCompanyId)).notifications : fileNotificationStore;
263655
264237
  const actorMemoryFor = async (companyId) => pgPool && companyId ? (await openCompanyPlane(companyId)).memory : actorMemoryService;
263656
264238
  const workorderDraftsFor = async (companyId) => pgPool && companyId ? (await openCompanyPlane(companyId)).drafts : workorderDrafts;
@@ -264220,7 +264802,7 @@ async function startServe(opts) {
264220
264802
  }));
264221
264803
  }
264222
264804
  const limits = { wallClockMs: wallClockForKind("planner") };
264223
- const artifactId = `artifact:planner:${(0, import_node_crypto77.randomUUID)()}`;
264805
+ const artifactId = `artifact:planner:${(0, import_node_crypto78.randomUUID)()}`;
264224
264806
  const handle = await chatRemoteAdapter.spawn({
264225
264807
  actor: planner.id,
264226
264808
  // companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
@@ -264395,7 +264977,7 @@ async function startServe(opts) {
264395
264977
  readMarkers: readMarkerStore,
264396
264978
  // bundle=`inbox`:水位按当前公司取(与上面 resolveChatSession 成对)。
264397
264979
  ...pgPool ? { resolveReadMarkers: readMarkerStoreFor } : {},
264398
- resolveNotificationLedger: notificationStoreFor,
264980
+ ...notificationLedgerEnabled ? { resolveNotificationLedger: notificationStoreFor } : {},
264399
264981
  escalationReminders: escalationDelivery.policy,
264400
264982
  defaultCompanyId,
264401
264983
  // 工单标签词表(组织级受控、旁账,与 playbook-overrides 同款侧存)。
@@ -265122,10 +265704,10 @@ async function startServe(opts) {
265122
265704
  if (isDeletedActor(dispatchTarget)) {
265123
265705
  throw new ApiError(409, ACTOR_DELETED_ERROR_CODE, ACTOR_DELETED_CHAT_MESSAGE);
265124
265706
  }
265125
- const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid2(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto77.randomUUID)());
265707
+ const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid2(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto78.randomUUID)());
265126
265708
  const resumeRuntimeSession = Boolean(sessionId);
265127
- const traceRunId = requestDispatchId ? `chat-run:${deterministicUuid2(`chat-trace:${requestDispatchId}`)}` : `chat-run:${(0, import_node_crypto77.randomUUID)()}`;
265128
- const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid2(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto77.randomUUID)()}`;
265709
+ const traceRunId = requestDispatchId ? `chat-run:${deterministicUuid2(`chat-trace:${requestDispatchId}`)}` : `chat-run:${(0, import_node_crypto78.randomUUID)()}`;
265710
+ const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid2(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto78.randomUUID)()}`;
265129
265711
  let stableDispatch = requestDispatchId ? await resolveStableChatDispatch({ store: await traceStoreFor(runCompanyId), runId: traceRunId }) : null;
265130
265712
  if (stableDispatch && !stableDispatch.shouldDispatch) {
265131
265713
  return {
@@ -265189,7 +265771,11 @@ async function startServe(opts) {
265189
265771
  }
265190
265772
  }
265191
265773
  }
265192
- const analyzedRunId = chatSessionId ? (await chatSessionStore.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
265774
+ const turnChatStore = chatSessionId ? await chatStoreFor(runCompanyId).catch((err) => {
265775
+ 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)}`);
265776
+ return null;
265777
+ }) : null;
265778
+ const analyzedRunId = chatSessionId ? (await turnChatStore?.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
265193
265779
  const actorConfigVersion = await actorService.latestConfig(actorId).then((c) => c?.version ?? null).catch(() => null);
265194
265780
  const candidateTraceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
265195
265781
  const traceCreateInput = (target, startedAt) => ({
@@ -265243,7 +265829,7 @@ async function startServe(opts) {
265243
265829
  requireNodeConnectors();
265244
265830
  }
265245
265831
  }
265246
- const priorChatSession = chatSessionId ? await chatSessionStore?.getSession(chatSessionId).catch(() => null) : null;
265832
+ const priorChatSession = chatSessionId ? await turnChatStore?.getSession(chatSessionId).catch(() => null) : null;
265247
265833
  const freshChatWorkdir = chatSessionId ? chatNodeChanged(
265248
265834
  { nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
265249
265835
  { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
@@ -265301,17 +265887,19 @@ async function startServe(opts) {
265301
265887
  const attachmentFiles = { ...preparedAttachments.files };
265302
265888
  const attachmentBinary = { ...preparedAttachments.binaryFiles };
265303
265889
  const attachmentPaths = [...preparedAttachments.paths];
265304
- let projectContextLines = [];
265890
+ let turnProject = priorChatSession ? { state: "none" } : { state: "unknown" };
265305
265891
  if (priorChatSession?.projectId) {
265306
- const projectBundle = await buildChatProjectBundle(
265307
- priorChatSession.projectId,
265308
- projectStateStore,
265309
- (blobId) => assets.get(blobId)
265310
- ).catch(() => null);
265892
+ const projectId2 = priorChatSession.projectId;
265893
+ const projectBundle = await projectStateFor(runCompanyId).then((projects) => buildChatProjectBundle(projectId2, projects, (blobId) => assets.get(blobId))).catch((err) => {
265894
+ console.warn(`[chat] \u8BFB\u9879\u76EE ${projectId2} \u5931\u8D25\uFF08\u672C\u8F6E\u4E0D\u5199\u9879\u76EE\u6BB5\uFF09: ${String(err)}`);
265895
+ return void 0;
265896
+ });
265311
265897
  if (projectBundle) {
265312
265898
  Object.assign(attachmentFiles, projectBundle.files);
265313
265899
  Object.assign(attachmentBinary, projectBundle.binaryFiles);
265314
- projectContextLines = projectBundle.contextLines;
265900
+ turnProject = { state: "bound", ...projectBundle.project };
265901
+ } else if (projectBundle === void 0) {
265902
+ turnProject = { state: "unknown" };
265315
265903
  }
265316
265904
  }
265317
265905
  const sessionLine = chatSessionId ? `chat_session_id: ${chatSessionId}` : `runtime_session_id: ${runtimeSessionId}`;
@@ -265334,29 +265922,21 @@ async function startServe(opts) {
265334
265922
  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
265923
  const parentConversationBundle = delegationBundleAdditions(parentConversationSnapshot);
265336
265924
  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
- "",
265925
+ "TASK.md": buildChatTaskMd({
265926
+ headerLines: taskHeader,
265927
+ // 「未归属」只对普通对话说:挂在工单上的对话(协调者 / 上报讨论房)项目看工单绑定,
265928
+ // 委派子会话建出来本就不带项目;知识库单轮受限生成连抬头都是另一套。
265929
+ projectSection: isolatedKnowledgeTurn ? [] : buildChatProjectSection(turnProject, { announceNone: !workspace && !delegatedChildTurn }),
265930
+ attachmentPaths,
265931
+ parentConversationLines: parentConversationBundle.taskLines,
265932
+ workspaceLines: workspaceBundle?.taskLines ?? [],
265933
+ // 项目公开输入指针:文字、位置、出现条件(查到了项目)都与改动前一致。
265934
+ projectInputsPointer: turnProject.state === "bound" ? CHAT_PROJECT_INPUTS_POINTER : [],
265935
+ whoLines,
265355
265936
  message,
265356
- ...chatMemoryLines,
265357
- // 压在最末:这条要盖住紧邻其上的「收工前想一下」,否则记忆写入就落到正文之后(见常量注释)。
265358
- ...isolatedKnowledgeTurn ? [] : chatFinalReplyRule({ memoryEnabled: chatMemoryEnabled })
265359
- ].join("\n")
265937
+ memoryLines: chatMemoryLines,
265938
+ finalReplyLines: isolatedKnowledgeTurn ? [] : chatFinalReplyRule({ memoryEnabled: chatMemoryEnabled })
265939
+ })
265360
265940
  };
265361
265941
  Object.assign(files, attachmentFiles);
265362
265942
  Object.assign(files, parentConversationBundle.files);
@@ -265472,6 +266052,8 @@ async function startServe(opts) {
265472
266052
  handle.onLiveEvent?.((event) => {
265473
266053
  if (liveEventCbs.length === 0) bufferedLiveEvents.push(event);
265474
266054
  for (const cb of liveEventCbs) cb(event);
266055
+ const subagentEvent = subagentRunEventFromLive(event);
266056
+ if (subagentEvent) enqueueTrace(() => traceAppender.append([subagentEvent]).then(() => void 0));
265475
266057
  });
265476
266058
  let capturedNativeSessionId;
265477
266059
  const done = new Promise((resolve10, reject) => {
@@ -265498,6 +266080,7 @@ async function startServe(opts) {
265498
266080
  durationMs: durationMs3,
265499
266081
  exitCode: info.code,
265500
266082
  exitReason: info.reason ?? null,
266083
+ ...!isHealthySessionExit(info) && info.reason !== "cancelled" ? { errorCode: info.reason ?? "error", errorMessage: formatExitError(info) } : {},
265501
266084
  transcriptRef: info.transcriptRef ?? null,
265502
266085
  usage,
265503
266086
  effectiveModel,
@@ -266251,7 +266834,7 @@ async function startServe(opts) {
266251
266834
  console.log(`[new-engine] dispatchWork ${workId} \u662F human work\u2014\u2014\u4E0D\u6D3E runtime\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406`);
266252
266835
  return;
266253
266836
  }
266254
- const ledgerId = `dispatch:${(0, import_node_crypto77.randomUUID)()}`;
266837
+ const ledgerId = `dispatch:${(0, import_node_crypto78.randomUUID)()}`;
266255
266838
  try {
266256
266839
  await (await dispatchLedgerFor(companyId)).insertOpen({
266257
266840
  id: ledgerId,
@@ -266336,7 +266919,7 @@ async function startServe(opts) {
266336
266919
  const kernelModel = newKernel.model;
266337
266920
  const art = kernelModel.artifacts.get(nodeId);
266338
266921
  const revWid = art?.workspace ?? "";
266339
- const ledgerId = `dispatch:${(0, import_node_crypto77.randomUUID)()}`;
266922
+ const ledgerId = `dispatch:${(0, import_node_crypto78.randomUUID)()}`;
266340
266923
  try {
266341
266924
  await (await dispatchLedgerFor(companyId)).insertOpen({
266342
266925
  id: ledgerId,
@@ -266714,7 +267297,7 @@ async function startServe(opts) {
266714
267297
  resolveSupplementalContext: server.knowledgeGovernance ? async ({ actorId, artifactId, workOrderId, action, part, runtimeKind, taskMarkdown }) => {
266715
267298
  try {
266716
267299
  const governance = server.knowledgeGovernance;
266717
- const taskFingerprint = (0, import_node_crypto77.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
267300
+ const taskFingerprint = (0, import_node_crypto78.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
266718
267301
  const requestContext = {
266719
267302
  organizationId: companyId,
266720
267303
  actorId,
@@ -267202,28 +267785,28 @@ async function startServe(opts) {
267202
267785
  const isDefault = cid === defaultCompanyId;
267203
267786
  const cacheKey = `${cid}::${workspace}`;
267204
267787
  if (initiatorCache.has(cacheKey)) return initiatorCache.get(cacheKey);
267205
- let found = null;
267788
+ let found2 = null;
267206
267789
  const kernelToRead = engine2?.kernel ?? newEngine.kernel;
267207
267790
  const store = kernelToRead.getStore?.();
267208
267791
  if (store) {
267209
267792
  try {
267210
267793
  const events = await store.transaction((tx) => tx.listEvents(workspace));
267211
267794
  const spawn10 = events.find((e) => e.event.kind === "plan.changed");
267212
- found = spawn10?.actorId ?? null;
267795
+ found2 = spawn10?.actorId ?? null;
267213
267796
  } catch {
267214
267797
  }
267215
267798
  }
267216
- if (found === null && isDefault) {
267799
+ if (found2 === null && isDefault) {
267217
267800
  for await (const entry of oplog.readAll()) {
267218
267801
  const op = entry.op;
267219
267802
  if (op.kind === "spawn_artifact" && op.payload.workspace === workspace) {
267220
- found = op.actor;
267803
+ found2 = op.actor;
267221
267804
  break;
267222
267805
  }
267223
267806
  }
267224
267807
  }
267225
- initiatorCache.set(cacheKey, found);
267226
- return found;
267808
+ initiatorCache.set(cacheKey, found2);
267809
+ return found2;
267227
267810
  };
267228
267811
  const leadOf = async (actor, registry2) => {
267229
267812
  const reg = registry2 ?? registryStore;
@@ -267407,7 +267990,7 @@ async function startServe(opts) {
267407
267990
  },
267408
267991
  log: (message) => console.warn(message)
267409
267992
  });
267410
- if (process.env["OASIS_NOTIFICATION_LEDGER"] !== "off") {
267993
+ if (notificationLedgerEnabled) {
267411
267994
  const ledgerPeople = /* @__PURE__ */ new Map();
267412
267995
  const worker = new NotificationLedgerWorker({
267413
267996
  listCompanies: async () => engineRouter.liveEntries().map((e) => e.companyId),
@@ -268662,7 +269245,7 @@ var SessionProcessState = class {
268662
269245
  // ../cli/src/daemon/ws-client.ts
268663
269246
  init_wrapper();
268664
269247
  var import_node_os9 = require("node:os");
268665
- var import_node_crypto78 = require("node:crypto");
269248
+ var import_node_crypto79 = require("node:crypto");
268666
269249
  init_src8();
268667
269250
  init_src6();
268668
269251
 
@@ -268731,7 +269314,7 @@ var import_node_fs20 = __toESM(require("node:fs"), 1);
268731
269314
  var import_node_path26 = __toESM(require("node:path"), 1);
268732
269315
  init_src6();
268733
269316
  init_src();
268734
- var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
269317
+ var WORKDIR_READ_MAX_BYTES = 2 * 1024 * 1024;
268735
269318
  var WORKDIR_LIST_MAX_ENTRIES = 500;
268736
269319
  var WORKDIR_WALK_MAX_ENTRIES2 = 1e4;
268737
269320
  var WORKDIR_WALK_BUDGET_MS2 = 1e4;
@@ -268909,7 +269492,7 @@ async function verifyOpenedFd(fh, base, fallback) {
268909
269492
  return { anchor: `/proc/self/fd/${fh.fd}` };
268910
269493
  }
268911
269494
  async function handleWorkdirRead(req) {
268912
- const maxBytes = req.maxBytes ?? WORKDIR_READ_MAX_BYTES2;
269495
+ const maxBytes = req.maxBytes ?? WORKDIR_READ_MAX_BYTES;
268913
269496
  try {
268914
269497
  const resolved = await resolveWithinWorkdir(req);
268915
269498
  if (!resolved.ok) return resolved;
@@ -269434,7 +270017,7 @@ var DaemonWsClient = class {
269434
270017
  */
269435
270018
  async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
269436
270019
  if (artifactIds.length === 0) return {};
269437
- const requestId = (0, import_node_crypto78.randomUUID)();
270020
+ const requestId = (0, import_node_crypto79.randomUUID)();
269438
270021
  return new Promise((resolve10, reject) => {
269439
270022
  const timer = setTimeout(() => {
269440
270023
  this.gcPending.delete(requestId);
@@ -269836,7 +270419,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
269836
270419
  // ../cli/src/daemon/machine-id.ts
269837
270420
  var import_node_child_process21 = require("node:child_process");
269838
270421
  var import_node_fs22 = require("node:fs");
269839
- var import_node_crypto79 = require("node:crypto");
270422
+ var import_node_crypto80 = require("node:crypto");
269840
270423
  var import_node_os12 = require("node:os");
269841
270424
  function linuxMachineId() {
269842
270425
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -269902,7 +270485,7 @@ var defaultSources = {
269902
270485
  function resolveNodeId(sources = {}) {
269903
270486
  const s2 = { ...defaultSources, ...sources };
269904
270487
  const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
269905
- const digest = (0, import_node_crypto79.createHash)("sha256").update(material).digest("hex").slice(0, 12);
270488
+ const digest = (0, import_node_crypto80.createHash)("sha256").update(material).digest("hex").slice(0, 12);
269906
270489
  return `node-${digest}`;
269907
270490
  }
269908
270491
 
@@ -270913,7 +271496,7 @@ var COMMAND_DECLS = {
270913
271496
  flags: [
270914
271497
  { name: "name", desc: "\u9879\u76EE\u540D", required: true },
270915
271498
  { name: "goal", desc: "\u9879\u76EE\u76EE\u6807" },
270916
- { name: "slug", desc: "URL slug" },
271499
+ { name: "slug", desc: "\u81EA\u5B9A\u4E49\u9879\u76EE id\uFF08\u53EF\u9009\uFF09\u3002\u4E0D\u7ED9\u5219\u968F\u673A\u751F\u6210 proj_xxxxxxxx\uFF1B\u7ED9\u4E86\u4E14\u8BE5 id \u5DF2\u5B58\u5728\uFF0C\u8FD4\u56DE\u7684\u662F\u5DF2\u6709\u9879\u76EE\uFF08\u4F1A\u63D0\u793A\uFF09" },
270917
271500
  { name: "members", desc: "\u6210\u5458 JSON \u6570\u7EC4" }
270918
271501
  ],
270919
271502
  examples: ['oasis project create --name "My Project" --goal "\u6784\u5EFA\u534F\u4F5C\u5E73\u53F0"']
@@ -271673,13 +272256,13 @@ function ownFlagsFromDecl(command) {
271673
272256
  }
271674
272257
 
271675
272258
  // ../cli/src/connector-effect-runner.ts
271676
- var import_node_crypto80 = require("node:crypto");
272259
+ var import_node_crypto81 = require("node:crypto");
271677
272260
  var import_node_child_process22 = require("node:child_process");
271678
272261
  init_src8();
271679
272262
  function stableKey(effect) {
271680
272263
  const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
271681
272264
  const canonical = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
271682
- return `sha256:${(0, import_node_crypto80.createHash)("sha256").update(canonical).digest("hex")}`;
272265
+ return `sha256:${(0, import_node_crypto81.createHash)("sha256").update(canonical).digest("hex")}`;
271683
272266
  }
271684
272267
  async function runConnectorEffect(connector, toolArgs, deps) {
271685
272268
  const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
@@ -271915,7 +272498,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
271915
272498
  const timeoutMs = resolveAuthWaitMs();
271916
272499
  const controller = new AbortController();
271917
272500
  const timer = setTimeout(() => controller.abort(), timeoutMs);
271918
- const waiterId = (0, import_node_crypto81.randomUUID)();
272501
+ const waiterId = (0, import_node_crypto82.randomUUID)();
271919
272502
  process.stderr.write(
271920
272503
  `\u7B49\u5F85\u4EBA\u5DE5\u6388\u6743\u4E2D\u2026\u2026\u6700\u591A ${Math.round(timeoutMs / 1e3)} \u79D2\uFF0C\u8D85\u65F6\u4F1A\u81EA\u52A8\u8F6C\u4E3A\u5F85\u5BA1\uFF1B\u8FD9\u4E0D\u662F\u9519\u8BEF\uFF0C\u8BF7\u52FF\u91CD\u8BD5\u540C\u4E00\u6761\u547D\u4EE4\u3002
271921
272504
  `
@@ -272338,7 +272921,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
272338
272921
  const store = createNodeTokenStore(path35.join(dir, "node-tokens.json"));
272339
272922
  const sub = positional[0];
272340
272923
  if (sub === "issue") {
272341
- const id = flags.get("id") ?? `node-${(0, import_node_crypto81.randomUUID)()}`;
272924
+ const id = flags.get("id") ?? `node-${(0, import_node_crypto82.randomUUID)()}`;
272342
272925
  const token2 = store.issue(id);
272343
272926
  println(token2);
272344
272927
  process.stderr.write(
@@ -273362,7 +273945,8 @@ ${t.ref} ${t.name} [\u5267\u672C=${t.playbookRef}]`);
273362
273945
  const name = need(flags, "name").trim();
273363
273946
  if (!name) throw new Error("\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
273364
273947
  const goal = flags.get("goal")?.trim();
273365
- const id = projectSlug(flags.get("slug") ?? name);
273948
+ const slug6 = flags.get("slug");
273949
+ const id = slug6 !== void 0 ? projectSlug(slug6) : projectIdFromUuid((0, import_node_crypto82.randomUUID)());
273366
273950
  const result = await api.request("POST", "/api/projects", {
273367
273951
  ...id ? { id } : {},
273368
273952
  name,
@@ -273370,6 +273954,9 @@ ${t.ref} ${t.name} [\u5267\u672C=${t.playbookRef}]`);
273370
273954
  ...flags.has("members") ? { member_ids: parseStringArrayFlag(flags.get("members"), "members") } : {}
273371
273955
  });
273372
273956
  println(JSON.stringify(result, null, 2));
273957
+ if (typeof result?.name === "string" && result.name !== name) {
273958
+ println(`\u26A0\uFE0F \u9879\u76EE id\u300C${result.project_id ?? id}\u300D\u5DF2\u88AB\u300C${result.name}\u300D\u5360\u7528\uFF0C\u4E0A\u9762\u8FD4\u56DE\u7684\u662F\u90A3\u4E2A\u5DF2\u6709\u9879\u76EE\uFF0C\u6CA1\u6709\u65B0\u5EFA\u3002\u6362\u4E00\u4E2A --slug \u6216\u53BB\u6389 --slug \u91CD\u8BD5\u3002`);
273959
+ }
273373
273960
  break;
273374
273961
  }
273375
273962
  if (positional[0] === "member") {
@@ -275308,20 +275895,20 @@ function instancePaths(name) {
275308
275895
  };
275309
275896
  }
275310
275897
  function listInstances() {
275311
- const found = [];
275312
- if (fs43.existsSync(instancePaths(DEFAULT_INSTANCE).configFile)) found.push(DEFAULT_INSTANCE);
275898
+ const found2 = [];
275899
+ if (fs43.existsSync(instancePaths(DEFAULT_INSTANCE).configFile)) found2.push(DEFAULT_INSTANCE);
275313
275900
  let entries = [];
275314
275901
  try {
275315
275902
  entries = fs43.readdirSync(instancesRoot());
275316
275903
  } catch {
275317
- return found;
275904
+ return found2;
275318
275905
  }
275319
275906
  for (const name of entries.sort()) {
275320
275907
  if (name === DEFAULT_INSTANCE) continue;
275321
275908
  if (!isValidInstanceName(name)) continue;
275322
- if (fs43.existsSync(instancePaths(name).configFile)) found.push(name);
275909
+ if (fs43.existsSync(instancePaths(name).configFile)) found2.push(name);
275323
275910
  }
275324
- return found;
275911
+ return found2;
275325
275912
  }
275326
275913
  function referencedVersionLabels() {
275327
275914
  const labels = /* @__PURE__ */ new Set();
@@ -275351,13 +275938,13 @@ var LEGACY_SYSTEMD_UNIT = "oasis-node.service";
275351
275938
  var LEGACY_LAUNCHD_LABEL = "com.oasis.node";
275352
275939
 
275353
275940
  // src/install.ts
275354
- var import_node_crypto82 = require("node:crypto");
275941
+ var import_node_crypto83 = require("node:crypto");
275355
275942
  var fs44 = __toESM(require("node:fs"));
275356
275943
  var path39 = __toESM(require("node:path"));
275357
275944
  function versionLabel(sourceFile, pkgVersion, npmPrefixDir, pkgName = LEGACY_PKG_NAME) {
275358
275945
  const tag = pkgName === LEGACY_PKG_NAME ? "" : `+pkg.${pkgName.replace(/[^A-Za-z0-9-]/g, "-")}`;
275359
275946
  if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return `${pkgVersion}${tag}`;
275360
- const digest = (0, import_node_crypto82.createHash)("sha256").update(fs44.readFileSync(sourceFile)).digest("hex").slice(0, 8);
275947
+ const digest = (0, import_node_crypto83.createHash)("sha256").update(fs44.readFileSync(sourceFile)).digest("hex").slice(0, 8);
275361
275948
  return tag ? `${pkgVersion}${tag}.local.${digest}` : `${pkgVersion}+local.${digest}`;
275362
275949
  }
275363
275950
  function isFromNpmPrefix(sourceFile, npmPrefixDir) {
@@ -275480,7 +276067,7 @@ function shimScript() {
275480
276067
  }
275481
276068
 
275482
276069
  // src/index.ts
275483
- var PKG_VERSION = true ? "2.2.33" : "dev";
276070
+ var PKG_VERSION = true ? "2.2.35" : "dev";
275484
276071
  var LOCAL_BIN = localBin();
275485
276072
  var NPM_PREFIX = npmPrefix();
275486
276073
  var INSTANCE = DEFAULT_INSTANCE;
@@ -275765,7 +276352,7 @@ function newInstanceName() {
275765
276352
  const existing = new Set(listInstances());
275766
276353
  if (!existing.has(DEFAULT_INSTANCE)) return DEFAULT_INSTANCE;
275767
276354
  for (; ; ) {
275768
- const n = `inst-${(0, import_node_crypto83.randomBytes)(3).toString("hex")}`;
276355
+ const n = `inst-${(0, import_node_crypto84.randomBytes)(3).toString("hex")}`;
275769
276356
  if (!existing.has(n)) return n;
275770
276357
  }
275771
276358
  }