oasis_test_v2 2.2.69 → 2.2.70

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 +1451 -531
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1465,6 +1465,22 @@ var init_chat = __esm({
1465
1465
  });
1466
1466
 
1467
1467
  // ../contract/src/session-outputs.ts
1468
+ function sessionOutputFilesOfParts(parts) {
1469
+ if (!Array.isArray(parts)) return [];
1470
+ const out = [];
1471
+ for (const raw of parts) {
1472
+ if (!raw || typeof raw !== "object") continue;
1473
+ const p2 = raw;
1474
+ if (p2.type !== "outputs" || !Array.isArray(p2.outputs)) continue;
1475
+ for (const f2 of p2.outputs) {
1476
+ if (!f2 || typeof f2 !== "object") continue;
1477
+ const file = f2;
1478
+ if (typeof file.path !== "string" || !file.path) continue;
1479
+ out.push({ path: file.path, name: typeof file.name === "string" && file.name ? file.name : file.path });
1480
+ }
1481
+ }
1482
+ return out;
1483
+ }
1468
1484
  function diffSessionOutputs(scanned, previous3) {
1469
1485
  const prev = previous3 ?? {};
1470
1486
  const changed = [];
@@ -1485,11 +1501,13 @@ function basenameOf(path44) {
1485
1501
  const i = path44.lastIndexOf("/");
1486
1502
  return i >= 0 ? path44.slice(i + 1) : path44;
1487
1503
  }
1488
- var SESSION_OUTPUTS_DIR;
1504
+ var SESSION_OUTPUTS_DIR, SESSION_OUTPUTS_ITEM_CODE, sessionOutputsItemId;
1489
1505
  var init_session_outputs = __esm({
1490
1506
  "../contract/src/session-outputs.ts"() {
1491
1507
  "use strict";
1492
1508
  SESSION_OUTPUTS_DIR = "outputs";
1509
+ SESSION_OUTPUTS_ITEM_CODE = "session_outputs";
1510
+ sessionOutputsItemId = (messageId) => `oasis-outputs:${messageId}`;
1493
1511
  }
1494
1512
  });
1495
1513
 
@@ -1677,6 +1695,24 @@ var init_chat_turn = __esm({
1677
1695
  });
1678
1696
 
1679
1697
  // ../contract/src/delegation.ts
1698
+ function hasDelegationDelivery(ev) {
1699
+ if (!ev) return false;
1700
+ return (ev.movedFiles ?? 0) > 0 || (ev.unchangedFiles ?? 0) > 0 || (ev.artifacts ?? 0) > 0 || Boolean(ev.roundText?.trim());
1701
+ }
1702
+ function delegationEnding(record9, evidence) {
1703
+ if (record9.state === "done") return "done";
1704
+ if (record9.state !== "failed") return "done";
1705
+ if (record9.failureKind !== "timeout") return "failed";
1706
+ return hasDelegationDelivery(evidence) ? "timeout-delivered" : "timeout-empty";
1707
+ }
1708
+ function delegationEndingOfRecord(record9, artifacts = []) {
1709
+ const last = record9.roundSummaries[record9.roundSummaries.length - 1];
1710
+ const lastRound = last?.round;
1711
+ return delegationEnding(record9, {
1712
+ ...last?.text ? { roundText: last.text } : {},
1713
+ artifacts: lastRound === void 0 ? 0 : artifacts.filter((a) => a.round === lastRound).length
1714
+ });
1715
+ }
1680
1716
  function trimRoundSummariesForReturn(summaries, recentRounds = DELEGATION_RETURN_RECENT_ROUNDS) {
1681
1717
  const keep = Math.max(0, recentRounds);
1682
1718
  const cut = Math.max(0, summaries.length - keep);
@@ -2046,6 +2082,94 @@ var init_chunked_message = __esm({
2046
2082
  }
2047
2083
  });
2048
2084
 
2085
+ // ../contract/src/command-card-copy.ts
2086
+ function stripInternalIds(text7, placeholder = "\u2026") {
2087
+ if (!text7) return text7;
2088
+ let out = text7;
2089
+ for (const re of [PREFIXED_ID_RE, UUID_RE, SHORT_ID_RE]) {
2090
+ out = out.replace(re, placeholder);
2091
+ }
2092
+ return out.replace(new RegExp(`[ \\t]+${placeholder}`, "gu"), placeholder);
2093
+ }
2094
+ function firstSentenceSummary(text7, max = CARD_SUMMARY_MAX) {
2095
+ const raw = (text7 ?? "").trim();
2096
+ if (!raw) return "";
2097
+ const cut = raw.split(/[。!?\n]|——/u)[0].trim() || raw.trim();
2098
+ const clean = stripInternalIds(cut).trim();
2099
+ const cps = [...clean];
2100
+ return cps.length > max ? `${cps.slice(0, max).join("")}\u2026` : clean;
2101
+ }
2102
+ function nodePhrase(args, refs) {
2103
+ const id = argStr(args, "artifactId");
2104
+ const title = id ? refs.nodeTitle?.(id)?.trim() : void 0;
2105
+ return title ? `\u300A${title}\u300B` : NEUTRAL_NODE;
2106
+ }
2107
+ function targetPhrase(naming, neutral) {
2108
+ const summary = firstSentenceSummary(naming?.summary);
2109
+ const nodeTitle = naming?.nodeTitle?.trim();
2110
+ if (summary) {
2111
+ return { phrase: nodeTitle && !summary.includes(nodeTitle) ? `\u300A${nodeTitle}\u300B\u2014\u2014${summary}` : summary, named: true };
2112
+ }
2113
+ if (nodeTitle) return { phrase: `\u300A${nodeTitle}\u300B\u4E0A\u7684${neutral}`, named: true };
2114
+ return { phrase: neutral, named: false };
2115
+ }
2116
+ function escalationPhraseOf(refs, escalationId) {
2117
+ return targetPhrase(escalationId ? refs.escalation?.(escalationId) : void 0, NEUTRAL_ESCALATION);
2118
+ }
2119
+ function gapPhraseOf(refs, gapId) {
2120
+ return targetPhrase(gapId ? refs.gap?.(gapId) : void 0, NEUTRAL_GAP);
2121
+ }
2122
+ function actionPhrase(verb, noun, target, neutralVerb = verb) {
2123
+ return target.named ? `${verb}${noun}\uFF1A${target.phrase}` : `${neutralVerb}${target.phrase}`;
2124
+ }
2125
+ function describeCommandForHuman(command2, args, refs = {}) {
2126
+ const reason = argStr(args, "reason");
2127
+ const reasonTail = reason ? `\uFF08\u7406\u7531\uFF1A${stripInternalIds(reason)}\uFF09` : "";
2128
+ switch (command2) {
2129
+ case "forceConclude":
2130
+ return `\u5F3A\u5236\u6536\u53E3${nodePhrase(args, refs)}${reasonTail}\u2014\u2014\u7ED5\u8FC7\u8BC4\u5BA1\u76F4\u63A5\u76D6\u91CC\u7A0B\u7891\u3002`;
2131
+ case "promote":
2132
+ return `\u628A${nodePhrase(args, refs)}\u8F6C\u4E3A\u957F\u671F\u4FDD\u7559\u7684\u8282\u70B9\uFF08\u4E0D\u518D\u968F\u4EFB\u52A1\u7ED3\u675F\u56DE\u6536\uFF09\u3002`;
2133
+ // continueBatch(续跑额度卡)**故意不在这里**:它的文案由引擎写好放在 `continuationBudget.request.summary`
2134
+ // 里,本来就是「《节点名》尚未完成,已达到自动派发上限。累计已运行 N 轮…」这种名称式写法,没有 id。
2135
+ // 重写一遍只会让同一句话有两个事实源,交给 {@link commandCardDisplayLabel} 的兜底清洗即可。
2136
+ case "resolveEscalation": {
2137
+ const escalation = escalationPhraseOf(refs, argStr(args, "escalationId"));
2138
+ const gapId = argStr(args, "gapId");
2139
+ if (!gapId) return `${actionPhrase("\u5173\u95ED", "\u6C42\u52A9", escalation)}${reasonTail}`;
2140
+ return [
2141
+ reason ? `\u91C7\u7528\u65B9\u6848\uFF1A${stripInternalIds(reason)}` : "\u786E\u8BA4\u6062\u590D\u65B9\u6848",
2142
+ actionPhrase("\u89E3\u51B3", "\u7F3A\u53E3", gapPhraseOf(refs, gapId)),
2143
+ actionPhrase("\u5173\u95ED\u5173\u8054", "\u6C42\u52A9", escalation, "\u5173\u95ED\u5173\u8054\u7684"),
2144
+ "\u7EE7\u7EED\u6267\u884C\uFF1A\u662F\uFF08\u4ECD\u987B\u6EE1\u8DB3\u5176\u4ED6\u963B\u585E\u6761\u4EF6\uFF09"
2145
+ ].join("\uFF1B");
2146
+ }
2147
+ default:
2148
+ return void 0;
2149
+ }
2150
+ }
2151
+ function commandCardDisplayLabel(command2, args, storedLabel, refs = {}) {
2152
+ const described = command2 ? describeCommandForHuman(command2, args ?? {}, refs) : void 0;
2153
+ return described ?? stripInternalIds(storedLabel ?? "");
2154
+ }
2155
+ var PREFIXED_ID_RE, UUID_RE, SHORT_ID_RE, CARD_SUMMARY_MAX, NEUTRAL_NODE, NEUTRAL_ESCALATION, NEUTRAL_GAP, argStr;
2156
+ var init_command_card_copy = __esm({
2157
+ "../contract/src/command-card-copy.ts"() {
2158
+ "use strict";
2159
+ PREFIXED_ID_RE = /(?:artifact|esc|gap|wk|ws|actor|draft|issue|review|rev|work|node|company|chat|sess|int|blob|run|continuation)[::][A-Za-z0-9][A-Za-z0-9:._#-]*/gu;
2160
+ UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/giu;
2161
+ SHORT_ID_RE = /\b(?=[0-9a-f-]*[0-9])(?=[0-9a-f-]*[a-f])[0-9a-f]{8,}(?:-[0-9a-f]{4,})*\b/giu;
2162
+ CARD_SUMMARY_MAX = 48;
2163
+ NEUTRAL_NODE = "\u8FD9\u4E00\u6B65";
2164
+ NEUTRAL_ESCALATION = "\u8FD9\u6761\u6C42\u52A9";
2165
+ NEUTRAL_GAP = "\u8FD9\u4E2A\u7F3A\u53E3";
2166
+ argStr = (args, key2) => {
2167
+ const v2 = args[key2];
2168
+ return typeof v2 === "string" && v2.trim() ? v2.trim() : void 0;
2169
+ };
2170
+ }
2171
+ });
2172
+
2049
2173
  // ../contract/src/index.ts
2050
2174
  var init_src = __esm({
2051
2175
  "../contract/src/index.ts"() {
@@ -2108,6 +2232,7 @@ var init_src = __esm({
2108
2232
  init_chat_turn();
2109
2233
  init_delegation();
2110
2234
  init_delegation();
2235
+ init_delegation();
2111
2236
  init_delegation_prompts();
2112
2237
  init_knowledge();
2113
2238
  init_knowledge_governance();
@@ -2115,6 +2240,7 @@ var init_src = __esm({
2115
2240
  init_file_preview();
2116
2241
  init_file_limits();
2117
2242
  init_chunked_message();
2243
+ init_command_card_copy();
2118
2244
  }
2119
2245
  });
2120
2246
 
@@ -2264,6 +2390,21 @@ function sessionRuntimeOverride(session) {
2264
2390
  if (!nodeId || !runtimeKind) return null;
2265
2391
  return { nodeId, runtimeKind };
2266
2392
  }
2393
+ function chatSessionInitialRuntime(input) {
2394
+ const trimmed = (v2) => typeof v2 === "string" && v2.trim() ? v2.trim() : null;
2395
+ const binding = input.binding?.nodeId && input.binding?.runtimeKind ? { nodeId: input.binding.nodeId, runtimeKind: input.binding.runtimeKind } : null;
2396
+ const requestedOverride = input.requested?.override;
2397
+ const picked = requestedOverride?.nodeId && requestedOverride?.runtimeKind ? { nodeId: requestedOverride.nodeId, runtimeKind: requestedOverride.runtimeKind } : null;
2398
+ const target = picked ?? binding;
2399
+ const followsAgentTarget = !picked || !!binding && picked.nodeId === binding.nodeId && picked.runtimeKind === binding.runtimeKind;
2400
+ const requestedModel = input.requested?.model;
2401
+ const model = requestedModel !== void 0 ? trimmed(requestedModel) : followsAgentTarget ? trimmed(input.agentModel) : null;
2402
+ return {
2403
+ overrideNodeId: target?.nodeId ?? null,
2404
+ overrideRuntimeKind: target?.runtimeKind ?? null,
2405
+ model
2406
+ };
2407
+ }
2267
2408
  function sessionRuntimeOverrideChangesTarget(session, binding) {
2268
2409
  const override = sessionRuntimeOverride(session);
2269
2410
  if (!override) return false;
@@ -2323,7 +2464,7 @@ var init_session_runtime_target = __esm({
2323
2464
  this.name = "SessionRuntimeOverrideError";
2324
2465
  }
2325
2466
  };
2326
- CLEAR_SESSION_RUNTIME_HINT = "\u6E05\u9664\u8FD9\u6761\u4F1A\u8BDD\u7684\u8FD0\u884C\u65F6\u9009\u62E9\u5373\u53EF\u7EE7\u7EED\uFF08\u4F1A\u56DE\u5230\u8FD9\u4F4D Agent \u7684\u9ED8\u8BA4\u7ED1\u5B9A\uFF09\u3002";
2467
+ CLEAR_SESSION_RUNTIME_HINT = "\u5728\u8FD0\u884C\u65F6\u9009\u62E9\u91CC\u70B9\u300C\u6062\u590D\u4E3A\u5458\u5DE5\u9ED8\u8BA4\u8FD0\u884C\u914D\u7F6E\u300D\u5373\u53EF\u7EE7\u7EED\uFF08\u4F1A\u628A\u8FD9\u4F4D Agent \u6B64\u523B\u7684\u9ED8\u8BA4\u914D\u7F6E\u6284\u8FDB\u8FD9\u6761\u4F1A\u8BDD\uFF09\u3002";
2327
2468
  }
2328
2469
  });
2329
2470
 
@@ -3905,9 +4046,57 @@ function newClaudeUsageAcc() {
3905
4046
  costUsd: null,
3906
4047
  messageUsageById: /* @__PURE__ */ new Map(),
3907
4048
  finalUsage: null,
4049
+ restored: null,
3908
4050
  saw: false
3909
4051
  };
3910
4052
  }
4053
+ function findClaudeSessionFile(configDir, cwd, runtimeSessionId) {
4054
+ const root = path7.join(configDir, "projects");
4055
+ const name = `${runtimeSessionId}.jsonl`;
4056
+ const direct = path7.join(root, cwd.replace(/[^a-zA-Z0-9]/g, "-"), name);
4057
+ if (fs7.existsSync(direct)) return direct;
4058
+ try {
4059
+ for (const slug6 of fs7.readdirSync(root)) {
4060
+ const candidate = path7.join(root, slug6, name);
4061
+ if (fs7.existsSync(candidate)) return candidate;
4062
+ }
4063
+ } catch {
4064
+ }
4065
+ return void 0;
4066
+ }
4067
+ function readClaudeRestoredCost(sessionFile) {
4068
+ try {
4069
+ const stat4 = fs7.statSync(sessionFile);
4070
+ if (!stat4.isFile() || stat4.size === 0) return null;
4071
+ const length = Math.min(stat4.size, RESTORED_COST_TAIL_BYTES);
4072
+ const buf = Buffer.alloc(length);
4073
+ const fd = fs7.openSync(sessionFile, "r");
4074
+ try {
4075
+ fs7.readSync(fd, buf, 0, length, stat4.size - length);
4076
+ } finally {
4077
+ fs7.closeSync(fd);
4078
+ }
4079
+ const lines = buf.toString("utf8").split("\n");
4080
+ for (let i = lines.length - 1; i >= 0; i--) {
4081
+ const line2 = lines[i];
4082
+ if (!line2.includes('"cost-state"')) continue;
4083
+ let parsed;
4084
+ try {
4085
+ parsed = JSON.parse(line2);
4086
+ } catch {
4087
+ continue;
4088
+ }
4089
+ if (parsed["type"] !== "cost-state") continue;
4090
+ const cost = parsed["totalCostUSD"];
4091
+ return {
4092
+ usage: parseModelUsage(parsed["modelUsage"]),
4093
+ costUsd: typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : null
4094
+ };
4095
+ }
4096
+ } catch {
4097
+ }
4098
+ return null;
4099
+ }
3911
4100
  function usageNum(v2) {
3912
4101
  return typeof v2 === "number" && Number.isFinite(v2) && v2 >= 0 ? v2 : void 0;
3913
4102
  }
@@ -3981,14 +4170,34 @@ function accumulateClaudeUsage(acc, p2) {
3981
4170
  acc.saw = true;
3982
4171
  }
3983
4172
  } else if (type === "result") {
3984
- acc.finalUsage = mergeFinalUsage(parseModelUsage(p2["modelUsage"]), parseResultUsage(p2["usage"]));
3985
4173
  const c = p2["total_cost_usd"];
3986
- if (typeof c === "number" && Number.isFinite(c)) {
3987
- acc.costUsd = c;
4174
+ const cost = typeof c === "number" && Number.isFinite(c) ? c : null;
4175
+ const { usage: modelUsage, costUsd } = sinceRestored(acc.restored, parseModelUsage(p2["modelUsage"]), cost);
4176
+ acc.finalUsage = mergeFinalUsage(modelUsage, parseResultUsage(p2["usage"]));
4177
+ if (costUsd != null) {
4178
+ acc.costUsd = costUsd;
3988
4179
  }
3989
4180
  if (acc.finalUsage || acc.costUsd != null) acc.saw = true;
3990
4181
  }
3991
4182
  }
4183
+ function sinceRestored(restored, usage, costUsd) {
4184
+ if (!restored) return { usage, costUsd };
4185
+ const out = usage ? { ...usage } : null;
4186
+ if (out && restored.usage) {
4187
+ for (const k2 of ["input", "output", "cacheRead", "cacheCreation"]) {
4188
+ const base = restored.usage[k2];
4189
+ const total = out[k2];
4190
+ if (total == null || base == null) continue;
4191
+ if (total < base) return { usage, costUsd };
4192
+ out[k2] = total - base;
4193
+ }
4194
+ }
4195
+ if (costUsd != null && restored.costUsd != null) {
4196
+ if (costUsd < restored.costUsd) return { usage, costUsd };
4197
+ return { usage: out, costUsd: costUsd - restored.costUsd };
4198
+ }
4199
+ return { usage: out, costUsd };
4200
+ }
3992
4201
  function newClaudeRunningUsage() {
3993
4202
  return { byMessage: /* @__PURE__ */ new Map(), currentMessageId: null, lastKey: "" };
3994
4203
  }
@@ -4089,13 +4298,14 @@ function classifyExit(result) {
4089
4298
  if (status === 429 || status === 529) return "rate-limit";
4090
4299
  return "error";
4091
4300
  }
4092
- var import_node_child_process3, import_node_crypto6, fs7, path7, readline, CLAUDE_BLOCKED_FLAGS, SUBAGENT_TRANSCRIPT_MAX_BYTES, SUBAGENT_TERMINAL, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
4301
+ var import_node_child_process3, import_node_crypto6, fs7, os4, path7, readline, CLAUDE_BLOCKED_FLAGS, SUBAGENT_TRANSCRIPT_MAX_BYTES, SUBAGENT_TERMINAL, RESTORED_COST_TAIL_BYTES, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
4093
4302
  var init_claude_code = __esm({
4094
4303
  "../adapters/src/claude-code/index.ts"() {
4095
4304
  "use strict";
4096
4305
  import_node_child_process3 = require("node:child_process");
4097
4306
  import_node_crypto6 = require("node:crypto");
4098
4307
  fs7 = __toESM(require("node:fs"), 1);
4308
+ os4 = __toESM(require("node:os"), 1);
4099
4309
  path7 = __toESM(require("node:path"), 1);
4100
4310
  readline = __toESM(require("node:readline"), 1);
4101
4311
  init_sync_skills();
@@ -4142,6 +4352,7 @@ var init_claude_code = __esm({
4142
4352
  killed: "stopped",
4143
4353
  stopped: "stopped"
4144
4354
  };
4355
+ RESTORED_COST_TAIL_BYTES = 4 * 1024 * 1024;
4145
4356
  ONE_SHOT_UNSAFE_TOOLS = ["ScheduleWakeup", "CronCreate", "CronDelete", "CronList"];
4146
4357
  ClaudeCodeAdapter = class {
4147
4358
  constructor(opts = {}) {
@@ -4238,6 +4449,9 @@ var init_claude_code = __esm({
4238
4449
  { runtime: "claude-code", actor: job.actor, ...job.dispatchId ? { dispatchId: job.dispatchId } : {} }
4239
4450
  )
4240
4451
  ];
4452
+ const claudeConfigDir = job.env?.["CLAUDE_CONFIG_DIR"] || this.opts.env?.["CLAUDE_CONFIG_DIR"] || process.env["CLAUDE_CONFIG_DIR"] || path7.join(os4.homedir(), ".claude");
4453
+ const resumedSessionFile = job.runtimeSessionId && job.resumeRuntimeSession ? findClaudeSessionFile(claudeConfigDir, dir, job.runtimeSessionId) : void 0;
4454
+ const restoredCost = resumedSessionFile ? readClaudeRestoredCost(resumedSessionFile) : null;
4241
4455
  const otelEnv = this.resolveOtelEnv(job, id);
4242
4456
  const containerized = await maybeContainerize({
4243
4457
  bin: this.opts.claudeBin ?? "claude",
@@ -4365,6 +4579,7 @@ var init_claude_code = __esm({
4365
4579
  const STDERR_MAX_LINES = 40;
4366
4580
  const STDERR_MAX_BYTES = 4e3;
4367
4581
  const usageAcc = newClaudeUsageAcc();
4582
+ usageAcc.restored = restoredCost;
4368
4583
  const runningUsage = newClaudeRunningUsage();
4369
4584
  const usageCheckpointCbs = [];
4370
4585
  let sessionModel;
@@ -4834,7 +5049,7 @@ var init_resilient = __esm({
4834
5049
 
4835
5050
  // ../adapters/src/_core/skill-cache.ts
4836
5051
  function defaultCacheRoot() {
4837
- return path8.join(os4.homedir(), ".oasis", "skill-cache");
5052
+ return path8.join(os5.homedir(), ".oasis", "skill-cache");
4838
5053
  }
4839
5054
  function sessionSkillsSubdir(runtimeKind) {
4840
5055
  switch (runtimeKind) {
@@ -5239,13 +5454,13 @@ async function verifyPendingLink(skillsDir, name, pend, log3) {
5239
5454
  }
5240
5455
  return true;
5241
5456
  }
5242
- var import_node_crypto7, fsp, os4, path8, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
5457
+ var import_node_crypto7, fsp, os5, path8, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
5243
5458
  var init_skill_cache = __esm({
5244
5459
  "../adapters/src/_core/skill-cache.ts"() {
5245
5460
  "use strict";
5246
5461
  import_node_crypto7 = require("node:crypto");
5247
5462
  fsp = __toESM(require("node:fs/promises"), 1);
5248
- os4 = __toESM(require("node:os"), 1);
5463
+ os5 = __toESM(require("node:os"), 1);
5249
5464
  path8 = __toESM(require("node:path"), 1);
5250
5465
  MANIFEST_SCHEMA_VERSION = 2;
5251
5466
  STALE_LOCK_MS = 6e4;
@@ -6435,7 +6650,7 @@ function codexSessionsRoot() {
6435
6650
  const candidates = [];
6436
6651
  const ch = process.env["CODEX_HOME"];
6437
6652
  if (ch) candidates.push(path10.join(ch, "sessions"));
6438
- const home2 = process.env["HOME"] || os5.homedir();
6653
+ const home2 = process.env["HOME"] || os6.homedir();
6439
6654
  if (home2) candidates.push(path10.join(home2, ".codex", "sessions"));
6440
6655
  for (const c of candidates) {
6441
6656
  try {
@@ -6758,7 +6973,7 @@ function codexTurnErrorMessage(value2) {
6758
6973
  const detail = typeof error2?.additionalDetails === "string" ? error2.additionalDetails.trim() : "";
6759
6974
  return [summary, detail].filter(Boolean).join("\n").slice(0, 4e3);
6760
6975
  }
6761
- var import_node_child_process7, import_node_crypto9, fs8, os5, path10, readline2, CODEX_BLOCKED_FLAGS, CODEX_ARGV_PROMPT_MAX_BYTES, CodexRolloutUsageTail, CODEX_APP_SERVER_BLOCKED_FLAGS, CodexAdapter;
6976
+ var import_node_child_process7, import_node_crypto9, fs8, os6, path10, readline2, CODEX_BLOCKED_FLAGS, CODEX_ARGV_PROMPT_MAX_BYTES, CodexRolloutUsageTail, CODEX_APP_SERVER_BLOCKED_FLAGS, CodexAdapter;
6762
6977
  var init_codex = __esm({
6763
6978
  "../adapters/src/codex/index.ts"() {
6764
6979
  "use strict";
@@ -6766,7 +6981,7 @@ var init_codex = __esm({
6766
6981
  init_container_runtime();
6767
6982
  import_node_crypto9 = require("node:crypto");
6768
6983
  fs8 = __toESM(require("node:fs"), 1);
6769
- os5 = __toESM(require("node:os"), 1);
6984
+ os6 = __toESM(require("node:os"), 1);
6770
6985
  path10 = __toESM(require("node:path"), 1);
6771
6986
  readline2 = __toESM(require("node:readline"), 1);
6772
6987
  init_sync_skills();
@@ -9372,7 +9587,7 @@ function pdeathsigAvailable(probe = defaultProbe) {
9372
9587
  return cachedAvailable;
9373
9588
  }
9374
9589
  function defaultProbe() {
9375
- if (os6.platform() !== "linux") return false;
9590
+ if (os7.platform() !== "linux") return false;
9376
9591
  try {
9377
9592
  const help = (0, import_node_child_process9.execFileSync)("setpriv", ["--help"], { stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).toString();
9378
9593
  return help.includes("--pdeathsig");
@@ -9386,12 +9601,12 @@ function pdeathsigWrap(bin, args) {
9386
9601
  function shouldBindRuntimePdeathsig() {
9387
9602
  return process.env["OASIS_BIND_RUNTIME_PDEATHSIG"] === "1" && pdeathsigAvailable();
9388
9603
  }
9389
- var import_node_child_process9, os6, cachedAvailable;
9604
+ var import_node_child_process9, os7, cachedAvailable;
9390
9605
  var init_pdeathsig = __esm({
9391
9606
  "../adapters/src/_core/pdeathsig.ts"() {
9392
9607
  "use strict";
9393
9608
  import_node_child_process9 = require("node:child_process");
9394
- os6 = __toESM(require("node:os"), 1);
9609
+ os7 = __toESM(require("node:os"), 1);
9395
9610
  }
9396
9611
  });
9397
9612
 
@@ -12832,7 +13047,13 @@ async function reconcileChatTurn(turn, deps) {
12832
13047
  ...status === "succeeded" || !restartCause ? {} : { cancelledBy: restartCause }
12833
13048
  });
12834
13049
  if (!ok3) return raced;
12835
- return { settled: true, turnId: turn.id, status, reason: `run \u7EC8\u6001 ${run.status}` };
13050
+ return {
13051
+ settled: true,
13052
+ turnId: turn.id,
13053
+ status,
13054
+ reason: `run \u7EC8\u6001 ${run.status}`,
13055
+ ...run.exitReason ? { exitReason: run.exitReason } : {}
13056
+ };
12836
13057
  }
12837
13058
  async function sweepStaleChatTurns(deps) {
12838
13059
  const before = new Date(deps.now - (deps.staleAfterMs ?? 2 * 6e4)).toISOString();
@@ -12854,7 +13075,8 @@ async function sweepStaleChatTurns(deps) {
12854
13075
  chatSessionId: turn.chatSessionId,
12855
13076
  turnId: turn.id,
12856
13077
  status: settledStatus,
12857
- reason: outcome.reason ?? `\u8F6E\u6B21\u6536\u53E3\u4E3A ${settledStatus}`
13078
+ reason: outcome.reason ?? `\u8F6E\u6B21\u6536\u53E3\u4E3A ${settledStatus}`,
13079
+ ...outcome.settled && outcome.exitReason ? { exitReason: outcome.exitReason } : {}
12858
13080
  }).catch((e) => {
12859
13081
  deps.log?.(`[chat-turn-sweep] \u8F6E\u6B21 ${turn.id} \u6536\u53E3\u540E\u7684\u56DE\u8C03\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u62CD\uFF09: ${String(e)}`);
12860
13082
  });
@@ -138139,6 +138361,13 @@ function openBackpressureOf(model, id) {
138139
138361
  function unresolvedGapsOf(model, id) {
138140
138362
  return (model.gaps.get(id) ?? []).filter((g2) => !g2.resolved);
138141
138363
  }
138364
+ function findGap(model, gapId) {
138365
+ for (const [artifactId, list2] of model.gaps) {
138366
+ const gap = list2.find((g2) => g2.gapId === gapId);
138367
+ if (gap) return { artifactId, gap };
138368
+ }
138369
+ return null;
138370
+ }
138142
138371
  function unresolvedEscalationsOf(model, id) {
138143
138372
  return (model.escalations.get(id) ?? []).filter((e) => !e.resolved).map((e) => ({
138144
138373
  // ADR 0079 D4:把稳定 id 一并带出——inbox / 卡片要靠它指明「关哪一条」(现状 resolve 是 artifact 级全清)。
@@ -142189,9 +142418,6 @@ ${supplemental.taskAppend.trim()}
142189
142418
  `;
142190
142419
  }
142191
142420
  }
142192
- if (this.opts.executionContinuityInstructions && continuityExposureOn && (spec.action === "produce" || spec.action === "integrate") && continuityArtifact) {
142193
- bundle.files["execution/CONTINUITY.md"] = this.opts.executionContinuityInstructions;
142194
- }
142195
142421
  if (this.opts.resolveExecutionResumePack && continuityExposureOn && (spec.action === "produce" || spec.action === "integrate") && continuityArtifact) {
142196
142422
  let resumePack = null;
142197
142423
  try {
@@ -142799,6 +143025,9 @@ ${supplemental.taskAppend.trim()}
142799
143025
  });
142800
143026
 
142801
143027
  // ../core/src/session-limits.ts
143028
+ function sessionWallClockMs(kind) {
143029
+ return SESSION_WALL_CLOCK_MS[kind];
143030
+ }
142802
143031
  var SESSION_WALL_CLOCK_MS;
142803
143032
  var init_session_limits = __esm({
142804
143033
  "../core/src/session-limits.ts"() {
@@ -142929,6 +143158,41 @@ ${lines.join("\n")}`, "rejected-proposal-repeat");
142929
143158
  }
142930
143159
  });
142931
143160
 
143161
+ // ../core/src/command-card-naming.ts
143162
+ function commandCardRefsOf(model) {
143163
+ const nodeTitle = (id) => model.artifacts.get(id)?.title?.trim() || void 0;
143164
+ return {
143165
+ nodeTitle,
143166
+ escalation: (escalationId) => {
143167
+ const found2 = escalationById(model, escalationId);
143168
+ if (!found2) return void 0;
143169
+ return { summary: found2.reason, ...nodeTitle(found2.artifactId) ? { nodeTitle: nodeTitle(found2.artifactId) } : {} };
143170
+ },
143171
+ gap: (gapId) => {
143172
+ const found2 = findGap(model, gapId);
143173
+ if (!found2) return void 0;
143174
+ return { summary: found2.gap.description, ...nodeTitle(found2.artifactId) ? { nodeTitle: nodeTitle(found2.artifactId) } : {} };
143175
+ }
143176
+ };
143177
+ }
143178
+ function annotateCommandCardLabels(model) {
143179
+ const refs = commandCardRefsOf(model);
143180
+ for (const review of model.pendingReviews.values()) {
143181
+ if (!review.command) continue;
143182
+ review.command = {
143183
+ ...review.command,
143184
+ displayLabel: commandCardDisplayLabel(review.command.command, review.command.args, review.command.effectLabel, refs)
143185
+ };
143186
+ }
143187
+ }
143188
+ var init_command_card_naming = __esm({
143189
+ "../core/src/command-card-naming.ts"() {
143190
+ "use strict";
143191
+ init_src();
143192
+ init_read_model();
143193
+ }
143194
+ });
143195
+
142932
143196
  // ../core/src/queue-retirement.ts
142933
143197
  function isSettledProduction(w2) {
142934
143198
  if (w2.lane === "reply") return false;
@@ -144244,6 +144508,7 @@ var init_kernel_bridge = __esm({
144244
144508
  init_closure_gate2();
144245
144509
  init_kernel();
144246
144510
  init_bridge_readmodel();
144511
+ init_command_card_naming();
144247
144512
  init_propose_guard();
144248
144513
  init_src6();
144249
144514
  init_src6();
@@ -144569,6 +144834,7 @@ var init_kernel_bridge = __esm({
144569
144834
  ...card2.priorRejections && card2.priorRejections.length > 0 ? { priorRejections: card2.priorRejections } : {}
144570
144835
  });
144571
144836
  }
144837
+ annotateCommandCardLabels(fresh);
144572
144838
  Object.assign(this.model, fresh);
144573
144839
  if (!this.sweepingStaleDrafts && this.draftCards.size > 0) {
144574
144840
  this.sweepingStaleDrafts = true;
@@ -146675,6 +146941,7 @@ var init_src7 = __esm({
146675
146941
  init_dispatcher();
146676
146942
  init_session_limits();
146677
146943
  init_rejected_proposals();
146944
+ init_command_card_naming();
146678
146945
  init_kernel_bridge();
146679
146946
  init_bridge_readmodel();
146680
146947
  init_queue_retirement();
@@ -165233,6 +165500,23 @@ async function waitForInflightInsert(rowId, timeoutMs) {
165233
165500
  })]);
165234
165501
  if (timer) clearTimeout(timer);
165235
165502
  }
165503
+ async function waitForAdmittedTurnStart(chatSessionId, deps) {
165504
+ if (deps.liveChat.isRunning(chatSessionId)) return;
165505
+ if (!await deps.ledgerTurnRunning()) return;
165506
+ await new Promise((resolve11) => {
165507
+ let stop = () => {
165508
+ };
165509
+ const timer = setTimeout(() => {
165510
+ stop();
165511
+ resolve11();
165512
+ }, deps.timeoutMs);
165513
+ timer.unref?.();
165514
+ stop = deps.liveChat.onNextTurn(chatSessionId, () => {
165515
+ clearTimeout(timer);
165516
+ resolve11();
165517
+ });
165518
+ });
165519
+ }
165236
165520
  async function appendNowThroughQueue(chatSessionId, text7, deps, clientKey, attachments, retainLocallyOnFailure = false, opts) {
165237
165521
  const newId = deps.newId ?? import_node_crypto22.randomUUID;
165238
165522
  const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
@@ -200780,6 +201064,150 @@ var init_knowledge2 = __esm({
200780
201064
  }
200781
201065
  });
200782
201066
 
201067
+ // ../server/src/chat-exit.ts
201068
+ function transcriptTail(ref2) {
201069
+ try {
201070
+ const stat4 = fs17.statSync(ref2);
201071
+ const size = Math.min(stat4.size, 4e3);
201072
+ const buf = Buffer.alloc(size);
201073
+ const fd = fs17.openSync(ref2, "r");
201074
+ try {
201075
+ fs17.readSync(fd, buf, 0, size, Math.max(0, stat4.size - size));
201076
+ } finally {
201077
+ fs17.closeSync(fd);
201078
+ }
201079
+ return buf.toString("utf8").trim();
201080
+ } catch {
201081
+ return "";
201082
+ }
201083
+ }
201084
+ function clipLine(line2) {
201085
+ return line2.length > SUMMARY_MAX_CHARS ? `${line2.slice(0, SUMMARY_MAX_CHARS)}\u2026` : line2;
201086
+ }
201087
+ function providerMessage(line2) {
201088
+ const m2 = /['"]message['"]\s*:\s*(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")/.exec(line2);
201089
+ const text7 = (m2?.[1] ?? m2?.[2])?.trim();
201090
+ return text7 ? text7 : void 0;
201091
+ }
201092
+ function failureKind(reason, line2) {
201093
+ if (line2.startsWith(SESSION_STARTUP_FAILED_PREFIX)) return "startup-failed";
201094
+ switch (reason) {
201095
+ case "auth-error":
201096
+ return "auth";
201097
+ case "server-unreachable":
201098
+ return "node-unreachable";
201099
+ case "timeout":
201100
+ return "timeout";
201101
+ case "output-limit":
201102
+ return "output-limit";
201103
+ case "rate-limit":
201104
+ return QUOTA_RE3.test(line2) ? "quota" : "rate-limit";
201105
+ default:
201106
+ break;
201107
+ }
201108
+ if (!line2) return "unknown";
201109
+ if (DISK_FULL_RE.test(line2)) return "disk-full";
201110
+ if (QUOTA_RE3.test(line2)) return "quota";
201111
+ if (AUTH_RE2.test(line2)) return "auth";
201112
+ if (RATE_LIMIT_RE.test(line2)) return "rate-limit";
201113
+ if (MODEL_RE.test(line2)) return "model";
201114
+ if (UNREACHABLE_RE2.test(line2)) return "node-unreachable";
201115
+ return "unknown";
201116
+ }
201117
+ function setChatWallClockMs(ms) {
201118
+ effectiveChatWallClockMs = typeof ms === "number" && ms > 0 ? ms : void 0;
201119
+ }
201120
+ function wallClockText(ms = effectiveChatWallClockMs ?? sessionWallClockMs("chat")) {
201121
+ const minutes = Math.round(ms / 6e4);
201122
+ return minutes > 0 && minutes % 60 === 0 ? `${minutes / 60} \u5C0F\u65F6` : `${minutes} \u5206\u949F`;
201123
+ }
201124
+ function resetTime(line2) {
201125
+ return extractResetHint(line2)?.replace(/^(?:resets?|try again at)\s+/i, "").trim() || void 0;
201126
+ }
201127
+ function failureSummary(kind, ctx) {
201128
+ switch (kind) {
201129
+ case "auth":
201130
+ return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u7684\u767B\u5F55\u5DF2\u8FC7\u671F\u6216\u51ED\u8BC1\u65E0\u6548\uFF0C\u8C03\u7528\u4E0D\u4E86\u6A21\u578B\u3002\u8BF7\u8282\u70B9\u7BA1\u7406\u5458\u5728\u90A3\u53F0\u673A\u5668\u4E0A\u91CD\u65B0\u767B\u5F55\u8FD9\u4E2A\u8FD0\u884C\u65F6\uFF08\u6216\u66F4\u65B0 API Key\uFF09\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u7740\u6025\u7684\u8BDD\u4E5F\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u53EF\u7528\u7684\u8FD0\u884C\u65F6")}\u3002`;
201131
+ case "quota": {
201132
+ const at = resetTime(ctx.line);
201133
+ return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u6A21\u578B\u989D\u5EA6\u5DF2\u7528\u5B8C${at ? `\uFF0C\u9884\u8BA1 ${at} \u6062\u590D` : ""}\u3002\u8BF7\u7B49\u989D\u5EA6\u6062\u590D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u7740\u6025\u7684\u8BDD\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u989D\u5EA6\u5145\u8DB3\u7684\u8FD0\u884C\u65F6")}\u3002`;
201134
+ }
201135
+ case "rate-limit":
201136
+ return "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u6A21\u578B\u670D\u52A1\u6682\u65F6\u9650\u6D41\u6216\u7E41\u5FD9\u3002\u8BF7\u7B49\u4E00\u4E24\u5206\u949F\u518D\u53D1\u4E00\u6B21\u3002";
201137
+ case "node-unreachable":
201138
+ return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u8FD9\u4F4D Agent \u7684\u8282\u70B9\u79BB\u7EBF\u6216\u8FDE\u63A5\u4E2D\u65AD\u3002\u8BF7\u786E\u8BA4\u90A3\u53F0\u673A\u5668\u5F00\u7740\u3001\u8282\u70B9\u7A0B\u5E8F\u5728\u8FD0\u884C\u3001\u7F51\u7EDC\u6B63\u5E38\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u4E5F\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u5728\u7EBF\u7684\u8FD0\u884C\u65F6")}\u3002`;
201139
+ case "model":
201140
+ return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD9\u4E2A\u8FD0\u884C\u65F6\u7528\u4E0D\u4E86\u5F53\u524D\u914D\u7F6E\u7684\u6A21\u578B\u3002\u8BF7${chatRebindHint("\u6362\u4E00\u4E2A\u53EF\u7528\u7684\u6A21\u578B")}\u540E\u518D\u53D1\u4E00\u6B21\u3002`;
201141
+ case "timeout":
201142
+ return `\u8FD9\u4E00\u6B21\u6267\u884C\u8DD1\u6EE1\u4E86\u5355\u6B21\u8FD0\u884C\u7684\u65F6\u95F4\u4E0A\u9650\uFF08${wallClockText()}\uFF09\uFF0C\u88AB\u7CFB\u7EDF\u505C\u4E0B\u4E86\u3002\u5DF2\u7ECF\u505A\u5B8C\u7684\u90E8\u5206\u4E0D\u4F1A\u4E22\uFF1B\u8981\u63A5\u7740\u505A\u7684\u8BDD\uFF0C\u628A\u5269\u4E0B\u7684\u90E8\u5206\u62C6\u5C0F\u4E00\u4E9B\u518D\u53D1\u4E00\u6B21\u3002`;
201143
+ case "startup-failed":
201144
+ return `\u5BF9\u8BDD\u5931\u8D25\uFF1A${ctx.readable || SESSION_STARTUP_FAILED_PREFIX}\u3002\u8FD9\u4E00\u8F6E\u6CA1\u6709\u5F00\u59CB\u8FD0\u884C\uFF0C\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u591A\u534A\u662F\u90A3\u53F0\u8282\u70B9\u8D1F\u8F7D\u8FC7\u9AD8\uFF0C\u8BF7\u7BA1\u7406\u5458\u7ED9\u5B83\u51CF\u8D1F\u6216${chatRebindHint("\u6362\u4E00\u4E2A\u8FD0\u884C\u65F6")}\u3002`;
201145
+ case "disk-full":
201146
+ return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u8FD9\u4F4D Agent \u7684\u8282\u70B9\u78C1\u76D8\u7A7A\u95F4\u6EE1\u4E86\uFF0C\u8FD9\u4E00\u8F6E\u5199\u4E0D\u4E86\u6587\u4EF6\u3002\u8BF7\u8282\u70B9\u7BA1\u7406\u5458\u6E05\u7406\u90A3\u53F0\u673A\u5668\u7684\u78C1\u76D8\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u7740\u6025\u7684\u8BDD\u4E5F\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u8FD0\u884C\u65F6")}\u3002`;
201147
+ case "output-limit":
201148
+ return "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u6A21\u578B\u8F93\u51FA\u8D85\u51FA\u957F\u5EA6\u4E0A\u9650\uFF0C\u56DE\u590D\u88AB\u622A\u65AD\u4E86\u3002\u53EF\u4EE5\u8BA9\u5B83\u5206\u51E0\u6B21\u8F93\u51FA\uFF0C\u6216\u628A\u4EFB\u52A1\u62C6\u5C0F\u4E00\u4E9B\u518D\u53D1\u3002";
201149
+ case "unknown":
201150
+ if (ctx.line) {
201151
+ return "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u62A5\u9519\u4E86\uFF0C\u6682\u65F6\u6CA1\u8BA4\u51FA\u662F\u4EC0\u4E48\u539F\u56E0\u3002\u53EF\u4EE5\u7A0D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u8BF7\u70B9\u300C\u5C55\u5F00\u8BE6\u60C5\u300D\u628A\u539F\u59CB\u62A5\u9519\u53D1\u7ED9\u7BA1\u7406\u5458\u3002";
201152
+ }
201153
+ return ctx.hasDetail ? "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u5F02\u5E38\u9000\u51FA\uFF0C\u6CA1\u6709\u7ED9\u51FA\u62A5\u9519\u539F\u56E0\u3002\u53EF\u4EE5\u7A0D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u8BF7\u70B9\u300C\u5C55\u5F00\u8BE6\u60C5\u300D\u628A\u539F\u59CB\u8BB0\u5F55\u53D1\u7ED9\u7BA1\u7406\u5458\u3002" : `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u5F02\u5E38\u9000\u51FA\uFF08\u9000\u51FA\u7801 ${ctx.code ?? "\u65E0"}\uFF09\uFF0C\u6CA1\u6709\u7559\u4E0B\u62A5\u9519\u4FE1\u606F\u3002\u53EF\u4EE5\u7A0D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u8BF7\u7BA1\u7406\u5458\u5230\u300C\u8FD0\u884C\u8F68\u8FF9\u300D\u91CC\u67E5\u8FD9\u4E00\u8F6E\u3002`;
201154
+ }
201155
+ }
201156
+ function buildChatExitError(info) {
201157
+ const chatExit = chatExitSummary(info);
201158
+ return Object.assign(new Error(chatExit.summary), { chatExit });
201159
+ }
201160
+ function chatExitSummary(info) {
201161
+ const genericLine = `runtime exited with code ${info.code ?? "null"}${info.reason ? ` (${info.reason})` : ""}`;
201162
+ let detail = "";
201163
+ if (info.reason !== "cancelled") {
201164
+ detail = info.errorMessage?.trim() ?? "";
201165
+ if (!detail && info.transcriptRef) detail = transcriptTail(info.transcriptRef);
201166
+ }
201167
+ let summary;
201168
+ let cancelledBy;
201169
+ if (info.reason === "cancelled") {
201170
+ cancelledBy = cancelCauseFromExitMessage(info.errorMessage);
201171
+ summary = chatCancelNote(cancelledBy);
201172
+ } else {
201173
+ const firstErrorLine = (info.errorMessage?.split("\n").map((line2) => line2.trim()).find(Boolean) ?? "").replace(INTERNAL_PREFIX_RE, "");
201174
+ const readable = firstErrorLine ? clipLine(providerMessage(firstErrorLine) ?? firstErrorLine) : "";
201175
+ summary = failureSummary(failureKind(info.reason, firstErrorLine), {
201176
+ readable,
201177
+ line: firstErrorLine,
201178
+ hasDetail: Boolean(detail),
201179
+ code: info.code
201180
+ });
201181
+ }
201182
+ return {
201183
+ ...info.reason ? { reason: info.reason } : {},
201184
+ summary,
201185
+ ...cancelledBy ? { cancelledBy } : {},
201186
+ ...info.reason !== "cancelled" ? { detail: (detail ? `${genericLine}
201187
+
201188
+ ${detail}` : genericLine).slice(0, 4200) } : {}
201189
+ };
201190
+ }
201191
+ var fs17, SUMMARY_MAX_CHARS, INTERNAL_PREFIX_RE, QUOTA_RE3, RATE_LIMIT_RE, AUTH_RE2, MODEL_RE, DISK_FULL_RE, UNREACHABLE_RE2, effectiveChatWallClockMs, chatRebindHint;
201192
+ var init_chat_exit = __esm({
201193
+ "../server/src/chat-exit.ts"() {
201194
+ "use strict";
201195
+ fs17 = __toESM(require("node:fs"), 1);
201196
+ init_src();
201197
+ init_src7();
201198
+ init_chat_cancel_attribution();
201199
+ SUMMARY_MAX_CHARS = 500;
201200
+ INTERNAL_PREFIX_RE = /^\[[a-z][a-z0-9_]*\]\s*/;
201201
+ QUOTA_RE3 = /hit your (?:\w+ )?limit|usage limit|quota|insufficient[_ ](?:balance|credit|funds)|credit balance|billing|\b402\b|额度|余额/i;
201202
+ RATE_LIMIT_RE = /rate.?limit|too many requests|\b429\b|\b529\b|overloaded|限流/i;
201203
+ AUTH_RE2 = /\b401\b|\b403\b|unauthori[sz]ed|authenticat|oauth|invalid[_ -]?(?:api[_ -]?key|token|x-api-key)|api[_ -]?key[^\n]{0,40}\b(?:invalid|expired|revoked)|token[^\n]{0,30}\b(?:expired|invalid|revoked)|not logged in|\/login|credential|凭证|登录/i;
201204
+ MODEL_RE = /\bmodel\b[^\n]{0,120}?\b(?:not found|not exist|does not exist|not available|not supported|unavailable|unknown)\b|\b(?:unknown|invalid|unsupported) model\b|model_not_found/i;
201205
+ DISK_FULL_RE = /\bENOSPC\b|no space left on device|\bEDQUOT\b|disk quota exceeded|\[Errno 28\]|disk (?:is )?full|磁盘(?:空间)?(?:已满|不足)/i;
201206
+ UNREACHABLE_RE2 = /不在线|socket 非 OPEN|帧未送达|失联/;
201207
+ chatRebindHint = (what) => `\u5728\u7EC4\u7EC7\u9875\u8FD9\u4F4D Agent \u7684\u300C\u6A21\u578B\u300D\u9875\u7B7E\u91CC${what}`;
201208
+ }
201209
+ });
201210
+
200783
201211
  // ../server/src/domains/delegations/parent-conversation.ts
200784
201212
  function firstLine2(content3) {
200785
201213
  const line2 = content3.split("\n").map((s2) => s2.trim()).find((s2) => s2.length > 0);
@@ -201180,6 +201608,14 @@ var init_files = __esm({
201180
201608
  });
201181
201609
 
201182
201610
  // ../server/src/domains/delegations/service.ts
201611
+ function exitReasonOf(err) {
201612
+ if (!(err instanceof Error)) return void 0;
201613
+ const chatExit = err.chatExit;
201614
+ return chatExit?.reason;
201615
+ }
201616
+ function roundFailureText(kind, error2) {
201617
+ return kind === "timeout" ? `\u8FD9\u4E00\u6B21\u6267\u884C\u8DD1\u6EE1\u4E86\u5355\u6B21\u8FD0\u884C\u7684\u65F6\u95F4\u4E0A\u9650\uFF08${wallClockText()}\uFF09\uFF0C\u88AB\u7CFB\u7EDF\u505C\u4E0B\u4E86\uFF08${error2}\uFF09` : `\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u5B8C\uFF1A${error2}`;
201618
+ }
201183
201619
  function deterministicUuid2(seed) {
201184
201620
  const h = (0, import_node_crypto36.createHash)("sha256").update(seed).digest();
201185
201621
  const b2 = Buffer.from(h.subarray(0, 16));
@@ -201242,6 +201678,7 @@ var init_service3 = __esm({
201242
201678
  "use strict";
201243
201679
  import_node_crypto36 = require("node:crypto");
201244
201680
  init_src();
201681
+ init_chat_exit();
201245
201682
  init_parent_conversation();
201246
201683
  init_title();
201247
201684
  init_deletion();
@@ -201929,7 +202366,7 @@ var init_service3 = __esm({
201929
202366
  try {
201930
202367
  await dispatched.done;
201931
202368
  } catch (err) {
201932
- failureKind2 = "interrupted";
202369
+ failureKind2 = exitReasonOf(err) === "timeout" ? "timeout" : "interrupted";
201933
202370
  error2 = err instanceof Error ? err.message : String(err);
201934
202371
  }
201935
202372
  const at = this.now();
@@ -201946,7 +202383,11 @@ var init_service3 = __esm({
201946
202383
  const round = (fresh.roundSummaries[fresh.roundSummaries.length - 1]?.round ?? 0) + 1;
201947
202384
  const newSummary = {
201948
202385
  round,
201949
- text: text7 || (failureKind2 ? `\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u5B8C\uFF1A${error2}` : ""),
202386
+ /* ⚠️ 到点被停下那一档**不往摘要里合成句子**(Bug 0221):`text` 从此是「专家这一轮到底
202387
+ 写出没写出正文」的判据——回流通告据它判「有没有可验证的交付」(`hasDelegationDelivery`)。
202388
+ 合成的那句话仍写进下面子会话的占位行(用户在子会话里要看到一句交代),
202389
+ 通告里空正文由 `delegationReturnText` 渲染成「(这一轮没有文本产出)」。 */
202390
+ text: text7 || (failureKind2 && failureKind2 !== "timeout" ? roundFailureText(failureKind2, error2) : ""),
201950
202391
  at,
201951
202392
  // 本轮的键 = 派发这一轮时的 dispatchSeq(`record` 就是派发时那一份,轮次账本业务键也由它算)。
201952
202393
  dispatchSeq: record9.dispatchSeq ?? 0
@@ -201956,7 +202397,7 @@ var init_service3 = __esm({
201956
202397
  const stopLoss = consecutiveFails >= CONSECUTIVE_FAIL_LIMIT;
201957
202398
  const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
201958
202399
  await chatStore?.updateMessage(childRoundMessageIds(fresh.childSessionId, round).placeholderId, {
201959
- content: text7 || (failureKind2 ? `\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u5B8C\uFF1A${error2}` : ""),
202400
+ content: text7 || (failureKind2 ? roundFailureText(failureKind2, error2) : ""),
201960
202401
  status: failureKind2 ? "error" : "done",
201961
202402
  completedAt: at
201962
202403
  }).catch(() => void 0);
@@ -202029,9 +202470,11 @@ var init_service3 = __esm({
202029
202470
  if (opts?.outcome === "succeeded") return this.settleSucceededFromSweep(record9, dispatchSeq, companyId);
202030
202471
  const at = this.now();
202031
202472
  const settledSeqAtRound = record9.settledSeq + 1;
202473
+ const timedOut = opts?.exitReason === "timeout";
202474
+ const partialAnswer = timedOut ? await this.readChildRoundAnswer(record9, dispatchSeq + 1, companyId).catch(() => "") : "";
202032
202475
  const newSummary = {
202033
202476
  round: (record9.roundSummaries[record9.roundSummaries.length - 1]?.round ?? 0) + 1,
202034
- text: `\u8FD9\u4E00\u8F6E\u88AB\u65F6\u949F\u95ED\u5408\uFF1A${reason}`,
202477
+ text: timedOut ? partialAnswer : `\u8FD9\u4E00\u8F6E\u88AB\u65F6\u949F\u95ED\u5408\uFF1A${reason}`,
202035
202478
  at,
202036
202479
  settledSeq: settledSeqAtRound,
202037
202480
  dispatchSeq
@@ -202039,7 +202482,8 @@ var init_service3 = __esm({
202039
202482
  const settled = await store.appendRoundSummary(record9.id, newSummary, {
202040
202483
  state: "failed",
202041
202484
  // 退出帧丢失后被时钟闭合 = `interrupted`(§9),不是 `not-started`——它确实起来过。
202042
- failureKind: "interrupted",
202485
+ // run 报的是墙钟超时时归 `timeout`(Bug 0221):同样起来过,但「到点被停」与「断了」说法不同。
202486
+ failureKind: timedOut ? "timeout" : "interrupted",
202043
202487
  consecutiveFails: record9.consecutiveFails + 1,
202044
202488
  settledSeq: settledSeqAtRound,
202045
202489
  settledAt: at
@@ -202640,11 +203084,11 @@ var init_routes2 = __esm({
202640
203084
  });
202641
203085
 
202642
203086
  // ../server/src/domains/delegations/store.ts
202643
- var fs17, path18, touchKey, settlesTerminal, MemoryDelegationStore, FileDelegationStore;
203087
+ var fs18, path18, touchKey, settlesTerminal, MemoryDelegationStore, FileDelegationStore;
202644
203088
  var init_store2 = __esm({
202645
203089
  "../server/src/domains/delegations/store.ts"() {
202646
203090
  "use strict";
202647
- fs17 = __toESM(require("node:fs"), 1);
203091
+ fs18 = __toESM(require("node:fs"), 1);
202648
203092
  path18 = __toESM(require("node:path"), 1);
202649
203093
  init_src();
202650
203094
  touchKey = (delegationId, messageId) => `${delegationId}\0${messageId}`;
@@ -202788,13 +203232,13 @@ var init_store2 = __esm({
202788
203232
  }
202789
203233
  static async open(file) {
202790
203234
  const store = new _FileDelegationStore(file);
202791
- if (fs17.existsSync(file)) {
202792
- const snap = JSON.parse(fs17.readFileSync(file, "utf8"));
203235
+ if (fs18.existsSync(file)) {
203236
+ const snap = JSON.parse(fs18.readFileSync(file, "utf8"));
202793
203237
  for (const d of snap.delegations ?? []) store.rows.set(d.id, d);
202794
203238
  for (const t of snap.touches ?? []) store.touches.set(touchKey(t.delegationId, t.messageId), t);
202795
203239
  } else {
202796
- fs17.mkdirSync(path18.dirname(file), { recursive: true });
202797
- fs17.writeFileSync(file, JSON.stringify({ delegations: [], touches: [] }, null, 2));
203240
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
203241
+ fs18.writeFileSync(file, JSON.stringify({ delegations: [], touches: [] }, null, 2));
202798
203242
  }
202799
203243
  return store;
202800
203244
  }
@@ -202803,7 +203247,7 @@ var init_store2 = __esm({
202803
203247
  delegations: [...this.rows.values()],
202804
203248
  touches: [...this.touches.values()]
202805
203249
  };
202806
- fs17.writeFileSync(this.file, JSON.stringify(snapshot, null, 2));
203250
+ fs18.writeFileSync(this.file, JSON.stringify(snapshot, null, 2));
202807
203251
  }
202808
203252
  async create(record9) {
202809
203253
  const r = await super.create(record9);
@@ -203007,8 +203451,18 @@ function delegationReturnText(terminal, opts) {
203007
203451
  const { record: record9 } = terminal;
203008
203452
  const who = terminal.expertName ?? record9.expertActorId;
203009
203453
  const name = record9.label ? `${who}\uFF08${record9.label}\uFF09` : who;
203010
- const head = record9.state === "done" ? `\u3010\u7CFB\u7EDF\u3011${name} \u5DF2\u5B8C\u6210\u3002` : `\u3010\u7CFB\u7EDF\u3011${name} \u65E0\u6CD5\u5B8C\u6210${record9.failureKind ? `\uFF08${record9.failureKind}\uFF09` : ""}\u3002`;
203011
- const lines = [head, `\u59D4\u6D3E id\uFF1A${record9.id.slice(0, 8)}\u3000label\uFF1A${record9.label ?? "\uFF08\u65E0\uFF09"}`];
203454
+ const lastRoundText = record9.roundSummaries[record9.roundSummaries.length - 1]?.text;
203455
+ const ending = delegationEnding(record9, {
203456
+ movedFiles: opts.movedPaths.length,
203457
+ unchangedFiles: opts.unchangedPaths?.length ?? 0,
203458
+ ...lastRoundText ? { roundText: lastRoundText } : {}
203459
+ });
203460
+ const cap = wallClockText();
203461
+ const head = ending === "done" ? `\u3010\u7CFB\u7EDF\u3011${name} \u5DF2\u5B8C\u6210\u3002` : ending === "timeout-delivered" ? `\u3010\u7CFB\u7EDF\u3011${name} \u8FD9\u4E00\u6B21\u6267\u884C\u8DD1\u6EE1\u4E86\u5355\u6B21\u8FD0\u884C\u7684\u65F6\u95F4\u4E0A\u9650\uFF08${cap}\uFF09\uFF0C\u88AB\u7CFB\u7EDF\u505C\u4E0B\u4E86\u2014\u2014\u5DF2\u7ECF\u4EA4\u56DE\u7684\u5185\u5BB9\u89C1\u4E0B\u3002` : ending === "timeout-empty" ? `\u3010\u7CFB\u7EDF\u3011${name} \u8FD9\u4E00\u6B21\u6267\u884C\u8DD1\u6EE1\u4E86\u5355\u6B21\u8FD0\u884C\u7684\u65F6\u95F4\u4E0A\u9650\uFF08${cap}\uFF09\uFF0C\u88AB\u7CFB\u7EDF\u505C\u4E0B\u4E86\uFF0C\u8FD9\u4E00\u8F6E\u6CA1\u6709\u4EA4\u56DE\u4EFB\u4F55\u5185\u5BB9\u3002` : `\u3010\u7CFB\u7EDF\u3011${name} \u65E0\u6CD5\u5B8C\u6210\u3002`;
203462
+ const lines = [
203463
+ head,
203464
+ `\u59D4\u6D3E id\uFF1A${record9.id.slice(0, 8)}\u3000label\uFF1A${record9.label ?? "\uFF08\u65E0\uFF09"}` + (record9.failureKind ? `\u3000\u7ED3\u675F\u539F\u56E0\uFF1A${record9.failureKind}` : "")
203465
+ ];
203012
203466
  const trimmed = trimRoundSummariesForReturn(record9.roundSummaries, opts.recentRounds);
203013
203467
  if (trimmed.length > 0) {
203014
203468
  lines.push("", `\u4E13\u5BB6\u5404\u8F6E\u5C0F\u7ED3\uFF08\u5171 ${trimmed.length} \u8F6E\uFF1B\u6700\u8FD1 ${opts.recentRounds} \u8F6E\u7ED9\u5168\u6587\uFF0C\u66F4\u65E9\u7684\u53EA\u7559\u9996\u884C\uFF0C\u5168\u6587\u7528 \`oasis delegate --status\` \u770B\uFF09\uFF1A`);
@@ -203043,7 +203497,7 @@ function delegationReturnText(terminal, opts) {
203043
203497
  }
203044
203498
  lines.push(
203045
203499
  "",
203046
- record9.state === "done" ? "\u8FD9\u4E9B\u662F\u6750\u6599\uFF0C\u4E0D\u662F\u7ED9\u7528\u6237\u7684\u6210\u54C1\uFF1A\u4F60\u6709\u8FD9\u6761\u5BF9\u8BDD\u7684\u5B8C\u6574\u4E0A\u4E0B\u6587\uFF0C\u81EA\u5DF1\u5224\u65AD\u8BE5\u8BB2\u54EA\u4E00\u53E5\u3001\u8981\u4E0D\u8981\u63A5\u7740\u5F80\u4E0B\u505A\u3002" : "\u5148\u81EA\u5DF1\u5224\u65AD\u80FD\u4E0D\u80FD\u63A5\u4F4F\uFF1A\u63A5\u7BA1\u3001\u6362\u4E2A\u505A\u6CD5\u3001\u8FD8\u662F\u91CD\u6D3E\u3002**\u522B\u628A\u4E13\u4E1A\u5931\u8D25\u539F\u6837\u4E22\u56DE\u7ED9\u7528\u6237**\u3002"
203500
+ record9.state === "done" ? "\u8FD9\u4E9B\u662F\u6750\u6599\uFF0C\u4E0D\u662F\u7ED9\u7528\u6237\u7684\u6210\u54C1\uFF1A\u4F60\u6709\u8FD9\u6761\u5BF9\u8BDD\u7684\u5B8C\u6574\u4E0A\u4E0B\u6587\uFF0C\u81EA\u5DF1\u5224\u65AD\u8BE5\u8BB2\u54EA\u4E00\u53E5\u3001\u8981\u4E0D\u8981\u63A5\u7740\u5F80\u4E0B\u505A\u3002" : ending === "timeout-delivered" ? "**\u8FD9\u4E0D\u662F\u4E13\u4E1A\u5931\u8D25\uFF0C\u662F\u65F6\u95F4\u5230\u4E86**\uFF1A\u5148\u770B\u4E0A\u9762\u5DF2\u7ECF\u4EA4\u56DE\u7684\u4E1C\u897F\uFF0C\u80FD\u7528\u5C31\u63A5\u7740\u7528\uFF1B\u8FD8\u6CA1\u505A\u5B8C\u7684\u90E8\u5206\uFF0C\u62C6\u6210\u4E00\u8F6E\u8DD1\u5F97\u5B8C\u7684\u91CF\u518D\u6D3E\u4E00\u6B21\u3002\u7ED9\u7528\u6237\u4EA4\u4EE3\u65F6\u522B\u8BF4\u300C\u65E0\u6CD5\u5B8C\u6210\u300D\u2014\u2014\u5DF2\u7ECF\u505A\u6210\u7684\u90E8\u5206\u7167\u5B9E\u8BF4\u3002" : ending === "timeout-empty" ? "**\u8FD9\u4E0D\u662F\u4E13\u4E1A\u5931\u8D25\uFF0C\u662F\u65F6\u95F4\u5230\u4E86**\uFF1A\u8FD9\u4E00\u8F6E\u6CA1\u6709\u7559\u4E0B\u53EF\u7528\u7684\u4EA7\u51FA\uFF0C\u628A\u4EFB\u52A1\u62C6\u5C0F\u4E00\u4E9B\u518D\u6D3E\u4E00\u6B21\u3002" : "\u5148\u81EA\u5DF1\u5224\u65AD\u80FD\u4E0D\u80FD\u63A5\u4F4F\uFF1A\u63A5\u7BA1\u3001\u6362\u4E2A\u505A\u6CD5\u3001\u8FD8\u662F\u91CD\u6D3E\u3002**\u522B\u628A\u4E13\u4E1A\u5931\u8D25\u539F\u6837\u4E22\u56DE\u7ED9\u7528\u6237**\u3002"
203047
203501
  );
203048
203502
  if (opts.movedPaths.length > 0 || (opts.unchangedPaths?.length ?? 0) > 0) {
203049
203503
  lines.push(
@@ -203197,6 +203651,7 @@ var init_return_flow = __esm({
203197
203651
  "use strict";
203198
203652
  init_chat_session();
203199
203653
  init_src();
203654
+ init_chat_exit();
203200
203655
  init_chat_system_message();
203201
203656
  init_chat_broadcast();
203202
203657
  init_files();
@@ -208338,7 +208793,7 @@ async function resolveProposeContent(kernel, blobs, args, artifactId, ctx) {
208338
208793
  }
208339
208794
  return resolveContent2(blobs, args);
208340
208795
  }
208341
- function authorizedCommandReceipt(command2, args, actor) {
208796
+ function authorizedCommandReceipt(command2, args, actor, refs = {}) {
208342
208797
  switch (command2) {
208343
208798
  case "continueBatch":
208344
208799
  return { message: `\u5DF2\u6388\u6743\u518D\u8FD0\u884C ${args.rounds} \u8F6E\uFF1B\u5176\u4ED6\u963B\u585E\u89E3\u9664\u540E\u7EE7\u7EED\u3002` };
@@ -208347,7 +208802,7 @@ function authorizedCommandReceipt(command2, args, actor) {
208347
208802
  case "promote":
208348
208803
  return { message: "promoted to persistent" };
208349
208804
  case "resolveEscalation":
208350
- return { message: optStr(args, "gapId") ? `\u5DF2\u786E\u8BA4\u6062\u590D\u65B9\u6848\uFF0C\u89E3\u51B3\u7F3A\u53E3 ${str(args, "gapId")}\u3001\u5173\u95ED\u5173\u8054\u4E0A\u62A5 ${str(args, "escalationId")}\uFF1B\u8C03\u5EA6\u5668\u5C06\u5728\u5176\u4ED6\u963B\u585E\u89E3\u9664\u540E\u7EE7\u7EED\u6267\u884C\u3002` : `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
208805
+ return { message: optStr(args, "gapId") ? `\u5DF2\u786E\u8BA4\u6062\u590D\u65B9\u6848\uFF0C${actionPhrase("\u89E3\u51B3", "\u7F3A\u53E3", gapPhraseOf(refs, optStr(args, "gapId")))}\u3001${actionPhrase("\u5173\u95ED\u5173\u8054", "\u6C42\u52A9", escalationPhraseOf(refs, optStr(args, "escalationId")), "\u5173\u95ED\u5173\u8054\u7684")}\uFF1B\u8C03\u5EA6\u5668\u5C06\u5728\u5176\u4ED6\u963B\u585E\u89E3\u9664\u540E\u7EE7\u7EED\u6267\u884C\u3002` : `\u5DF2${actionPhrase("\u5173\u95ED", "\u6C42\u52A9", escalationPhraseOf(refs, optStr(args, "escalationId")))}\u3002` };
208351
208806
  default:
208352
208807
  throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u65E0\u56DE\u6267\u6784\u9020 ${command2}`);
208353
208808
  }
@@ -209069,7 +209524,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
209069
209524
  ...optStr(args, "reason") !== void 0 ? { reason: optStr(args, "reason") } : {},
209070
209525
  ...escId !== void 0 ? { escalationId: escId } : {}
209071
209526
  });
209072
- return { message: escId !== void 0 ? `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${escId}\uFF09\u3002` : "\u8BE5\u8282\u70B9\u7684\u672A\u89E3\u4E0A\u62A5\u5DF2\u5168\u90E8\u5173\u95ED\u3002" };
209527
+ return { message: escId !== void 0 ? `\u5DF2${actionPhrase("\u5173\u95ED", "\u6C42\u52A9", escalationPhraseOf(commandCardRefsOf(kernel.model), escId))}\u3002` : "\u8BE5\u8282\u70B9\u7684\u672A\u89E3\u4E0A\u62A5\u5DF2\u5168\u90E8\u5173\u95ED\u3002" };
209073
209528
  }
209074
209529
  case "addRevision": {
209075
209530
  const addRevId = str(args, "artifactId");
@@ -209135,7 +209590,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
209135
209590
  }
209136
209591
  case "promote": {
209137
209592
  await kernel.promote({ artifactId: str(args, "artifactId"), actor });
209138
- return authorizedCommandReceipt(command2, args, actor);
209593
+ return authorizedCommandReceipt(command2, args, actor, commandCardRefsOf(kernel.model));
209139
209594
  }
209140
209595
  case "hold": {
209141
209596
  const artifactId = str(args, "artifactId");
@@ -209179,7 +209634,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
209179
209634
  reason: str(args, "reason"),
209180
209635
  note: optStr(args, "note")
209181
209636
  });
209182
- return authorizedCommandReceipt(command2, args, actor);
209637
+ return authorizedCommandReceipt(command2, args, actor, commandCardRefsOf(kernel.model));
209183
209638
  }
209184
209639
  case "unlink": {
209185
209640
  await kernel.unlinkInput({ artifactId: str(args, "artifactId"), to: str(args, "to"), actor });
@@ -209705,19 +210160,20 @@ async function startOasisServer(opts) {
209705
210160
  return { accepted: r.accepted, ...r.reason ? { reason: r.reason } : {}, queued: r.queued, insertStatus: r.insertStatus };
209706
210161
  };
209707
210162
  const GRAPH_EDIT_SUMMARY_MAX = 60;
210163
+ const NEUTRAL_APPROVER = "\u8D1F\u8D23\u4EBA";
209708
210164
  const truncateSummary = (s2) => {
209709
210165
  const cps = [...s2];
209710
210166
  return cps.length > GRAPH_EDIT_SUMMARY_MAX ? `${cps.slice(0, GRAPH_EDIT_SUMMARY_MAX).join("")}\u2026` : s2;
209711
210167
  };
209712
210168
  const authorizationStatusText = (resolution, ctx) => {
209713
210169
  const approved = resolution.status === "approved";
209714
- const by = ctx.authorizedBy ?? "\u7BA1\u7406\u8005";
210170
+ const by = ctx.authorizedByName?.trim() || NEUTRAL_APPROVER;
209715
210171
  const reason = resolution.status === "denied" ? resolution.reason ?? ctx.reason : ctx.reason;
209716
210172
  if (ctx.kind === "graph-edit") {
209717
210173
  const summary = truncateSummary((ctx.effectLabel ?? "").trim()) || "\uFF08\u672A\u586B\u5199\u7406\u7531\u7684\u56FE\u53D8\u66F4\uFF09";
209718
210174
  return approved ? `\u300C${summary}\u300D\u7684\u53D8\u66F4\u5DF2\u7531 ${by} \u6279\u51C6\u5E76\u843D\u5730\u3002` : `\u300C${summary}\u300D\u7684\u53D8\u66F4\u5DF2\u88AB ${by} \u9A73\u56DE${reason ? `\uFF1B\u7406\u7531\uFF1A${reason}` : ""}\u3002`;
209719
210175
  }
209720
- const label2 = ctx.effectLabel || ctx.command || "";
210176
+ const label2 = ctx.displayLabel || stripInternalIds(ctx.effectLabel ?? "") || ctx.command || "";
209721
210177
  return approved ? `\u547D\u4EE4\u5DF2\u7531 ${by} \u6279\u51C6\u5E76\u4EE5\u5176\u540D\u4E49\u6267\u884C${label2 ? `\uFF1A${label2}` : ""}\u3002` : `\u547D\u4EE4\u5DF2\u88AB ${by} \u9A73\u56DE${reason ? `\uFF1A${reason}` : ""}\u3002`;
209722
210178
  };
209723
210179
  const GRAPH_EDIT_IN_FLIGHT_SUFFIX = "\uFF08\u8FD9\u6761\u662F\u5728\u4F60\u672C\u8F6E\u8FDB\u884C\u4E2D\u63D2\u5165\u7684\u901A\u77E5\uFF1A\u56DE\u5E94\u5B8C\u540E\u63A5\u7740\u505A\u4F60\u624B\u4E0A\u8FD8\u6CA1\u505A\u5B8C\u7684\u4E8B\uFF0C\u522B\u5C31\u6B64\u6536\u5C3E\u3002\uFF09";
@@ -211140,13 +211596,23 @@ async function startOasisServer(opts) {
211140
211596
  const { draftId } = JSON.parse(rawBody);
211141
211597
  const review = engine.kernel.model.pendingReviews.get(draftId);
211142
211598
  if (!review) throw new Error(`\u8349\u6848\u4E0D\u5B58\u5728\u6216\u5DF2\u5904\u7406\uFF1A${draftId}`);
211599
+ const authorizedByName = (await (engine.registry ?? opts.registry)?.getActor(actor).catch(() => null))?.name?.trim();
211143
211600
  if (review.kind === "command" && review.command) {
211144
211601
  const { command: command2, artifactId } = await engine.kernel.applyAuthorizedCommand({ draftId, actor });
211145
- const receipt = authorizedCommandReceipt(command2, review.command.args, actor);
211602
+ const receipt = authorizedCommandReceipt(command2, review.command.args, actor, commandCardRefsOf(engine.kernel.model));
211146
211603
  notifyReviewResolved(
211147
211604
  draftId,
211148
211605
  { status: "approved", command: command2, effectLabel: review.command.effectLabel, authorizedBy: actor, receipt },
211149
- { origin: review.origin, kind: "command", authorizedBy: actor, effectLabel: review.command.effectLabel, command: command2 },
211606
+ {
211607
+ origin: review.origin,
211608
+ kind: "command",
211609
+ authorizedBy: actor,
211610
+ ...authorizedByName ? { authorizedByName } : {},
211611
+ // effectLabel 带 id:RESUME 提示词给起草 agent 用;displayLabel 是状态条(给人)那句。
211612
+ effectLabel: review.command.effectLabel,
211613
+ ...review.command.displayLabel ? { displayLabel: review.command.displayLabel } : {},
211614
+ command: command2
211615
+ },
211150
211616
  chatStoreNowOrNull,
211151
211617
  currentCompanyId
211152
211618
  );
@@ -211165,7 +211631,7 @@ async function startOasisServer(opts) {
211165
211631
  { status: "approved", command: "", effectLabel: review.plan.reason, authorizedBy: actor },
211166
211632
  // 状态条要的两样也得进 resume 上下文(不能只放在 resolution 里):`effectLabel` 是改图卡的
211167
211633
  // 「改动摘要」(= plan.reason,见 diagnosis 口径 1),`authorizedBy` 是「谁批的」。
211168
- { origin: review.origin, kind: review.kind ?? "graph-edit", authorizedBy: actor, effectLabel: review.plan.reason },
211634
+ { origin: review.origin, kind: review.kind ?? "graph-edit", authorizedBy: actor, ...authorizedByName ? { authorizedByName } : {}, effectLabel: review.plan.reason },
211169
211635
  chatStoreNowOrNull,
211170
211636
  // 续跑那一轮照样可能往 outputs/ 写东西——按公司出产出卡(与命令卡那支同口径)。
211171
211637
  currentCompanyId
@@ -211195,6 +211661,7 @@ async function startOasisServer(opts) {
211195
211661
  return;
211196
211662
  }
211197
211663
  const { recorded } = await engine.kernel.rejectIntervention({ draftId, anchor: review.anchor, actor, ...reason !== void 0 ? { reason } : {} });
211664
+ const authorizedByName = (await (engine.registry ?? opts.registry)?.getActor(actor).catch(() => null))?.name?.trim();
211198
211665
  notifyReviewResolved(
211199
211666
  draftId,
211200
211667
  { status: "denied", ...reason !== void 0 ? { reason } : {} },
@@ -211202,10 +211669,13 @@ async function startOasisServer(opts) {
211202
211669
  origin: review.origin,
211203
211670
  kind: review.kind ?? "graph-edit",
211204
211671
  authorizedBy: actor,
211672
+ ...authorizedByName ? { authorizedByName } : {},
211205
211673
  // 状态条要的那段「效果标签 / 改动摘要」:命令卡用命令的效果标签,改图卡用 `plan.reason`
211206
211674
  // ——与 apply-draft 改图那一支同源(那里传的就是 `review.plan.reason`),否则驳回档的
211207
211675
  // 改图卡状态条会永远拿不到摘要、只能落占位符(diagnosis 口径 1)。
211208
211676
  ...review.command?.effectLabel || review.plan.reason ? { effectLabel: review.command?.effectLabel || review.plan.reason } : {},
211677
+ // Bug 0215:驳回档的状态条同样走人读那句(命令卡才有;改图卡的摘要仍是 plan.reason)。
211678
+ ...review.command?.displayLabel ? { displayLabel: review.command.displayLabel } : {},
211209
211679
  ...review.command?.command ? { command: review.command.command } : {},
211210
211680
  ...reason !== void 0 ? { reason } : {}
211211
211681
  },
@@ -212036,6 +212506,15 @@ async function startOasisServer(opts) {
212036
212506
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "EMPTY_TEXT" }));
212037
212507
  return;
212038
212508
  }
212509
+ await waitForAdmittedTurnStart(sid, {
212510
+ liveChat,
212511
+ ledgerTurnRunning: async () => {
212512
+ const turns = opts.resolveChatTurns ? await opts.resolveChatTurns(currentCompanyId) : opts.chatTurns;
212513
+ const status = (await turns?.activeTurn(sid).catch(() => null))?.status;
212514
+ return status === "reserved" || status === "running";
212515
+ },
212516
+ timeoutMs: APPEND_TURN_START_WAIT_MS
212517
+ });
212039
212518
  await respondAppend(res, wantsAppendProgress(req), (onAwaitingConsumption) => appendNowThroughQueue2(sid, text7, clientKey, insertAttachments, perCompanyStore, retainLocallyOnFailure, onAwaitingConsumption, resend));
212040
212519
  return;
212041
212520
  }
@@ -214107,7 +214586,7 @@ async function markDeliveredWithRetry(store, messageId, attempts = 3) {
214107
214586
  }
214108
214587
  return false;
214109
214588
  }
214110
- var http, import_node_crypto41, githubAppPending, connectorAuthSnapshots, CONNECTOR_AUTH_SNAPSHOT_TTL_MS, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, STREAMING_PATHS, PLANNER_BLOCKING_CODES, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
214589
+ var http, import_node_crypto41, githubAppPending, connectorAuthSnapshots, CONNECTOR_AUTH_SNAPSHOT_TTL_MS, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, APPEND_TURN_START_WAIT_MS, STREAMING_PATHS, PLANNER_BLOCKING_CODES, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
214111
214590
  var init_server3 = __esm({
214112
214591
  "../server/src/server.ts"() {
214113
214592
  "use strict";
@@ -214122,6 +214601,7 @@ var init_server3 = __esm({
214122
214601
  init_src();
214123
214602
  init_src();
214124
214603
  init_src();
214604
+ init_src();
214125
214605
  init_file_http();
214126
214606
  init_build_info();
214127
214607
  init_artifact_content2();
@@ -214181,6 +214661,7 @@ var init_server3 = __esm({
214181
214661
  defaultResolveActor = (token) => token.startsWith("token:") ? token.slice("token:".length) : null;
214182
214662
  enc = (s2) => new TextEncoder().encode(s2);
214183
214663
  CHAT_OUTPUT_LIMIT = 16e3;
214664
+ APPEND_TURN_START_WAIT_MS = 1e4;
214184
214665
  STREAMING_PATHS = ["/api/events", "/api/chat", "/api/trajectory/events", "/api/intervention/wait"];
214185
214666
  PLANNER_BLOCKING_CODES = /* @__PURE__ */ new Set([
214186
214667
  "title_collision",
@@ -214271,12 +214752,12 @@ function createTokenIssuer(opts = {}) {
214271
214752
  function createNodeTokenStore(file) {
214272
214753
  const read3 = () => {
214273
214754
  try {
214274
- return JSON.parse(fs18.readFileSync(file, "utf8"));
214755
+ return JSON.parse(fs19.readFileSync(file, "utf8"));
214275
214756
  } catch {
214276
214757
  return {};
214277
214758
  }
214278
214759
  };
214279
- const write = (table) => fs18.writeFileSync(file, JSON.stringify(table, null, 2), { mode: 384 });
214760
+ const write = (table) => fs19.writeFileSync(file, JSON.stringify(table, null, 2), { mode: 384 });
214280
214761
  return {
214281
214762
  issue(nodeId) {
214282
214763
  const table = read3();
@@ -214302,14 +214783,14 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
214302
214783
  const read3 = () => {
214303
214784
  if (!file) return table;
214304
214785
  try {
214305
- return new Map(Object.entries(JSON.parse(fs18.readFileSync(file, "utf8"))));
214786
+ return new Map(Object.entries(JSON.parse(fs19.readFileSync(file, "utf8"))));
214306
214787
  } catch {
214307
214788
  return /* @__PURE__ */ new Map();
214308
214789
  }
214309
214790
  };
214310
214791
  const write = (m2) => {
214311
214792
  if (!file) return;
214312
- fs18.writeFileSync(file, JSON.stringify(Object.fromEntries(m2), null, 2), { mode: 384 });
214793
+ fs19.writeFileSync(file, JSON.stringify(Object.fromEntries(m2), null, 2), { mode: 384 });
214313
214794
  };
214314
214795
  const table = /* @__PURE__ */ new Map();
214315
214796
  const prune = () => {
@@ -214371,12 +214852,12 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
214371
214852
  }
214372
214853
  };
214373
214854
  }
214374
- var import_node_crypto42, fs18, path19, SESSION_TOKEN_PREFIX, b64url2, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
214855
+ var import_node_crypto42, fs19, path19, SESSION_TOKEN_PREFIX, b64url2, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
214375
214856
  var init_tokens = __esm({
214376
214857
  "../server/src/tokens.ts"() {
214377
214858
  "use strict";
214378
214859
  import_node_crypto42 = require("node:crypto");
214379
- fs18 = __toESM(require("node:fs"), 1);
214860
+ fs19 = __toESM(require("node:fs"), 1);
214380
214861
  path19 = __toESM(require("node:path"), 1);
214381
214862
  SESSION_TOKEN_PREFIX = "oat_v2_";
214382
214863
  b64url2 = (value2) => Buffer.from(value2).toString("base64url");
@@ -214384,13 +214865,13 @@ var init_tokens = __esm({
214384
214865
  readOrCreateSecret = (file) => {
214385
214866
  if (!file) return (0, import_node_crypto42.randomBytes)(32);
214386
214867
  try {
214387
- const raw = fs18.readFileSync(file, "utf8").trim();
214868
+ const raw = fs19.readFileSync(file, "utf8").trim();
214388
214869
  if (raw) return Buffer.from(raw, "base64url");
214389
214870
  } catch {
214390
214871
  }
214391
214872
  const secret = (0, import_node_crypto42.randomBytes)(32);
214392
- fs18.mkdirSync(path19.dirname(file), { recursive: true });
214393
- fs18.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
214873
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
214874
+ fs19.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
214394
214875
  return secret;
214395
214876
  };
214396
214877
  sign = (secret, payload2) => (0, import_node_crypto42.createHmac)("sha256", secret).update(payload2).digest("base64url");
@@ -214456,12 +214937,12 @@ function createDataplaneTokenIssuer(opts = {}) {
214456
214937
  }
214457
214938
  };
214458
214939
  }
214459
- var import_node_crypto43, fs19, path20, PREFIX2, b64url3, fromB64url2, sign2, sigMatches, readOrCreateSecret2, isClaims, isDataplaneToken;
214940
+ var import_node_crypto43, fs20, path20, PREFIX2, b64url3, fromB64url2, sign2, sigMatches, readOrCreateSecret2, isClaims, isDataplaneToken;
214460
214941
  var init_session_dataplane_token = __esm({
214461
214942
  "../server/src/session-dataplane-token.ts"() {
214462
214943
  "use strict";
214463
214944
  import_node_crypto43 = require("node:crypto");
214464
- fs19 = __toESM(require("node:fs"), 1);
214945
+ fs20 = __toESM(require("node:fs"), 1);
214465
214946
  path20 = __toESM(require("node:path"), 1);
214466
214947
  init_src();
214467
214948
  PREFIX2 = "osd_";
@@ -214476,13 +214957,13 @@ var init_session_dataplane_token = __esm({
214476
214957
  readOrCreateSecret2 = (file) => {
214477
214958
  if (!file) return (0, import_node_crypto43.randomBytes)(32);
214478
214959
  try {
214479
- const raw = fs19.readFileSync(file, "utf8").trim();
214960
+ const raw = fs20.readFileSync(file, "utf8").trim();
214480
214961
  if (raw) return Buffer.from(raw, "base64url");
214481
214962
  } catch {
214482
214963
  }
214483
214964
  const secret = (0, import_node_crypto43.randomBytes)(32);
214484
- fs19.mkdirSync(path20.dirname(file), { recursive: true });
214485
- fs19.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
214965
+ fs20.mkdirSync(path20.dirname(file), { recursive: true });
214966
+ fs20.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
214486
214967
  return secret;
214487
214968
  };
214488
214969
  isClaims = (v2) => {
@@ -220080,13 +220561,13 @@ function applyTurnPatch(t, patch) {
220080
220561
  ...patch.lastError !== void 0 ? { lastError: patch.lastError } : {}
220081
220562
  };
220082
220563
  }
220083
- var import_node_crypto46, fs20, path21, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
220564
+ var import_node_crypto46, fs21, path21, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
220084
220565
  var init_dev_store = __esm({
220085
220566
  "../server/src/dev-store.ts"() {
220086
220567
  "use strict";
220087
220568
  init_src();
220088
220569
  import_node_crypto46 = require("node:crypto");
220089
- fs20 = __toESM(require("node:fs"), 1);
220570
+ fs21 = __toESM(require("node:fs"), 1);
220090
220571
  path21 = __toESM(require("node:path"), 1);
220091
220572
  init_src();
220092
220573
  init_src9();
@@ -220100,8 +220581,8 @@ var init_dev_store = __esm({
220100
220581
  }
220101
220582
  static async open(file) {
220102
220583
  const memory = new MemoryOplogStore();
220103
- if (fs20.existsSync(file)) {
220104
- for (const line2 of fs20.readFileSync(file, "utf8").split("\n")) {
220584
+ if (fs21.existsSync(file)) {
220585
+ for (const line2 of fs21.readFileSync(file, "utf8").split("\n")) {
220105
220586
  if (!line2.trim()) continue;
220106
220587
  const stored = JSON.parse(line2);
220107
220588
  const { artifactId, seq, ...op } = stored;
@@ -220109,15 +220590,15 @@ var init_dev_store = __esm({
220109
220590
  if (assigned !== seq) throw new Error(`corrupt ndjson log: seq mismatch at ${line2}`);
220110
220591
  }
220111
220592
  } else {
220112
- fs20.mkdirSync(path21.dirname(file), { recursive: true });
220113
- fs20.writeFileSync(file, "");
220593
+ fs21.mkdirSync(path21.dirname(file), { recursive: true });
220594
+ fs21.writeFileSync(file, "");
220114
220595
  }
220115
220596
  return new _NdjsonOplogStore(file, memory);
220116
220597
  }
220117
220598
  async append(artifactId, op, expectedSeq) {
220118
220599
  const seq = await this.memory.append(artifactId, op, expectedSeq);
220119
220600
  const stored = { ...op, artifactId, seq };
220120
- fs20.appendFileSync(this.file, JSON.stringify(stored) + "\n");
220601
+ fs21.appendFileSync(this.file, JSON.stringify(stored) + "\n");
220121
220602
  return seq;
220122
220603
  }
220123
220604
  read(artifactId, fromSeq) {
@@ -220133,7 +220614,7 @@ var init_dev_store = __esm({
220133
220614
  DirBlobStore = class {
220134
220615
  constructor(dir) {
220135
220616
  this.dir = dir;
220136
- fs20.mkdirSync(dir, { recursive: true });
220617
+ fs21.mkdirSync(dir, { recursive: true });
220137
220618
  }
220138
220619
  fileOf(hash3) {
220139
220620
  return path21.join(this.dir, hash3);
@@ -220141,31 +220622,31 @@ var init_dev_store = __esm({
220141
220622
  async put(bytes2) {
220142
220623
  const hash3 = (0, import_node_crypto46.createHash)("sha256").update(bytes2).digest("hex");
220143
220624
  const file = this.fileOf(hash3);
220144
- if (!fs20.existsSync(file)) fs20.writeFileSync(file, bytes2);
220625
+ if (!fs21.existsSync(file)) fs21.writeFileSync(file, bytes2);
220145
220626
  return hash3;
220146
220627
  }
220147
220628
  async meta(hash3) {
220148
220629
  const file = this.fileOf(hash3);
220149
- if (!fs20.existsSync(file)) return null;
220150
- const size = fs20.statSync(file).size;
220151
- const fd = fs20.openSync(file, "r");
220630
+ if (!fs21.existsSync(file)) return null;
220631
+ const size = fs21.statSync(file).size;
220632
+ const fd = fs21.openSync(file, "r");
220152
220633
  const head = Buffer.alloc(16);
220153
- fs20.readSync(fd, head, 0, 16, 0);
220154
- fs20.closeSync(fd);
220634
+ fs21.readSync(fd, head, 0, 16, 0);
220635
+ fs21.closeSync(fd);
220155
220636
  const contentType = sniffContentType(new Uint8Array(head));
220156
220637
  return { size, ...contentType ? { contentType } : {} };
220157
220638
  }
220158
220639
  async get(hash3) {
220159
220640
  const file = this.fileOf(hash3);
220160
- if (!fs20.existsSync(file)) throw new BlobNotFoundError(hash3);
220161
- return new Uint8Array(fs20.readFileSync(file));
220641
+ if (!fs21.existsSync(file)) throw new BlobNotFoundError(hash3);
220642
+ return new Uint8Array(fs21.readFileSync(file));
220162
220643
  }
220163
220644
  async has(hash3) {
220164
- return fs20.existsSync(this.fileOf(hash3));
220645
+ return fs21.existsSync(this.fileOf(hash3));
220165
220646
  }
220166
220647
  async sweep(reachable) {
220167
- for (const name of fs20.readdirSync(this.dir)) {
220168
- if (!reachable.has(name)) fs20.unlinkSync(this.fileOf(name));
220648
+ for (const name of fs21.readdirSync(this.dir)) {
220649
+ if (!reachable.has(name)) fs21.unlinkSync(this.fileOf(name));
220169
220650
  }
220170
220651
  }
220171
220652
  };
@@ -220194,7 +220675,7 @@ var init_dev_store = __esm({
220194
220675
  this.file = file;
220195
220676
  }
220196
220677
  static async open(file) {
220197
- const seed = fs20.existsSync(file) ? JSON.parse(fs20.readFileSync(file, "utf8")) : [];
220678
+ const seed = fs21.existsSync(file) ? JSON.parse(fs21.readFileSync(file, "utf8")) : [];
220198
220679
  return new _FileTypeRegistryStore(file, seed);
220199
220680
  }
220200
220681
  async put(def) {
@@ -220207,8 +220688,8 @@ var init_dev_store = __esm({
220207
220688
  }
220208
220689
  async save() {
220209
220690
  const tmp = `${this.file}.tmp`;
220210
- fs20.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
220211
- fs20.renameSync(tmp, this.file);
220691
+ fs21.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
220692
+ fs21.renameSync(tmp, this.file);
220212
220693
  }
220213
220694
  };
220214
220695
  FileRoleRegistryStore = class _FileRoleRegistryStore extends MemoryRoleRegistryStore {
@@ -220217,7 +220698,7 @@ var init_dev_store = __esm({
220217
220698
  this.file = file;
220218
220699
  }
220219
220700
  static async open(file) {
220220
- const seed = fs20.existsSync(file) ? JSON.parse(fs20.readFileSync(file, "utf8")) : [];
220701
+ const seed = fs21.existsSync(file) ? JSON.parse(fs21.readFileSync(file, "utf8")) : [];
220221
220702
  return new _FileRoleRegistryStore(file, seed);
220222
220703
  }
220223
220704
  async put(def) {
@@ -220230,8 +220711,8 @@ var init_dev_store = __esm({
220230
220711
  }
220231
220712
  async save() {
220232
220713
  const tmp = `${this.file}.tmp`;
220233
- fs20.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
220234
- fs20.renameSync(tmp, this.file);
220714
+ fs21.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
220715
+ fs21.renameSync(tmp, this.file);
220235
220716
  }
220236
220717
  };
220237
220718
  FileRegistryStore = class _FileRegistryStore extends MemoryRegistryStore {
@@ -220249,8 +220730,8 @@ var init_dev_store = __esm({
220249
220730
  }
220250
220731
  static async open(file) {
220251
220732
  const store = new _FileRegistryStore(file);
220252
- if (fs20.existsSync(file)) {
220253
- const snap = JSON.parse(fs20.readFileSync(file, "utf8"));
220733
+ if (fs21.existsSync(file)) {
220734
+ const snap = JSON.parse(fs21.readFileSync(file, "utf8"));
220254
220735
  for (const a of snap.actors) await MemoryRegistryStore.prototype.upsertActor.call(store, a);
220255
220736
  for (const c of snap.configs) await MemoryRegistryStore.prototype.appendConfig.call(store, c);
220256
220737
  for (const t of snap.teams) await MemoryRegistryStore.prototype.upsertTeam.call(store, t);
@@ -220291,7 +220772,7 @@ var init_dev_store = __esm({
220291
220772
  skillFiles: skillFilesEntries,
220292
220773
  connections
220293
220774
  };
220294
- fs20.writeFileSync(this.file, JSON.stringify(snap, null, 2));
220775
+ fs21.writeFileSync(this.file, JSON.stringify(snap, null, 2));
220295
220776
  }
220296
220777
  };
220297
220778
  FileAssistantBindStore = class _FileAssistantBindStore extends MemoryAssistantBindStore {
@@ -220309,11 +220790,11 @@ var init_dev_store = __esm({
220309
220790
  }
220310
220791
  static async open(file) {
220311
220792
  const store = new _FileAssistantBindStore(file);
220312
- if (fs20.existsSync(file)) store._load(JSON.parse(fs20.readFileSync(file, "utf8")));
220793
+ if (fs21.existsSync(file)) store._load(JSON.parse(fs21.readFileSync(file, "utf8")));
220313
220794
  return store;
220314
220795
  }
220315
220796
  async save() {
220316
- fs20.writeFileSync(this.file, JSON.stringify(this._dump(), null, 2));
220797
+ fs21.writeFileSync(this.file, JSON.stringify(this._dump(), null, 2));
220317
220798
  }
220318
220799
  };
220319
220800
  FileHumanPrefsStore = class _FileHumanPrefsStore extends MemoryHumanPrefsStore {
@@ -220331,11 +220812,11 @@ var init_dev_store = __esm({
220331
220812
  }
220332
220813
  static async open(file) {
220333
220814
  const store = new _FileHumanPrefsStore(file);
220334
- if (fs20.existsSync(file)) store._load(JSON.parse(fs20.readFileSync(file, "utf8")));
220815
+ if (fs21.existsSync(file)) store._load(JSON.parse(fs21.readFileSync(file, "utf8")));
220335
220816
  return store;
220336
220817
  }
220337
220818
  async save() {
220338
- fs20.writeFileSync(this.file, JSON.stringify(this._dump(), null, 2));
220819
+ fs21.writeFileSync(this.file, JSON.stringify(this._dump(), null, 2));
220339
220820
  }
220340
220821
  };
220341
220822
  FileProjectStateStore = class _FileProjectStateStore extends MemoryProjectStateStore {
@@ -220345,8 +220826,8 @@ var init_dev_store = __esm({
220345
220826
  }
220346
220827
  static async open(file) {
220347
220828
  const store = new _FileProjectStateStore(file);
220348
- if (fs20.existsSync(file)) {
220349
- const snap = JSON.parse(fs20.readFileSync(file, "utf8"));
220829
+ if (fs21.existsSync(file)) {
220830
+ const snap = JSON.parse(fs21.readFileSync(file, "utf8"));
220350
220831
  for (const project of snap.projects ?? []) await MemoryProjectStateStore.prototype.upsertProject.call(store, project);
220351
220832
  const membersByProject = /* @__PURE__ */ new Map();
220352
220833
  for (const member of snap.members ?? []) {
@@ -220358,8 +220839,8 @@ var init_dev_store = __esm({
220358
220839
  await MemoryProjectStateStore.prototype.replaceProjectMembers.call(store, projectId2, members);
220359
220840
  }
220360
220841
  } else {
220361
- fs20.mkdirSync(path21.dirname(file), { recursive: true });
220362
- fs20.writeFileSync(file, JSON.stringify({ projects: [], members: [] }, null, 2));
220842
+ fs21.mkdirSync(path21.dirname(file), { recursive: true });
220843
+ fs21.writeFileSync(file, JSON.stringify({ projects: [], members: [] }, null, 2));
220363
220844
  }
220364
220845
  return store;
220365
220846
  }
@@ -220378,7 +220859,7 @@ var init_dev_store = __esm({
220378
220859
  async save() {
220379
220860
  const projects = await this.listProjects();
220380
220861
  const members = (await Promise.all(projects.map((project) => this.listProjectMembers(project.id)))).flat();
220381
- fs20.writeFileSync(this.file, JSON.stringify({ projects, members }, null, 2));
220862
+ fs21.writeFileSync(this.file, JSON.stringify({ projects, members }, null, 2));
220382
220863
  }
220383
220864
  };
220384
220865
  FileArtifactStateStore = class _FileArtifactStateStore extends MemoryArtifactStateStore {
@@ -220388,8 +220869,8 @@ var init_dev_store = __esm({
220388
220869
  }
220389
220870
  static async open(file) {
220390
220871
  const store = new _FileArtifactStateStore(file);
220391
- if (fs20.existsSync(file)) {
220392
- const snap = JSON.parse(fs20.readFileSync(file, "utf8"));
220872
+ if (fs21.existsSync(file)) {
220873
+ const snap = JSON.parse(fs21.readFileSync(file, "utf8"));
220393
220874
  for (const artifact of snap.artifacts ?? []) await MemoryArtifactStateStore.prototype.upsertArtifact.call(store, artifact);
220394
220875
  for (const revision of snap.revisions ?? []) await MemoryArtifactStateStore.prototype.upsertRevision.call(store, revision);
220395
220876
  for (const event of snap.events ?? []) await MemoryArtifactStateStore.prototype.appendEvent.call(store, event);
@@ -220399,8 +220880,8 @@ var init_dev_store = __esm({
220399
220880
  for (const ws of snap.workspaceDispatchHeld ?? []) await MemoryArtifactStateStore.prototype.setWorkspaceDispatchHold.call(store, ws, true);
220400
220881
  for (const f2 of snap.workOrderFiles ?? []) await MemoryArtifactStateStore.prototype.addWorkOrderFile.call(store, f2);
220401
220882
  } else {
220402
- fs20.mkdirSync(path21.dirname(file), { recursive: true });
220403
- fs20.writeFileSync(file, JSON.stringify({ artifacts: [], revisions: [], events: [], annotations: [], nodeOutputs: [], workspaceBindings: [], workspaceDispatchHeld: [], workOrderFiles: [] }, null, 2));
220883
+ fs21.mkdirSync(path21.dirname(file), { recursive: true });
220884
+ fs21.writeFileSync(file, JSON.stringify({ artifacts: [], revisions: [], events: [], annotations: [], nodeOutputs: [], workspaceBindings: [], workspaceDispatchHeld: [], workOrderFiles: [] }, null, 2));
220404
220885
  }
220405
220886
  return store;
220406
220887
  }
@@ -220437,7 +220918,7 @@ var init_dev_store = __esm({
220437
220918
  await this.save();
220438
220919
  }
220439
220920
  async save() {
220440
- fs20.writeFileSync(this.file, JSON.stringify(await this.listAll(), null, 2));
220921
+ fs21.writeFileSync(this.file, JSON.stringify(await this.listAll(), null, 2));
220441
220922
  }
220442
220923
  };
220443
220924
  FileTraceStore = class _FileTraceStore extends MemoryTraceStore {
@@ -220464,25 +220945,25 @@ var init_dev_store = __esm({
220464
220945
  return path21.join(this.eventsDir, `${encodeURIComponent(runId)}.ndjson`);
220465
220946
  }
220466
220947
  appendLine(file, obj2) {
220467
- fs20.appendFileSync(file, JSON.stringify(obj2) + "\n");
220948
+ fs21.appendFileSync(file, JSON.stringify(obj2) + "\n");
220468
220949
  }
220469
220950
  static replay(file, fn) {
220470
- if (!fs20.existsSync(file)) return;
220471
- for (const line2 of fs20.readFileSync(file, "utf8").split("\n")) {
220951
+ if (!fs21.existsSync(file)) return;
220952
+ for (const line2 of fs21.readFileSync(file, "utf8").split("\n")) {
220472
220953
  if (!line2.trim()) continue;
220473
220954
  fn(JSON.parse(line2));
220474
220955
  }
220475
220956
  }
220476
220957
  static async open(dir) {
220477
220958
  const store = new _FileTraceStore(dir);
220478
- fs20.mkdirSync(store.eventsDir, { recursive: true });
220959
+ fs21.mkdirSync(store.eventsDir, { recursive: true });
220479
220960
  _FileTraceStore.replay(store.runsFile, (r) => store.restoreRun(r));
220480
- for (const name of fs20.readdirSync(store.eventsDir)) {
220961
+ for (const name of fs21.readdirSync(store.eventsDir)) {
220481
220962
  _FileTraceStore.replay(path21.join(store.eventsDir, name), (e) => store.restoreEvent(e));
220482
220963
  }
220483
220964
  _FileTraceStore.replay(store.toolCallsFile, (c) => store.restoreToolCall(c));
220484
220965
  _FileTraceStore.replay(store.linksFile, (l) => store.restoreLink(l));
220485
- store.restoreContinuity(fs20.existsSync(store.continuityFile) ? JSON.parse(fs20.readFileSync(store.continuityFile, "utf8")) : {});
220966
+ store.restoreContinuity(fs21.existsSync(store.continuityFile) ? JSON.parse(fs21.readFileSync(store.continuityFile, "utf8")) : {});
220486
220967
  return store;
220487
220968
  }
220488
220969
  async createRun(input) {
@@ -220534,8 +221015,8 @@ var init_dev_store = __esm({
220534
221015
  }
220535
221016
  saveContinuity() {
220536
221017
  const tmp = `${this.continuityFile}.tmp`;
220537
- fs20.writeFileSync(tmp, JSON.stringify(this.continuitySnapshot(), null, 2));
220538
- fs20.renameSync(tmp, this.continuityFile);
221018
+ fs21.writeFileSync(tmp, JSON.stringify(this.continuitySnapshot(), null, 2));
221019
+ fs21.renameSync(tmp, this.continuityFile);
220539
221020
  }
220540
221021
  async appendEvents(runId, inputs) {
220541
221022
  const existing = await super.listEvents(runId, {});
@@ -220996,8 +221477,8 @@ var init_dev_store = __esm({
220996
221477
  }
220997
221478
  static async open(file) {
220998
221479
  const store = new _FileChatSessionStore(file);
220999
- if (fs20.existsSync(file)) {
221000
- const snap = JSON.parse(fs20.readFileSync(file, "utf8"));
221480
+ if (fs21.existsSync(file)) {
221481
+ const snap = JSON.parse(fs21.readFileSync(file, "utf8"));
221001
221482
  for (const [sid, copies] of Object.entries(snap.outputCopies ?? {})) store.outputCopies.set(sid, copies);
221002
221483
  for (const [sid, fps] of Object.entries(snap.outputFingerprints ?? {})) store.outputFingerprints.set(sid, fps);
221003
221484
  for (const [sid, issues] of Object.entries(snap.fileIssues ?? {})) store.fileIssues.set(sid, issues);
@@ -221007,8 +221488,8 @@ var init_dev_store = __esm({
221007
221488
  for (const [sid, kinds] of Object.entries(snap.sessionWorkOrderKinds ?? {})) store.sessionWorkOrderKinds.set(sid, new Map(Object.entries(kinds)));
221008
221489
  for (const [sid, list2] of Object.entries(snap.pending ?? {})) store.pending.set(sid, list2);
221009
221490
  } else {
221010
- fs20.mkdirSync(path21.dirname(file), { recursive: true });
221011
- fs20.writeFileSync(file, JSON.stringify({ sessions: [], messages: {}, sessionWorkOrders: {} }, null, 2));
221491
+ fs21.mkdirSync(path21.dirname(file), { recursive: true });
221492
+ fs21.writeFileSync(file, JSON.stringify({ sessions: [], messages: {}, sessionWorkOrders: {} }, null, 2));
221012
221493
  }
221013
221494
  return store;
221014
221495
  }
@@ -221102,7 +221583,7 @@ var init_dev_store = __esm({
221102
221583
  sessionWorkOrderKinds: Object.fromEntries([...this.sessionWorkOrderKinds].map(([k2, v2]) => [k2, Object.fromEntries(v2)])),
221103
221584
  pending: Object.fromEntries(this.pending)
221104
221585
  };
221105
- fs20.writeFileSync(this.file, JSON.stringify(snap, null, 2));
221586
+ fs21.writeFileSync(this.file, JSON.stringify(snap, null, 2));
221106
221587
  }
221107
221588
  };
221108
221589
  MemoryChatTurnStore = class {
@@ -221185,17 +221666,17 @@ var init_dev_store = __esm({
221185
221666
  }
221186
221667
  static async open(file) {
221187
221668
  const store = new _FileChatTurnStore(file);
221188
- if (fs20.existsSync(file)) {
221189
- const snap = JSON.parse(fs20.readFileSync(file, "utf8"));
221669
+ if (fs21.existsSync(file)) {
221670
+ const snap = JSON.parse(fs21.readFileSync(file, "utf8"));
221190
221671
  for (const t of snap.turns ?? []) store.turns.set(t.id, t);
221191
221672
  } else {
221192
- fs20.mkdirSync(path21.dirname(file), { recursive: true });
221193
- fs20.writeFileSync(file, JSON.stringify({ turns: [] }, null, 2));
221673
+ fs21.mkdirSync(path21.dirname(file), { recursive: true });
221674
+ fs21.writeFileSync(file, JSON.stringify({ turns: [] }, null, 2));
221194
221675
  }
221195
221676
  return store;
221196
221677
  }
221197
221678
  persist() {
221198
- fs20.writeFileSync(this.file, JSON.stringify({ turns: [...this.turns.values()] }, null, 2));
221679
+ fs21.writeFileSync(this.file, JSON.stringify({ turns: [...this.turns.values()] }, null, 2));
221199
221680
  }
221200
221681
  };
221201
221682
  MemoryReadMarkerStore = class _MemoryReadMarkerStore {
@@ -221221,12 +221702,12 @@ var init_dev_store = __esm({
221221
221702
  }
221222
221703
  static async open(file) {
221223
221704
  const store = new _FileReadMarkerStore(file);
221224
- if (fs20.existsSync(file)) {
221225
- const snap = JSON.parse(fs20.readFileSync(file, "utf8"));
221705
+ if (fs21.existsSync(file)) {
221706
+ const snap = JSON.parse(fs21.readFileSync(file, "utf8"));
221226
221707
  for (const m2 of snap.markers ?? []) store.markers.set(`${m2.actorId}\0${m2.scope}`, m2);
221227
221708
  } else {
221228
- fs20.mkdirSync(path21.dirname(file), { recursive: true });
221229
- fs20.writeFileSync(file, JSON.stringify({ markers: [] }, null, 2));
221709
+ fs21.mkdirSync(path21.dirname(file), { recursive: true });
221710
+ fs21.writeFileSync(file, JSON.stringify({ markers: [] }, null, 2));
221230
221711
  }
221231
221712
  return store;
221232
221713
  }
@@ -221235,18 +221716,18 @@ var init_dev_store = __esm({
221235
221716
  this.save();
221236
221717
  }
221237
221718
  save() {
221238
- fs20.writeFileSync(this.file, JSON.stringify({ markers: [...this.markers.values()] }, null, 2));
221719
+ fs21.writeFileSync(this.file, JSON.stringify({ markers: [...this.markers.values()] }, null, 2));
221239
221720
  }
221240
221721
  };
221241
221722
  }
221242
221723
  });
221243
221724
 
221244
221725
  // ../server/src/control-plane-file-store.ts
221245
- var fs21, path22, MUTATORS2, SNAPSHOT_VERSION, FileControlPlaneStore;
221726
+ var fs22, path22, MUTATORS2, SNAPSHOT_VERSION, FileControlPlaneStore;
221246
221727
  var init_control_plane_file_store = __esm({
221247
221728
  "../server/src/control-plane-file-store.ts"() {
221248
221729
  "use strict";
221249
- fs21 = __toESM(require("node:fs"), 1);
221730
+ fs22 = __toESM(require("node:fs"), 1);
221250
221731
  path22 = __toESM(require("node:path"), 1);
221251
221732
  init_src9();
221252
221733
  MUTATORS2 = [
@@ -221289,8 +221770,8 @@ var init_control_plane_file_store = __esm({
221289
221770
  }
221290
221771
  static async open(file) {
221291
221772
  const store = new _FileControlPlaneStore(file);
221292
- if (fs21.existsSync(file)) {
221293
- const snap = JSON.parse(fs21.readFileSync(file, "utf8"));
221773
+ if (fs22.existsSync(file)) {
221774
+ const snap = JSON.parse(fs22.readFileSync(file, "utf8"));
221294
221775
  const needsOnboardingBackfill = (snap.version ?? 1) < 2;
221295
221776
  const backfillAt = (/* @__PURE__ */ new Date()).toISOString();
221296
221777
  for (const c of snap.companies ?? []) await MemoryControlPlaneStore.prototype.createCompany.call(store, c);
@@ -221308,7 +221789,7 @@ var init_control_plane_file_store = __esm({
221308
221789
  for (const op of snap.offboardings ?? []) store.offboardings.set(`${op.companyId}\0${op.operationId}`, op);
221309
221790
  if (needsOnboardingBackfill) await store.save();
221310
221791
  } else {
221311
- fs21.mkdirSync(path22.dirname(file), { recursive: true });
221792
+ fs22.mkdirSync(path22.dirname(file), { recursive: true });
221312
221793
  }
221313
221794
  return store;
221314
221795
  }
@@ -221345,7 +221826,7 @@ var init_control_plane_file_store = __esm({
221345
221826
  // 交接操作无按公司列举的读面(按 operationId / accountId 定位),直接读 protected map。
221346
221827
  offboardings: [...this.offboardings.values()]
221347
221828
  };
221348
- fs21.writeFileSync(this.file, JSON.stringify(snap, null, 2));
221829
+ fs22.writeFileSync(this.file, JSON.stringify(snap, null, 2));
221349
221830
  }
221350
221831
  };
221351
221832
  }
@@ -223517,8 +223998,12 @@ var init_dispatch_binding_diagnosis = __esm({
223517
223998
  });
223518
223999
 
223519
224000
  // ../server/src/zero-output-breaker.ts
224001
+ function roundKeyOfZeroOutputRun(run) {
224002
+ return run.workId ?? run.id.replace(PROTOCOL_RECOVERY_SUFFIX, "");
224003
+ }
223520
224004
  function absorb(b2, r, startedMs) {
223521
224005
  b2.count += 1;
224006
+ b2.rounds.add(roundKeyOfZeroOutputRun(r));
223522
224007
  b2.costUsdMicros += r.costUsdMicros ?? 0;
223523
224008
  if (startedMs >= b2.latestStartedMs) {
223524
224009
  b2.latestStartedMs = startedMs;
@@ -223586,6 +224071,7 @@ async function scanZeroOutputBreaker(deps) {
223586
224071
  actorId: axis === "count" ? b2.actorId : null,
223587
224072
  cls: axis === "count" ? b2.cls : null,
223588
224073
  count: b2.count,
224074
+ workCount: b2.rounds.size,
223589
224075
  costUsdMicros: b2.costUsdMicros,
223590
224076
  windowMs: deps.windowMs,
223591
224077
  latestRunId: b2.latestRunId,
@@ -223595,7 +224081,7 @@ async function scanZeroOutputBreaker(deps) {
223595
224081
  const who = axis === "count" ? `${b2.actorId}\uFF08${b2.cls}\uFF09` : "\u8BE5\u8282\u70B9\u5168\u90E8\u6267\u884C\u4EBA\u5408\u8BA1";
223596
224082
  const cidTag = deps.defaultCompanyId && b2.companyId !== deps.defaultCompanyId ? ` [${b2.companyId}]` : "";
223597
224083
  deps.log?.(
223598
- `[zero-output-breaker] ${b2.artifactId}${cidTag}\uFF1A\u6700\u8FD1 ${Math.round(deps.windowMs / 6e4)} \u5206\u949F\u5185 ${who} \u7D2F\u8BA1 ${b2.count} \u6B21\u96F6\u4EA7\u51FA\uFF08\u7EA6 $${(b2.costUsdMicros / 1e6).toFixed(1)}\uFF09\u2192 ${axis} \u8F74\u8DF3\u95F8`
224084
+ `[zero-output-breaker] ${b2.artifactId}${cidTag}\uFF1A\u6700\u8FD1 ${Math.round(deps.windowMs / 6e4)} \u5206\u949F\u5185 ${who} \u7D2F\u8BA1 ${b2.count} \u6B21\u96F6\u4EA7\u51FA\uFF08${b2.rounds.size} \u8F6E\uFF0C\u7EA6 $${(b2.costUsdMicros / 1e6).toFixed(1)}\uFF09\u2192 ${axis} \u8F74\u8DF3\u95F8`
223599
224085
  );
223600
224086
  };
223601
224087
  const byKey = (a, b2) => a.artifactId.localeCompare(b2.artifactId) || (a.actorId ?? "").localeCompare(b2.actorId ?? "");
@@ -223621,32 +224107,35 @@ async function scanZeroOutputBreaker(deps) {
223621
224107
  function formatZeroOutputNotice(t) {
223622
224108
  const usd2 = (t.costUsdMicros / 1e6).toFixed(1);
223623
224109
  const mins = Math.round(t.windowMs / 6e4);
224110
+ const runs = t.count > t.workCount ? `\u5171 ${t.count} \u6B21\u8FD0\u884C\uFF0C\u542B\u540C\u4E00\u8F6E\u5185\u7684\u81EA\u52A8\u7EED\u8DD1\uFF1B` : "";
223624
224111
  const remark = t.closingRemark && t.closingRemark.trim() ? `
223625
224112
 
223626
224113
  \u6700\u8FD1\u4E00\u6B21\u8FD0\u884C\u7684\u6536\u5C3E\u53D1\u8A00\uFF08\u5F80\u5F80\u76F4\u63A5\u5199\u7740\u5361\u5728\u54EA\uFF09\uFF1A
223627
224114
  ${t.closingRemark.trim()}` : "";
223628
224115
  const tail = `\u8FD9\u4E0D\u662F\u5728\u5224\u67D0\u4E00\u6B21\u8FD0\u884C\u5931\u8D25\u2014\u2014\u662F\u5728\u8FD9\u6BB5\u65F6\u95F4\u91CC\u53CD\u590D\u96F6\u4EA7\u51FA\u3002\u65E0\u8BBA\u6BCF\u4E00\u6B21\u9000\u51FA\u591A\u4E48\u300C\u6B63\u5E38\u300D\uFF0C\u53CD\u590D\u5230\u8FD9\u4E2A\u6B21\u6570\u901A\u5E38\u610F\u5473\u7740\u5361\u5728\u4E00\u4E2A\u53CD\u590D\u91CD\u8BD5\u4E5F\u8FC7\u4E0D\u53BB\u7684\u574E\u4E0A\uFF08\u7F3A\u51ED\u636E\u3001\u4E0A\u6E38\u670D\u52A1\u8FDE\u4E0D\u4E0A\u3001\u6216\u649E\u4E0A\u4E86\u522B\u7684\u6B7B\u5FAA\u73AF\uFF09\u3002\u8BF7\u770B\u4E00\u773C\u6839\u56E0\uFF1B\u5904\u7406\u5B8C\u7528 \`oasis release\` \u6062\u590D\u3002` + remark;
223629
224116
  if (t.axis === "cost") {
223630
- return `\u8FD9\u4E2A\u4EA7\u7269\u5728\u6700\u8FD1 ${mins} \u5206\u949F\u5185\u7D2F\u8BA1\u6709 ${t.count} \u6B21\u8FD0\u884C**\u4EC0\u4E48\u90FD\u6CA1\u4EA4\u51FA\u6765**\uFF0C\u8FD9\u4E9B\u7A7A\u8DD1\u5408\u8BA1\u5DF2\u7ECF\u82B1\u6389\u7EA6 $${usd2}\uFF0C\u5DF2\u81EA\u52A8\u6682\u505C\u6D3E\u53D1\u4EE5\u6B62\u635F\u3002
224117
+ return `\u8FD9\u4E2A\u4EA7\u7269\u5728\u6700\u8FD1 ${mins} \u5206\u949F\u5185\u6709 ${t.workCount} \u8F6E**\u4EC0\u4E48\u90FD\u6CA1\u4EA4\u51FA\u6765**\uFF08${runs}\u8FD9\u4E9B\u7A7A\u8DD1\u5408\u8BA1\u5DF2\u7ECF\u82B1\u6389\u7EA6 $${usd2}\uFF09\uFF0C\u5DF2\u81EA\u52A8\u6682\u505C\u6D3E\u53D1\u4EE5\u6B62\u635F\u3002
223631
224118
 
223632
224119
  ` + tail;
223633
224120
  }
223634
224121
  const who = t.cls === "review" ? `\u8BC4\u5BA1\u5458 ${t.actorId}` : `\u6267\u884C\u4EBA ${t.actorId}`;
223635
224122
  const what = t.cls === "review" ? `\u5DF2\u505C\u6389\u8FD9\u540D\u8BC4\u5BA1\u5458\u5728\u672C\u8282\u70B9\u4E0A\u7684\u8BC4\u5BA1\uFF08**\u4EA4\u4ED8\u5185\u5BB9\u539F\u6837\u4FDD\u7559**\uFF0C\u6062\u590D\u540E\u53EF\u7EE7\u7EED\u6295\u7968\uFF09` : `\u5DF2\u81EA\u52A8\u6682\u505C\u8BE5\u4EA7\u7269\u7684\u6D3E\u53D1\u4EE5\u6B62\u635F`;
223636
- return `${who} \u5728\u6700\u8FD1 ${mins} \u5206\u949F\u5185\u4E3A\u8FD9\u4E2A\u4EA7\u7269\u8DD1\u4E86 ${t.count} \u6B21\uFF0C**\u6BCF\u6B21\u90FD\u4EC0\u4E48\u90FD\u6CA1\u4EA4\u51FA\u6765**\uFF08\u8FD9\u4E9B\u7A7A\u8DD1\u7D2F\u8BA1\u82B1\u8D39\u7EA6 $${usd2}\uFF09\uFF0C${what}\u3002
224123
+ return `${who} \u5728\u6700\u8FD1 ${mins} \u5206\u949F\u5185\u4E3A\u8FD9\u4E2A\u4EA7\u7269\u8DD1\u4E86 ${t.workCount} \u8F6E\uFF0C**\u6BCF\u4E00\u8F6E\u90FD\u4EC0\u4E48\u90FD\u6CA1\u4EA4\u51FA\u6765**\uFF08${runs}\u8FD9\u4E9B\u7A7A\u8DD1\u7D2F\u8BA1\u82B1\u8D39\u7EA6 $${usd2}\uFF09\uFF0C${what}\u3002
223637
224124
 
223638
224125
  ` + tail;
223639
224126
  }
223640
- var emptyBucket;
224127
+ var PROTOCOL_RECOVERY_SUFFIX, emptyBucket;
223641
224128
  var init_zero_output_breaker = __esm({
223642
224129
  "../server/src/zero-output-breaker.ts"() {
223643
224130
  "use strict";
224131
+ PROTOCOL_RECOVERY_SUFFIX = /(?::protocol-recovery)+$/;
223644
224132
  emptyBucket = (artifactId, companyId, actorId, cls) => ({
223645
224133
  artifactId,
223646
224134
  companyId,
223647
224135
  actorId,
223648
224136
  cls,
223649
224137
  count: 0,
224138
+ rounds: /* @__PURE__ */ new Set(),
223650
224139
  costUsdMicros: 0,
223651
224140
  latestRunId: "",
223652
224141
  latestStartedMs: -Infinity
@@ -224046,6 +224535,11 @@ function chatRunLedgerFields(req) {
224046
224535
  if (req.executionProfile === "discussion-background") return { chatSessionId: null, chatTurnId: null };
224047
224536
  return { chatSessionId: req.chatSessionId ?? null, chatTurnId: req.chatTurnId ?? null };
224048
224537
  }
224538
+ function chatExitModelFields(runtimeReportedModel, dispatchedModel, dispatchedModelSource) {
224539
+ const effectiveModel = runtimeReportedModel || dispatchedModel || null;
224540
+ if (!effectiveModel) return {};
224541
+ return { effectiveModel, modelSource: runtimeReportedModel ? "runtime" : dispatchedModelSource };
224542
+ }
224049
224543
  function createUsageLedgerResolver(deps) {
224050
224544
  const safeCompanyOfRun = (runId) => deps.companyOfRun(runId).catch(() => null);
224051
224545
  return async (dispatchId) => {
@@ -224061,6 +224555,15 @@ function createUsageLedgerResolver(deps) {
224061
224555
  return chatCid ? { runId: stable, store: await deps.storeFor(chatCid) } : null;
224062
224556
  };
224063
224557
  }
224558
+ async function backfillRunModel(target, run, model, warn) {
224559
+ if (!model || model.trim().length === 0) return;
224560
+ if (run.effectiveModel && run.effectiveModel.trim().length > 0) return;
224561
+ try {
224562
+ await target.store.updateRun(target.runId, { effectiveModel: model, modelSource: "runtime" });
224563
+ } catch (err) {
224564
+ warn(`[usage-ledger] run ${target.runId} \u7684\u6A21\u578B\u540D\u8865\u5199\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u4F1A\u8BDD\uFF09\uFF1A${String(err)}`);
224565
+ }
224566
+ }
224064
224567
  function createUsageLedgerFrameHandler(deps) {
224065
224568
  const warn = deps.warn ?? defaultWarn;
224066
224569
  const marker = deps.marker ?? new RuntimeStartMarker({ warn });
@@ -224122,6 +224625,7 @@ function createUsageLedgerFrameHandler(deps) {
224122
224625
  usage: msg.usage,
224123
224626
  model: msg.model ?? null
224124
224627
  });
224628
+ await backfillRunModel(target, run, msg.model, warn);
224125
224629
  } catch (err) {
224126
224630
  warn(`[usage-ledger] \u5904\u7406 ${msg.type} dispatch=${dispatchId} \u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u4F1A\u8BDD\uFF09\uFF1A${String(err)}`);
224127
224631
  }
@@ -225124,18 +225628,27 @@ function wireRecoveredChatTurn(deps) {
225124
225628
  log3(`[chat-recovery] \u4F1A\u8BDD ${plan.chatSessionId} \u91CD\u6302\uFF1A\u8865\u53D1\u6709 ${droppedFrames} \u5E27\u6CA1\u63A5\u4F4F\uFF08run ${plan.runId}\uFF09\uFF0C\u672C\u8F6E\u6309\u300C\u8F93\u51FA\u4E0D\u5B8C\u6574\u300D\u6536\u5C3E${exitIsFailure ? "\uFF08\u9000\u51FA\u5E27\u672C\u8EAB\u4E5F\u662F\u5931\u8D25\uFF09" : "\uFF0C\u4E0D\u8BB0\u6210\u6210\u529F"}`);
225125
225629
  }
225126
225630
  if (failed) {
225127
- const exitAttribution = exitIsFailure ? attributeChatExit(exitErrorText(info), {
225128
- ...info.reason ? { reason: info.reason } : {},
225129
- ...cancelCauseFromExitMessage(info.errorMessage) ? { cancelledBy: cancelCauseFromExitMessage(info.errorMessage) } : {}
225631
+ const exitSummary = exitIsFailure ? chatExitSummary(info) : void 0;
225632
+ const exitAttribution = exitSummary ? attributeChatExit(exitSummary.summary, {
225633
+ ...exitSummary.reason ? { reason: exitSummary.reason } : {},
225634
+ ...exitSummary.detail ? { detail: exitSummary.detail } : {},
225635
+ ...exitSummary.cancelledBy ? { cancelledBy: exitSummary.cancelledBy } : {}
225130
225636
  }, live.cancelCause()) : void 0;
225131
225637
  const errText2 = [
225132
225638
  exitAttribution?.text,
225133
225639
  droppedFrames > 0 ? outputGapNotice(droppedFrames) : void 0
225134
225640
  ].filter((t) => Boolean(t)).join("\n\n");
225135
- live.emit({ type: "error", text: errText2, ...exitAttribution?.extra.cancelledBy ? { cancelledBy: exitAttribution.extra.cancelledBy } : {} });
225641
+ live.emit({
225642
+ type: "error",
225643
+ text: errText2,
225644
+ ...exitAttribution?.extra.detail ? { detail: exitAttribution.extra.detail } : {},
225645
+ ...exitAttribution?.extra.cancelledBy ? { cancelledBy: exitAttribution.extra.cancelledBy } : {}
225646
+ });
225136
225647
  const errReason = exitIsFailure ? info.reason : OUTPUT_GAP_ERROR_CODE;
225137
225648
  const errExtra = {
225138
225649
  ...errReason ? { reason: errReason } : {},
225650
+ /* 运行时原文只进 detail:刷新后读的是这一行,「展开详情」要能翻出退出码与原始报错。 */
225651
+ ...exitAttribution?.extra.detail ? { detail: exitAttribution.extra.detail } : {},
225139
225652
  ...exitAttribution?.extra.cancelledBy ? { cancelledBy: exitAttribution.extra.cancelledBy } : {}
225140
225653
  };
225141
225654
  const errorOutcome = itemLedger.recordError(errText2, errExtra);
@@ -225176,7 +225689,7 @@ function wireRecoveredChatTurn(deps) {
225176
225689
  touchedAt: finishedAt
225177
225690
  }).catch(() => void 0);
225178
225691
  if (deps.onTurnSettled) {
225179
- await deps.onTurnSettled(failed ? "failed" : "succeeded", finishedAt).catch((err) => {
225692
+ await deps.onTurnSettled(failed ? "failed" : "succeeded", finishedAt, info.reason ?? void 0).catch((err) => {
225180
225693
  log3(`[chat-recovery] \u4F1A\u8BDD ${plan.chatSessionId} \u6536\u53E3\u8BB0\u8D26\u5931\u8D25\uFF08\u9000\u56DE\u6E05\u626B\u62CD\u515C\u5E95\uFF09: ${String(err)}`);
225181
225694
  });
225182
225695
  }
@@ -225373,13 +225886,26 @@ async function reconcileChatExitFrame(deps) {
225373
225886
  if (part) parts.push({ ...part, seq: ++seq });
225374
225887
  }
225375
225888
  if (failed) {
225889
+ const exitSummary = exitIsFailure ? chatExitSummary(info) : void 0;
225376
225890
  const errText2 = [
225377
- exitIsFailure ? exitErrorText(info) : void 0,
225891
+ exitSummary?.summary,
225378
225892
  droppedFrames > 0 ? outputGapNotice(droppedFrames) : void 0
225379
225893
  ].filter((t) => Boolean(t)).join("\n\n");
225380
225894
  const errReason = exitIsFailure ? info.reason : OUTPUT_GAP_ERROR_CODE;
225381
- itemLedger.recordError(errText2, { ...errReason ? { reason: errReason } : {} });
225382
- parts.push({ type: "error", seq: ++seq, text: errText2 });
225895
+ const errExtra = {
225896
+ ...errReason ? { reason: errReason } : {},
225897
+ ...exitSummary?.detail ? { detail: exitSummary.detail } : {},
225898
+ ...exitSummary?.cancelledBy ? { cancelledBy: exitSummary.cancelledBy } : {}
225899
+ };
225900
+ itemLedger.recordError(errText2, errExtra);
225901
+ parts.push({
225902
+ type: "error",
225903
+ seq: ++seq,
225904
+ text: errText2,
225905
+ ...exitIsFailure && info.reason ? { reason: info.reason } : {},
225906
+ ...exitSummary?.detail ? { detail: exitSummary.detail } : {},
225907
+ ...exitSummary?.cancelledBy ? { cancelledBy: exitSummary.cancelledBy } : {}
225908
+ });
225383
225909
  }
225384
225910
  normalizer.turnFinished(failed ? "failed" : "completed");
225385
225911
  itemLedger.finish(failed ? "failed" : "completed");
@@ -225440,7 +225966,8 @@ async function reconcileChatExitFrame(deps) {
225440
225966
  chatSessionId: row.sessionId,
225441
225967
  turnId: turn.id,
225442
225968
  status,
225443
- reason: `\u505C\u673A\u7A97\u53E3\u5185\u7ED3\u675F\u7684\u8F6E\u4EE5 ${status} \u6536\u5C3E`
225969
+ reason: `\u505C\u673A\u7A97\u53E3\u5185\u7ED3\u675F\u7684\u8F6E\u4EE5 ${status} \u6536\u5C3E`,
225970
+ ...info.reason ? { exitReason: info.reason } : {}
225444
225971
  });
225445
225972
  } catch (err) {
225446
225973
  deps.log?.(`[chat-recovery] \u8F6E\u6B21 ${turn.id} \u6536\u53E3\u540E\u7684\u8BB0\u8D26/\u5916\u62A5\u5931\u8D25\uFF08\u884C\u5DF2\u6536\u5C3E\uFF09: ${String(err)}`);
@@ -225596,7 +226123,7 @@ async function recoverChatTurnsAcrossCompanies(deps) {
225596
226123
  }
225597
226124
  const handle = deps.recover(plan);
225598
226125
  const settleTurnId = turn?.id;
225599
- const onTurnSettled = settleTurnId && stores.settleTurn ? async (status, completedAt) => {
226126
+ const onTurnSettled = settleTurnId && stores.settleTurn ? async (status, completedAt, exitReason) => {
225600
226127
  await stores.settleTurn(settleTurnId, {
225601
226128
  status,
225602
226129
  completedAt,
@@ -225608,6 +226135,7 @@ async function recoverChatTurnsAcrossCompanies(deps) {
225608
226135
  turnId: settleTurnId,
225609
226136
  status,
225610
226137
  reason: `\u91CD\u6302\u7684\u8F6E\u4EE5 ${status} \u6536\u5C3E`,
226138
+ ...exitReason ? { exitReason } : {},
225611
226139
  ...companyId ? { companyId } : {}
225612
226140
  });
225613
226141
  } : void 0;
@@ -225653,6 +226181,7 @@ var init_chat_recovery = __esm({
225653
226181
  init_chat_assistant_turn();
225654
226182
  init_src2();
225655
226183
  init_session_exit_outcome();
226184
+ init_chat_exit();
225656
226185
  init_chat_cancel_attribution();
225657
226186
  asObj3 = (v2) => v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : void 0;
225658
226187
  partsOf = (parts) => Array.isArray(parts) ? parts.filter((p2) => p2 && typeof p2 === "object" && typeof p2.type === "string") : [];
@@ -225902,6 +226431,8 @@ var init_daemon_hub = __esm({
225902
226431
  sessionMeta = /* @__PURE__ */ new Map();
225903
226432
  sessionConnectListeners = [];
225904
226433
  sessionDisconnectListeners = [];
226434
+ /** Bug 0224:等某条会话连上数据面的一次性等待者(dispatchId → 兑现函数)。见 {@link waitForSession}。 */
226435
+ sessionReadyWaiters = /* @__PURE__ */ new Map();
225905
226436
  // ── ADR 0300 D2「日志复制」:帧号 + 累积 ackSeq + 幂等摄入 ────────────────────────────
225906
226437
  // 只对自报 supportsFrameSeq 的会话生效;老会话这三张表始终为空、逐字走原 outbox 语义。
225907
226438
  /** 自报支持帧号的 dispatchId。 */
@@ -226165,6 +226696,11 @@ var init_daemon_hub = __esm({
226165
226696
  ...msg.jobArtifactId ? { jobArtifactId: msg.jobArtifactId } : {}
226166
226697
  });
226167
226698
  for (const l of this.sessionConnectListeners) l({ dispatchId, sessionId: msg.sessionId, nodeId });
226699
+ const readyWaiters = this.sessionReadyWaiters.get(dispatchId);
226700
+ if (readyWaiters) {
226701
+ this.sessionReadyWaiters.delete(dispatchId);
226702
+ for (const w2 of readyWaiters) w2(true);
226703
+ }
226168
226704
  void this.judgeSession(dispatchId, ws);
226169
226705
  }
226170
226706
  if (msg.supportsFrameSeq) {
@@ -226336,6 +226872,35 @@ var init_daemon_hub = __esm({
226336
226872
  addSessionDisconnectListener(l) {
226337
226873
  this.sessionDisconnectListeners.push(l);
226338
226874
  }
226875
+ /**
226876
+ * Bug 0224:等这次派发的会话进程连上数据面(`session_hello` 登记完),最多等 `timeoutMs`。
226877
+ * 已连着立即回 true;等到了回 true;超时回 false。
226878
+ *
226879
+ * 为什么要等:每一轮开始时节点另起一个会话进程,它自己连回来、在 hello 里报「能回报插话读到的位置」。
226880
+ * 连上之前 {@link supportsInputConsumptionReceipts} 只能答 false——那不是「不支持」,是「还没到」。
226881
+ */
226882
+ waitForSession(dispatchId, timeoutMs) {
226883
+ if (this.hasSession(dispatchId)) return Promise.resolve(true);
226884
+ return new Promise((resolve11) => {
226885
+ let waiters = this.sessionReadyWaiters.get(dispatchId);
226886
+ if (!waiters) {
226887
+ waiters = /* @__PURE__ */ new Set();
226888
+ this.sessionReadyWaiters.set(dispatchId, waiters);
226889
+ }
226890
+ const done = (connected) => {
226891
+ clearTimeout(timer);
226892
+ resolve11(connected);
226893
+ };
226894
+ const timer = setTimeout(() => {
226895
+ const set = this.sessionReadyWaiters.get(dispatchId);
226896
+ set?.delete(done);
226897
+ if (set && set.size === 0) this.sessionReadyWaiters.delete(dispatchId);
226898
+ resolve11(false);
226899
+ }, timeoutMs);
226900
+ timer.unref?.();
226901
+ waiters.add(done);
226902
+ });
226903
+ }
226339
226904
  /** 这次派发有没有一根活着的数据面连接(有 = 会话自己在说话,不必再问节点)。 */
226340
226905
  hasSession(dispatchId) {
226341
226906
  return this.sessionSockets.get(dispatchId)?.readyState === import_websocket.default.OPEN;
@@ -231312,7 +231877,7 @@ function createRegistryAuditSink(file) {
231312
231877
  return {
231313
231878
  /** 登记审计:落盘 + 推 registry 通知(前端据此重拉登记类投影)。 */
231314
231879
  registryAudit: (record9) => {
231315
- fs22.appendFile(file, JSON.stringify(record9) + "\n", () => {
231880
+ fs23.appendFile(file, JSON.stringify(record9) + "\n", () => {
231316
231881
  });
231317
231882
  notify({ kind: record9.kind, actor: record9.actor, target: record9.target, timestamp: record9.timestamp });
231318
231883
  },
@@ -231326,7 +231891,7 @@ function createRegistryAuditSink(file) {
231326
231891
  * 得先让通知带组织、`/api/events` 按组织过滤(recvvzbhOGiS7l 的完整修复),再放回来。
231327
231892
  */
231328
231893
  credentialAudit: (record9) => {
231329
- fs22.appendFile(file, JSON.stringify(record9) + "\n", () => {
231894
+ fs23.appendFile(file, JSON.stringify(record9) + "\n", () => {
231330
231895
  });
231331
231896
  },
231332
231897
  /** runtime 状态变了:只发无内容的失效提示(ADR 0576)。 */
@@ -231343,11 +231908,11 @@ function createRegistryAuditSink(file) {
231343
231908
  }
231344
231909
  };
231345
231910
  }
231346
- var fs22;
231911
+ var fs23;
231347
231912
  var init_registry_audit_sink = __esm({
231348
231913
  "../server/src/domains/actors/registry-audit-sink.ts"() {
231349
231914
  "use strict";
231350
- fs22 = __toESM(require("node:fs"), 1);
231915
+ fs23 = __toESM(require("node:fs"), 1);
231351
231916
  }
231352
231917
  });
231353
231918
 
@@ -233277,6 +233842,15 @@ var init_review_requirement_activity = __esm({
233277
233842
  });
233278
233843
 
233279
233844
  // ../server/src/domains/collab/activity.ts
233845
+ function coordinatorCardGapId(cardId) {
233846
+ return cardId.startsWith(COORDINATOR_CARD_PREFIX) ? cardId.slice(COORDINATOR_CARD_PREFIX.length) : void 0;
233847
+ }
233848
+ function coordinatorRunOfGap(gap, runs) {
233849
+ const opensAt = gap.createdAt;
233850
+ const closesAt = gap.resolvedAt ?? null;
233851
+ const overlapping = runs.filter((r) => (!closesAt || r.startedAt <= closesAt) && (!r.endedAt || r.endedAt >= opensAt)).sort((a, b2) => a.startedAt.localeCompare(b2.startedAt) || a.id.localeCompare(b2.id));
233852
+ return overlapping.find((r) => r.startedAt >= opensAt) ?? overlapping.at(-1);
233853
+ }
233280
233854
  function escalationKindOf(issueId, authorActorId) {
233281
233855
  if (issueId.startsWith(RETRY_EXHAUSTED_ESC_PREFIX)) return "retry_exhausted";
233282
233856
  if (issueId.startsWith(REVIEW_EXHAUSTED_ESC_PREFIX2)) return "review_exhausted";
@@ -233288,7 +233862,7 @@ function parseStopLossFacts(kind, body2) {
233288
233862
  const reason = /末次失败原因(原文):([^\n]+)/.exec(body2)?.[1]?.trim();
233289
233863
  return { ...n2 ? { count: Number(n2) } : {}, ...reason ? { lastReason: firstSentence(reason) } : {} };
233290
233864
  }
233291
- const n = /(?:跑了|累计有)\s*(\d+)\s*次/.exec(body2)?.[1];
233865
+ const n = /(?:跑了|有)\s*(\d+)\s*轮/.exec(body2)?.[1] ?? /(?:跑了|累计有)\s*(\d+)\s*次/.exec(body2)?.[1];
233292
233866
  const rawCost = /约\s*\$([\d.]+)/.exec(body2)?.[1];
233293
233867
  const cost = rawCost !== void 0 && Number.isFinite(Number(rawCost)) ? `$${Number(rawCost).toFixed(2)}` : void 0;
233294
233868
  return { ...n ? { count: Number(n) } : {}, ...cost ? { cost } : {} };
@@ -233646,7 +234220,7 @@ function buildWorkorderActivity(input) {
233646
234220
  card2.segmentOpen = false;
233647
234221
  card2.status = interrupt.kind === "gap" ? ACTIVITY_COPY.interruptedByGap(name, nameOf(managerId ?? ownerId)) : ACTIVITY_COPY.interruptedByIssue(name);
233648
234222
  if (interrupt.kind === "gap" && interrupt.authorId) {
233649
- card2.actions = [{ kind: "open_thread", label: BUTTON.gapThread, target: gapThreadSessionId(interrupt.authorId) }];
234223
+ card2.actions = [{ kind: "open_thread", label: BUTTON.view, target: gapThreadSessionId(interrupt.authorId) }];
233650
234224
  }
233651
234225
  if (interrupt.at > card2.updatedAt) card2.updatedAt = interrupt.at;
233652
234226
  } else if (supersededBy) {
@@ -233700,6 +234274,7 @@ function buildWorkorderActivity(input) {
233700
234274
  const recoveryLetterIssueIds = /* @__PURE__ */ new Set();
233701
234275
  const plainComments = [];
233702
234276
  const escalations = [];
234277
+ const gapCreatedSeq = /* @__PURE__ */ new Map();
233703
234278
  const plainResolveEvents = /* @__PURE__ */ new Map();
233704
234279
  const acceptCards = /* @__PURE__ */ new Map();
233705
234280
  const standalone = [];
@@ -233956,7 +234531,10 @@ function buildWorkorderActivity(input) {
233956
234531
  });
233957
234532
  break;
233958
234533
  }
233959
- if (isGap) break;
234534
+ if (isGap) {
234535
+ gapCreatedSeq.set(issueId, rec2.seq);
234536
+ break;
234537
+ }
233960
234538
  if (isRecoveryLetter) recoveryLetterIssueIds.add(issueId);
233961
234539
  const nodeAssignee = nodeId ? nodeById.get(nodeId)?.assigneeActorId : void 0;
233962
234540
  const commentCounterpart = nodeAssignee ?? ownerId;
@@ -233982,7 +234560,7 @@ function buildWorkorderActivity(input) {
233982
234560
  全库实测最长 8096 字印两遍(`ws:wo-56bba3c1`),一张卡顶掉整屏。 */
233983
234561
  artifacts: [],
233984
234562
  /* 缺口不出按钮(处置在缺口线上,由「任务内沟通」那一列进);
233985
- 留言给「去查看」直达留言线——线是投影出来的、恒存在,id 这里就能算;
234563
+ 留言给「查看留言」直达留言线——线是投影出来的、恒存在,id 这里就能算;
233986
234564
  回压仍给「去处理」落到产物预览栏(那儿才有批注的处置面)。
233987
234565
  留言算不出对面是谁(该产物没负责人)时退回「去处理」,别给一个开不了的落点。 */
233988
234566
  actions: isBackpressure || !commentTargetId ? [{ kind: "open_issue", label: BUTTON.handle, target: issueId }] : [{ kind: "open_thread", label: BUTTON.view, target: commentThreadSessionId(authorId, commentTargetId) }]
@@ -234156,154 +234734,135 @@ function buildWorkorderActivity(input) {
234156
234734
  commentIssueIds: ids2
234157
234735
  });
234158
234736
  }
234159
- {
234160
- const groupKeyOf2 = (e, kind) => kind === "output_stall" ? `${e.nodeId ?? ""}|${kind}` : `solo|${e.id}`;
234161
- const groups = /* @__PURE__ */ new Map();
234162
- for (const e of [...escalations].sort((a, b2) => a.at.localeCompare(b2.at) || a.seq - b2.seq)) {
234163
- const kind = escalationKindOf(e.id, issueById.get(e.id)?.authorActorId ?? e.authorId);
234164
- const key2 = groupKeyOf2(e, kind);
234165
- groups.set(key2, [...groups.get(key2) ?? [], { ...e, kind }]);
234166
- }
234167
- for (const members of groups.values()) {
234168
- const first = members[0];
234169
- const latest = members.at(-1);
234170
- const kind = latest.kind;
234171
- const resolvedAt = (id) => issueById.get(id)?.resolvedAt ?? null;
234172
- const open3 = members.filter((m2) => !resolvedAt(m2.id));
234173
- const target = resolveEscalationRecipient({
234174
- recipients: latest.recipients,
234175
- authorActorId: latest.authorId,
234176
- stuckActorId: latest.stuckActorId,
234177
- ownerActorId: ownerId,
234178
- managerActorId: managerId
234179
- });
234180
- const stuckActor = (isStopLoss(kind) ? latest.nodeId ? nodeById.get(latest.nodeId)?.assigneeActorId : void 0 : void 0) ?? latest.stuckActorId;
234181
- const active = open3.at(-1) ?? latest;
234182
- const inDiscussion = open3.some((m2) => input.escalationsInDiscussion?.has(m2.id));
234183
- const stamps = [
234184
- ...members.map((m2) => m2.at),
234185
- ...members.map((m2) => resolvedAt(m2.id)).filter((t) => !!t)
234186
- ].sort();
234187
- const gapBehind = (id) => {
234188
- const gapId = issueById.get(id)?.gapId;
234189
- return typeof gapId === "string" && !!issueById.get(gapId);
234190
- };
234191
- const lastReviewFailure = kind === "review_exhausted" ? (input.runFailures ?? []).filter((f2) => f2.nodeId === latest.nodeId && f2.actorId === latest.authorId && f2.startedAt <= latest.at && f2.message.trim()).sort((a, b2) => a.startedAt.localeCompare(b2.startedAt)).at(-1)?.message : void 0;
234192
- const head = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHeadRetryExhausted(nodeName(latest.nodeId)) : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHeadReviewExhausted(
234193
- nodeName(latest.nodeId),
234194
- lastReviewFailure ? firstSentence(lastReviewFailure) : void 0
234195
- ) : kind === "output_stall" ? ACTIVITY_COPY.escalationHeadOutputStall(nodeName(latest.nodeId)) : gapBehind(latest.id) ? ACTIVITY_COPY.escalationHeadGapEscalated(nameOf(latest.authorId), nodeName(latest.nodeId)) : ACTIVITY_COPY.escalationHeadManual(nameOf(stuckActor));
234196
- const howTo = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHowToRetryExhausted : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHowToReviewExhausted : kind === "output_stall" ? ACTIVITY_COPY.escalationHowToOutputStall : void 0;
234197
- const closing = issueById.get(latest.id);
234198
- const closeEvent = plainResolveEvents.get(latest.id);
234199
- const closedBy = text5(closing?.resolvedBy) ?? closeEvent?.by ?? target;
234200
- const closingNote = text5(closing?.resolvedNote) ?? text5(closeEvent?.note);
234201
- const noteOf = (id) => text5(issueById.get(id)?.resolvedNote) ?? text5(plainResolveEvents.get(id)?.note);
234202
- const rerunClosed = closingNote === ESCALATION_RERUN_NOTE;
234203
- const rerunOrdinal = escalations.filter((e) => e.nodeId === latest.nodeId && noteOf(e.id) === ESCALATION_RERUN_NOTE && (issueById.get(e.id)?.resolvedAt ?? "") <= (issueById.get(latest.id)?.resolvedAt ?? "")).length;
234204
- const doneStatus = rerunClosed ? ACTIVITY_COPY.escalationRerun(nameOf(closedBy), Math.max(rerunOrdinal, 1)) : ACTIVITY_COPY.escalationHandled(head, nameOf(closedBy), closingNote);
234205
- const retryExhaustedSoFar = escalations.filter((e) => e.nodeId === latest.nodeId && escalationKindOf(e.id, issueById.get(e.id)?.authorActorId ?? e.authorId) === "retry_exhausted" && e.at <= latest.at).length;
234206
- const viewerIsTarget = !input.viewerActorId || input.viewerActorId === target;
234207
- const offerRerun = kind === "retry_exhausted" && viewerIsTarget && retryExhaustedSoFar < 2 && !!latest.nodeId;
234208
- issueCards.set(first.id, {
234209
- id: `issue:${first.id}`,
234210
- seq: first.seq,
234211
- at: first.at,
234212
- updatedAt: stamps.at(-1) ?? first.at,
234213
- ...first.nodeId ? { nodeId: first.nodeId } : {},
234214
- executorId: target,
234215
- /* 代操作人只在**还没关**的时候带:那是「谁替谁把这条上报派成待办」的事实。
234216
- 关掉之后归因只认这次收口(`resolvedBy`),不沿用派发待办时的代操作者。 */
234217
- ...open3.length > 0 && active.handActorId ? { handActorId: active.handActorId } : {},
234218
- escalationKind: kind,
234219
- /* 大状态三档(F5):待处理 → 处理中(讨论房里已经有人说过话)→ 已完成。
234220
- 颜色跟着「这一段还开着吗」走:没关 ⇒ 暖橙(与「待开始」同一档),关了 ⇒ 灰。
234221
- 关掉那一档不覆写 `phaseLabel`,用四档默认的「已完成」。 */
234222
- phase: open3.length === 0 ? "done" : inDiscussion ? "running" : "not_started",
234223
- segmentOpen: open3.length > 0,
234224
- ...open3.length === 0 ? {} : { phaseLabel: inDiscussion ? PHASE_LABEL.escalationInProgress : PHASE_LABEL.escalationPending },
234225
- status: open3.length === 0 ? doneStatus : ACTIVITY_COPY.escalationPendingWith(head, nameOf(target), howTo),
234226
- /* 上报正文**不上卡**——与缺口 / 回压同一条口径。此前这里把整封信塞进展开区,
234227
- `ws:wo-635d7add` 那条渲染出的是一屏字,连内部 artifact id 都印在卡面上。
234228
- 全文的去处,就是那个「去处理」按钮。 */
234229
- artifacts: [],
234230
- /* 「去处理」进**还开着的那一条**上报自己的讨论房(`discuss_escalation`)——合并卡上点进去
234231
- 必须是要人处理的那一条,不是第一条。房间不在这里造:前端拿 escalationId 去打
234232
- `POST /api/inbox/discuss`,那个端点按项幂等(ADR 0079 D1「一上报一房」)。
234233
- 按钮文案按**查看者**分两态(Bug 0151 / PR #2003 / 用例 T-31):不是收件人就写「去查看」
234234
- ——他点进去只有只读视图,按钮不该承诺一件他做不到的事。查看者取不到时保持「去处理」。
234235
- 关掉之后按钮不收走、只换成「去查看」:那间讨论房是这条上报唯一的来龙去脉。 */
234236
- actions: open3.length === 0 ? [{ kind: "discuss_escalation", label: BUTTON.view, target: latest.id }] : offerRerun ? [
234237
- { kind: "rerun_node", label: BUTTON.rerun, target: active.id, variant: "primary" },
234238
- { kind: "discuss_escalation", label: BUTTON.handle, target: active.id, variant: "secondary" }
234239
- ] : [{
234240
- kind: "discuss_escalation",
234241
- label: viewerIsTarget ? BUTTON.handle : BUTTON.view,
234242
- target: active.id
234243
- }]
234244
- });
234245
- }
234737
+ for (const e of [...escalations].sort((a, b2) => a.at.localeCompare(b2.at) || a.seq - b2.seq)) {
234738
+ const kind = escalationKindOf(e.id, issueById.get(e.id)?.authorActorId ?? e.authorId);
234739
+ const resolvedAt = (id) => issueById.get(id)?.resolvedAt ?? null;
234740
+ const closedAt = resolvedAt(e.id);
234741
+ const open3 = !closedAt;
234742
+ const target = resolveEscalationRecipient({
234743
+ recipients: e.recipients,
234744
+ authorActorId: e.authorId,
234745
+ stuckActorId: e.stuckActorId,
234746
+ ownerActorId: ownerId,
234747
+ managerActorId: managerId
234748
+ });
234749
+ const stuckActor = (isStopLoss(kind) ? e.nodeId ? nodeById.get(e.nodeId)?.assigneeActorId : void 0 : void 0) ?? e.stuckActorId;
234750
+ const inDiscussion = open3 && !!input.escalationsInDiscussion?.has(e.id);
234751
+ const gapBehind = (id) => {
234752
+ const gapId = issueById.get(id)?.gapId;
234753
+ return typeof gapId === "string" && !!issueById.get(gapId);
234754
+ };
234755
+ const head = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHeadRetryExhausted(nodeName(e.nodeId)) : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHeadReviewExhausted(nodeName(e.nodeId)) : kind === "output_stall" ? ACTIVITY_COPY.escalationHeadOutputStall(nodeName(e.nodeId)) : gapBehind(e.id) ? ACTIVITY_COPY.escalationHeadGapEscalated(nameOf(e.authorId), nodeName(e.nodeId)) : ACTIVITY_COPY.escalationHeadManual(nameOf(stuckActor));
234756
+ const howTo = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHowToRetryExhausted : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHowToReviewExhausted : kind === "output_stall" ? ACTIVITY_COPY.escalationHowToOutputStall : void 0;
234757
+ const closing = issueById.get(e.id);
234758
+ const closeEvent = plainResolveEvents.get(e.id);
234759
+ const closedBy = text5(closing?.resolvedBy) ?? closeEvent?.by ?? target;
234760
+ const closingNote = text5(closing?.resolvedNote) ?? text5(closeEvent?.note);
234761
+ const noteOf = (id) => text5(issueById.get(id)?.resolvedNote) ?? text5(plainResolveEvents.get(id)?.note);
234762
+ const rerunClosed = closingNote === ESCALATION_RERUN_NOTE;
234763
+ const rerunOrdinal = escalations.filter((x2) => x2.nodeId === e.nodeId && noteOf(x2.id) === ESCALATION_RERUN_NOTE && (resolvedAt(x2.id) ?? "") <= (closedAt ?? "")).length;
234764
+ const doneStatus = rerunClosed ? ACTIVITY_COPY.escalationRerun(nameOf(closedBy), Math.max(rerunOrdinal, 1)) : ACTIVITY_COPY.escalationHandled(head, nameOf(closedBy), closingNote);
234765
+ const retryExhaustedSoFar = escalations.filter((x2) => x2.nodeId === e.nodeId && escalationKindOf(x2.id, issueById.get(x2.id)?.authorActorId ?? x2.authorId) === "retry_exhausted" && x2.at <= e.at).length;
234766
+ const viewerIsTarget = !input.viewerActorId || input.viewerActorId === target;
234767
+ const offerRerun = kind === "retry_exhausted" && viewerIsTarget && retryExhaustedSoFar < 2 && !!e.nodeId;
234768
+ issueCards.set(e.id, {
234769
+ id: `issue:${e.id}`,
234770
+ seq: e.seq,
234771
+ at: e.at,
234772
+ updatedAt: closedAt ?? e.at,
234773
+ ...e.nodeId ? { nodeId: e.nodeId } : {},
234774
+ executorId: target,
234775
+ /* 代操作人只在**还没关**的时候带:那是「谁替谁把这条上报派成待办」的事实。
234776
+ 关掉之后归因只认这次收口(`resolvedBy`),不沿用派发待办时的代操作者。 */
234777
+ ...open3 && e.handActorId ? { handActorId: e.handActorId } : {},
234778
+ escalationKind: kind,
234779
+ /* 大状态三档(F5):待处理 → 处理中(讨论房里已经有人说过话)→ 已完成。
234780
+ 关掉那一档不覆写 `phaseLabel`,用四档默认的「已完成」。 */
234781
+ phase: !open3 ? "done" : inDiscussion ? "running" : "not_started",
234782
+ segmentOpen: open3,
234783
+ /* 颜色(奕璇 2026-09-22 定的判据「是不是在等人动手」):**上报没关 ⇒ 砖红**——这一段就卡在人
234784
+ 身上,卡面给的正是「去处理」/「重新运行」。关了 ⇒ 不再等人,`segmentOpen` 也随之为假 ⇒ 灰。
234785
+ ⚠ **不按查看者分**:非收件人看到的按钮词是「查看留言」(Bug 0151 / 用例 T-31),但这件事照样
234786
+ 卡在人身上——颜色说的是这一段的状态,不是「轮到你了没有」。 */
234787
+ awaitingHuman: open3,
234788
+ ...open3 ? { phaseLabel: inDiscussion ? PHASE_LABEL.escalationInProgress : PHASE_LABEL.escalationPending } : {},
234789
+ status: open3 ? ACTIVITY_COPY.escalationPendingWith(head, nameOf(target), howTo) : doneStatus,
234790
+ /* 上报正文**不上卡**——与缺口 / 回压同一条口径。此前这里把整封信塞进展开区,
234791
+ `ws:wo-635d7add` 那条渲染出的是一屏字,连内部 artifact id 都印在卡面上。
234792
+ 全文的去处,就是那个「去处理」按钮。 */
234793
+ artifacts: [],
234794
+ /* 「去处理」进**这条上报自己的**讨论房(`discuss_escalation`)——卡与房一一对应(ADR 0079 D1
234795
+ 「一上报一房」),所以关掉之后这张卡仍然指着当初处理它的那间房,不会被后来新开的上报改指。
234796
+ 房间不在这里造:前端拿 escalationId 去打 `POST /api/inbox/discuss`,那个端点按项幂等。
234797
+ 按钮文案按**查看者**分两态(Bug 0151 / PR #2003 / 用例 T-31):不是收件人就写「查看留言」
234798
+ ——他点进去只有只读视图,按钮不该承诺一件他做不到的事。查看者取不到时保持「去处理」。
234799
+ 关掉之后按钮不收走、只换成「查看留言」:那间讨论房是这条上报唯一的来龙去脉。 */
234800
+ actions: !open3 ? [{ kind: "discuss_escalation", label: BUTTON.view, target: e.id }] : offerRerun ? [
234801
+ { kind: "rerun_node", label: BUTTON.rerun, target: e.id, variant: "primary" },
234802
+ { kind: "discuss_escalation", label: BUTTON.handle, target: e.id, variant: "secondary" }
234803
+ ] : [{
234804
+ kind: "discuss_escalation",
234805
+ label: viewerIsTarget ? BUTTON.handle : BUTTON.view,
234806
+ target: e.id
234807
+ }]
234808
+ });
234246
234809
  }
234247
234810
  const coordinatorCards = [];
234811
+ const gapsClosedByCoordinatorCard = /* @__PURE__ */ new Set();
234248
234812
  {
234249
- const runs = [...input.coordinatorRuns ?? []].filter((r) => !!r.id && !!r.nodeId).sort((a, b2) => a.startedAt.localeCompare(b2.startedAt) || a.id.localeCompare(b2.id));
234250
- const episodes = [];
234251
- for (const r of runs) {
234252
- const prev = episodes.at(-1);
234253
- const overlapping = !!prev && prev.nodeId === r.nodeId && prev.actorId === r.actorId && (prev.endedAt === null || r.startedAt <= prev.endedAt);
234254
- if (prev && overlapping) {
234255
- prev.endedAt = prev.endedAt === null || !r.endedAt ? null : r.endedAt > prev.endedAt ? r.endedAt : prev.endedAt;
234256
- prev.statuses.push(r.status ?? null);
234257
- continue;
234258
- }
234259
- episodes.push({
234260
- id: r.id,
234261
- nodeId: r.nodeId,
234262
- actorId: r.actorId,
234263
- startedAt: r.startedAt,
234264
- endedAt: r.endedAt ?? null,
234265
- statuses: [r.status ?? null]
234266
- });
234267
- }
234268
- for (const run of episodes) {
234269
- const name = nodeName(run.nodeId);
234270
- const endedAt = run.endedAt;
234271
- const inWindow2 = (at) => !!at && at >= run.startedAt && (!endedAt || at <= endedAt);
234272
- const escalated = snap.issues.find((i) => i.kind === "escalation" && (i.aboutNodeId ?? i.raisedByNodeId) === run.nodeId && escalationKindOf(i.id, i.authorActorId) === "manual" && inWindow2(i.createdAt));
234273
- const fixedGap = snap.issues.find((i) => i.kind === "gap" && (i.aboutNodeId ?? i.raisedByNodeId) === run.nodeId && inWindow2(i.resolvedAt));
234274
- const gapOnNode = snap.issues.find((i) => i.kind === "gap" && (i.aboutNodeId ?? i.raisedByNodeId) === run.nodeId && i.createdAt <= (endedAt ?? run.startedAt));
234275
- const failedRun = !!endedAt && run.statuses.every((st) => !!st && st !== "succeeded");
234813
+ const gaps = [...snap.issues].filter((i) => i.kind === "gap" && !!(i.aboutNodeId ?? i.raisedByNodeId)).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || a.id.localeCompare(b2.id));
234814
+ for (const gap of gaps) {
234815
+ const gapId = gap.id;
234816
+ const nodeId = gap.aboutNodeId ?? gap.raisedByNodeId;
234817
+ const name = nodeName(nodeId);
234818
+ const resolvedAt = gap.resolvedAt ?? null;
234819
+ const inWindow2 = (at) => !!at && at >= gap.createdAt && (!resolvedAt || at <= resolvedAt);
234820
+ const escalated = snap.issues.find((i) => i.kind === "escalation" && escalationKindOf(i.id, i.authorActorId) === "manual" && (i.gapId === gapId || (i.aboutNodeId ?? i.raisedByNodeId) === nodeId && inWindow2(i.createdAt)));
234821
+ const run = coordinatorRunOfGap(
234822
+ { createdAt: gap.createdAt, resolvedAt },
234823
+ (input.coordinatorRuns ?? []).filter((r) => !!r.id && r.nodeId === nodeId)
234824
+ );
234825
+ const failedRun = !!run?.endedAt && !!run.status && run.status !== "succeeded";
234826
+ const coordinatorId = gap.resolvedBy ?? run?.actorId ?? managerId ?? ownerId;
234827
+ const executorId = gap.authorActorId;
234276
234828
  const recipient = escalated ? resolveEscalationRecipient({
234277
234829
  recipients: (snap.issueRecipients ?? []).filter((r) => r.issueId === escalated.id).map((r) => r.actorId),
234278
234830
  authorActorId: escalated.authorActorId,
234279
- stuckActorId: gapOnNode?.authorActorId ?? escalated.authorActorId,
234831
+ stuckActorId: executorId,
234280
234832
  ownerActorId: ownerId,
234281
234833
  managerActorId: managerId
234282
234834
  }) : void 0;
234283
- coordinatorCards.push({
234284
- id: `${COORDINATOR_CARD_PREFIX}${run.id}`,
234285
- seq: 0,
234286
- at: run.startedAt,
234287
- updatedAt: endedAt ?? run.startedAt,
234288
- nodeId: run.nodeId,
234289
- executorId: run.actorId,
234290
- /* 大状态用四档默认词(进行中 / 已完成),不覆写 `phaseLabel`——「补上了还是升级了」
234291
- 在小状态那句话里说得比一个词清楚。颜色跟着「这一段还开着吗」走。 */
234292
- phase: endedAt ? "done" : "running",
234293
- segmentOpen: !endedAt,
234294
- status: !endedAt ? ACTIVITY_COPY.coordinatingNode(name) : escalated ? ACTIVITY_COPY.coordinatorEscalated(nameOf(run.actorId), name, nameOf(recipient)) : fixedGap ? ACTIVITY_COPY.coordinatorFixedGap(nameOf(run.actorId), nameOf(gapOnNode?.authorActorId)) : failedRun ? ACTIVITY_COPY.coordinatorFailed(name) : ACTIVITY_COPY.coordinatorInconclusive(nameOf(run.actorId), name),
234835
+ const segmentOpen = !resolvedAt && !escalated;
234836
+ const draft = {
234837
+ id: coordinatorCardId(gapId),
234838
+ seq: gapCreatedSeq.get(gapId) ?? 0,
234839
+ at: gap.createdAt,
234840
+ updatedAt: [gap.createdAt, resolvedAt, escalated?.createdAt, run?.endedAt].filter((t) => !!t).sort().at(-1),
234841
+ nodeId,
234842
+ executorId: coordinatorId,
234843
+ /* 大状态用四档默认词(进行中 / 已完成 / 执行中断),不覆写 `phaseLabel`——「补上了还是升级了」
234844
+ 在小状态那句话里说得比一个词清楚。颜色跟着 `segmentOpen` 走。 */
234845
+ phase: !segmentOpen ? "done" : failedRun ? "stuck" : "running",
234846
+ segmentOpen,
234847
+ /* 协调者卡**从不砖红**:缺口的第一接手人恒是协调者本人,这一段没有人在等着动手
234848
+ (奕璇 2026-09-22 的判据)。连它自己那一段跑挂了(`failedRun` ⇒ `phase: "stuck"`)也一样,
234849
+ 那时按 `segmentOpen` 走暖橙——真要人动手的是它升级出去的那张升级卡,红归那一张。
234850
+ 显式写 `false` 而不是省略:省略 = 不表态,前端会退回老规则(`stuck` + 开着 ⇒ 砖红)。 */
234851
+ awaitingHuman: false,
234852
+ status: resolvedAt ? ACTIVITY_COPY.coordinatorFixedGap(nameOf(coordinatorId), nameOf(executorId)) : escalated ? ACTIVITY_COPY.coordinatorEscalated(nameOf(coordinatorId), name, nameOf(recipient)) : failedRun ? ACTIVITY_COPY.coordinatorFailed(name) : ACTIVITY_COPY.coordinatingGap(nameOf(coordinatorId), name),
234295
234853
  artifacts: [],
234296
- /* 点进去 = **这段协调轨迹**(run 在 `agent_runs` 里,`runId` 直接给)+ 缺口线。 */
234297
- traceAvailable: true,
234298
- runId: run.id,
234299
- actions: gapOnNode ? [{ kind: "open_thread", label: BUTTON.gapThread, target: gapThreadSessionId(gapOnNode.authorActorId) }] : []
234300
- });
234854
+ /* 点进去 = **这段协调轨迹**(run 在 `agent_runs` 里,`runId` 直接给)+ 缺口线。
234855
+ 查不到那一段运行 ⇒ 不给轨迹入口,卡与那颗「查看留言」照旧。 */
234856
+ ...run ? { traceAvailable: true, runId: run.id } : {},
234857
+ actions: [{ kind: "open_thread", label: BUTTON.view, target: gapThreadSessionId(executorId) }]
234858
+ };
234859
+ coordinatorCards.push(draft);
234860
+ if (resolvedAt) gapsClosedByCoordinatorCard.add(gapId);
234301
234861
  }
234302
234862
  }
234303
- for (const [issueId, d] of [...issueCards]) {
234863
+ for (const issueId of [...issueCards.keys()]) {
234304
234864
  if (!issueId.startsWith(REWORK_ANNOTATION_PREFIX)) continue;
234305
- const covered = coordinatorCards.some((c) => c.nodeId === d.nodeId && c.at <= d.at && d.at <= c.updatedAt);
234306
- if (covered) issueCards.delete(issueId);
234865
+ if (gapsClosedByCoordinatorCard.has(issueId.slice(REWORK_ANNOTATION_PREFIX.length))) issueCards.delete(issueId);
234307
234866
  }
234308
234867
  const lastFailureOf = (card2, trip) => {
234309
234868
  const message = (input.runFailures ?? []).filter((f2) => f2.nodeId === card2.nodeId && f2.startedAt >= card2.at && f2.startedAt <= trip.at && f2.message.trim()).sort((a, b2) => a.startedAt.localeCompare(b2.startedAt)).at(-1)?.message;
@@ -234430,6 +234989,7 @@ function buildWorkorderActivity(input) {
234430
234989
  d.phase = "done";
234431
234990
  delete d.phaseLabel;
234432
234991
  d.segmentOpen = false;
234992
+ d.awaitingHuman = false;
234433
234993
  d.status = ACTIVITY_COPY.closedNoAction(terminal);
234434
234994
  if (!d.commentIssueIds) d.actions = [];
234435
234995
  }
@@ -234498,6 +235058,7 @@ function buildWorkorderActivity(input) {
234498
235058
  phase: d.phase,
234499
235059
  ...d.phaseLabel ? { phaseLabel: d.phaseLabel } : {},
234500
235060
  ...d.segmentOpen === void 0 ? {} : { segmentOpen: d.segmentOpen },
235061
+ ...d.awaitingHuman === void 0 ? {} : { awaitingHuman: d.awaitingHuman },
234501
235062
  ...d.escalationKind ? { escalationKind: d.escalationKind } : {},
234502
235063
  ...d.noticeTitle ? { noticeTitle: d.noticeTitle } : {},
234503
235064
  status: d.status,
@@ -234515,7 +235076,7 @@ function buildWorkorderActivity(input) {
234515
235076
  cards.sort((a, b2) => a.at.localeCompare(b2.at) || a.seq - b2.seq || a.id.localeCompare(b2.id));
234516
235077
  return { workorderId, cards, truncated };
234517
235078
  }
234518
- var ACTIVITY_EVENT_KINDS, LEGACY_PAUSE_REASON, ACTIVITY_EVENT_LIMIT, UPSTREAM_ACK_NOTE_CAP, ACTIVITY_COPY, REWORK_ANNOTATION_PREFIX, RECOVERY_ANNOTATION_PREFIX, ENGINE_RETRY_EXHAUSTED, BUTTON, PHASE_LABEL, COORDINATOR_CARD_PREFIX, RETRY_EXHAUSTED_ESC_PREFIX, REVIEW_EXHAUSTED_ESC_PREFIX2, isStopLoss;
235079
+ var ACTIVITY_EVENT_KINDS, LEGACY_PAUSE_REASON, ACTIVITY_EVENT_LIMIT, UPSTREAM_ACK_NOTE_CAP, ACTIVITY_COPY, REWORK_ANNOTATION_PREFIX, RECOVERY_ANNOTATION_PREFIX, ENGINE_RETRY_EXHAUSTED, BUTTON, PHASE_LABEL, COORDINATOR_CARD_PREFIX, coordinatorCardId, RETRY_EXHAUSTED_ESC_PREFIX, REVIEW_EXHAUSTED_ESC_PREFIX2, isStopLoss;
234519
235080
  var init_activity2 = __esm({
234520
235081
  "../server/src/domains/collab/activity.ts"() {
234521
235082
  "use strict";
@@ -234648,9 +235209,10 @@ var init_activity2 = __esm({
234648
235209
  改完之后两种止损的**执行者是一样的**(执行卡挂执行人、升级卡挂等处理的那个人),
234649
235210
  能不能分辨全落在这几句话上。投影侧怎么判是哪一种见 {@link escalationKindOf}。
234650
235211
  次数与花费**取上报正文里系统自己算出来的那个数**(引擎 `activate.ts` 的 `连续 N 次执行失败`、
234651
- serve `zero-output-breaker.ts` 的 `跑了 N 次 … 约 $X`)——本投影手上没有第二份可信来源:
234652
- 保险丝数的是 30 分钟窗口里的 `agent_runs` 空跑数(`ws:wo-7cf619ff` 实测 5 次),而快照里
234653
- 同一节点只有 3 条 work,按 work 数自己算会说出一个跟上报正文对不上的数。 */
235212
+ serve `zero-output-breaker.ts` 的 `跑了 N 轮 … 约 $X`)——本投影手上没有第二份可信来源,
235213
+ 上报正文是唯一事实源(奕璇 2026-09-22 定)。两头的口径都已经是**轮**:引擎那条本来就数
235214
+ work,保险丝那条 Bug 0211 起把同一轮里的协议续唤 run 折回去再数(老正文不回填,见
235215
+ {@link parseStopLossFacts})。 */
234654
235216
  retryExhausted: (executor, node2, n, lastReason) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u91CD\u8BD5\u5DF2\u7528\u5C3D${lastReason ? `\uFF08\u672B\u6B21\uFF1A${lastReason}\uFF09` : ""}\u3002`,
234655
235217
  outputStalled: (executor, node2, n, cost) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u4EA4\u51FA\u300A${node2}\u300B\u7684\u4EFB\u4F55\u6210\u679C${cost ? `\uFF08\u7EA6 ${cost}\uFF09` : ""}\uFF0C\u7CFB\u7EDF\u5DF2\u6B62\u635F\u3002`,
234656
235218
  /* ── 升级卡(上报)的整句 ───────────────────────────────────────────────────────────
@@ -234659,8 +235221,16 @@ var init_activity2 = __esm({
234659
235221
  同一件事,只换后半句(「等 @X 处理」→「X 已处理:…」)。 */
234660
235222
  escalationHeadRetryExhausted: (node2) => `\u300A${node2}\u300B\u91CD\u8BD5\u5DF2\u7528\u5C3D\uFF0C`,
234661
235223
  escalationHeadOutputStall: (node2) => `\u7CFB\u7EDF\u6B62\u635F\uFF1A\u300A${node2}\u300B\u505C\u4F4F\u4E86\uFF0C`,
234662
- /** 评审连败熔断(Bug 0212):卡住的是**这一轮评审**,不是那一版活——话不能写成「执行异常」。 */
234663
- escalationHeadReviewExhausted: (node2, lastReason) => `\u300A${node2}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7740\u6CA1\u8DD1\u6210\uFF0C\u8FD9\u4E00\u8F6E\u6536\u4E0D\u9F50${lastReason ? `\uFF08\u672B\u6B21\uFF1A${lastReason}\uFF09` : ""}\uFF0C`,
235224
+ /**
235225
+ * 评审连败熔断(Bug 0212):卡住的是**这一轮评审**,不是那一版活——话不能写成「执行异常」。
235226
+ *
235227
+ * 🚨 **这里没有「(末次:…)」**,与连败熔断卡不同:报错原话只在 `agent_runs.error_message`,
235228
+ * 而**评审派发的 run 行 `node_id` 与 `work_order_id` 双双为 null**(v2 `ws:wo-65841524` 实测:
235229
+ * qa-quality 连败那 4 条只有 `artifact_id`)——`listRunFailures` 按 `work_order_id` 圈范围,
235230
+ * 这批行根本取不到。硬拼一句取不到值的模板只会永远不出现,不如不写。
235231
+ * 要补这半句,得先让评审派发把 `work_order_id` / `node_id` 记进 `agent_runs`(另开单)。
235232
+ */
235233
+ escalationHeadReviewExhausted: (node2) => `\u300A${node2}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7740\u6CA1\u8DD1\u6210\uFF0C\u8FD9\u4E00\u8F6E\u6536\u4E0D\u9F50\uFF0C`,
234664
235234
  /** 人工上报里**关联着缺口**的那一档:协调者接了缺口、补不了,于是升级给人(四种停法的第二种)。 */
234665
235235
  escalationHeadGapEscalated: (coordinator, node2) => `${coordinator} \u8865\u4E0D\u4E86\u300A${node2}\u300B\u7684\u7F3A\u53E3\uFF0C`,
234666
235236
  /** 其余人工上报:主语是卡住的那个执行人(异常是它的,不是上报作者的)。 */
@@ -234686,15 +235256,20 @@ var init_activity2 = __esm({
234686
235256
  * ——人并没有写说明,说明是这颗按钮替他写的。次数是**这个节点上第几次这么重试**。
234687
235257
  */
234688
235258
  escalationRerun: (by, n) => `${by} \u91CD\u65B0\u8FD0\u884C\u4E86\u8FD9\u4E00\u6B65\uFF08\u7B2C ${n} \u6B21\u91CD\u8BD5\uFF09\u3002`,
234689
- /* ── 协调者卡(F2,新增)──────────────────────────────────────────────────────────
234690
- 对应「协调者处理这个节点的卡点」那一段运行(`agent_runs` 里 id 为 `coordinate:<uuid>` 的行,
234691
- 生产 9/10 起 9 天 71 段、17 张单,此前一张卡都没有)。它不在引擎快照里,由 handler 喂进来。 */
234692
- coordinatingNode: (node2) => `\u6B63\u5728\u5904\u7406\u300A${node2}\u300B\u7684\u7F3A\u53E3\u3002`,
235259
+ /* ── 协调者卡(F2)──────────────────────────────────────────────────────────────────
235260
+ 「协调者处理**某条缺口**的那一段」。三句话对应缺口的三种去向(已补齐 / 补不了升级给人 /
235261
+ 还开着),判据全在缺口这条 issue 上,不看旁账那条 `coordinate:` 运行有没有留下结论
235262
+ (Bug 0213;前两句 T-104 / T-113 已验收,逐字不动)。 */
235263
+ coordinatingGap: (coordinator, node2) => `${coordinator} \u6B63\u5728\u5904\u7406\u300A${node2}\u300B\u7684\u7F3A\u53E3\u3002`,
234693
235264
  coordinatorFixedGap: (coordinator, executor) => `${coordinator} \u5DF2\u8865\u9F50\u7F3A\u53E3\uFF0C${executor} \u7EE7\u7EED\u6267\u884C\u3002`,
234694
235265
  coordinatorEscalated: (coordinator, node2, target) => `${coordinator} \u8865\u4E0D\u4E86\u300A${node2}\u300B\u7684\u7F3A\u53E3\uFF0C\u5DF2\u5347\u7EA7\u7ED9 @${target} \u5904\u7406\u3002`,
234695
- /** 这一段结束了,缺口既没补上也没升级——如实说到此为止,别替它宣布一个没发生的结局。 */
234696
- coordinatorInconclusive: (coordinator, node2) => `${coordinator} \u5904\u7406\u300A${node2}\u300B\u7684\u7F3A\u53E3\uFF0C\u8FD9\u4E00\u6BB5\u6CA1\u6709\u7ED3\u8BBA\u3002`,
234697
- /** 协调者这一段自己没跑成(run 判 failed / orphaned / timeout)——如实说,别冒充「已处理」。 */
235266
+ /**
235267
+ * 缺口还开着、也没升级给人,而这一段协调运行已经结束且没跑成(failed / orphaned / timeout)——
235268
+ * 如实说这一轮没跑成,别说「正在处理」(没人在处理),也别替它宣布一个没发生的结局。
235269
+ *
235270
+ * 🚨 兜底句「{协调者} 处理《X》的缺口,这一段没有结论」**已删**(Bug 0213):那句是拿 run 的结论
235271
+ * 拼卡时的产物——取不到结论就替这一段下一个空结论。缺口自己的状态永远答得出「补上了没有」。
235272
+ */
234698
235273
  coordinatorFailed: (node2) => `\u5904\u7406\u300A${node2}\u300B\u7684\u7F3A\u53E3\u65F6\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u3002`,
234699
235274
  /**
234700
235275
  * 这一轮**被计划变更打断**(P15)。与卡点中断同级:计划一动,这一轮就不再是「还在跑」,
@@ -234809,7 +235384,7 @@ var init_activity2 = __esm({
234809
235384
  *
234810
235385
  * 此前走 `issueOpen`:正文整段当小状态,`detail` 又是同一段。全库实测最长
234811
235386
  * **8096 字印两遍**(`ws:wo-56bba3c1`),一张卡顶掉整屏。正文的去处是「留言线」——
234812
- * 与缺口线同一套投影(`gap-thread.ts` 的 `buildCommentThread`),卡上给「去查看」。
235387
+ * 与缺口线同一套投影(`gap-thread.ts` 的 `buildCommentThread`),卡上给「查看留言」。
234813
235388
  */
234814
235389
  commentRaised: (author, target, node2) => `${author} \u5728\u300A${node2}\u300B\u7559\u8A00\uFF0C\u9700\u8981 @${target} \u67E5\u770B\u3002`,
234815
235390
  /* ── 普通留言卡(奕璇 2026-09-18 定,ADR 0594)────────────────────────────────────────
@@ -234851,9 +235426,17 @@ var init_activity2 = __esm({
234851
235426
  acceptPass: "\u9A8C\u6536\u901A\u8FC7",
234852
235427
  acceptFail: "\u9A8C\u6536\u4E0D\u901A\u8FC7",
234853
235428
  handle: "\u53BB\u5904\u7406",
234854
- view: "\u53BB\u67E5\u770B",
234855
- /** 缺口线(执行人 ↔ 协调者)。缺口卡删掉之后,正文与来回的入口挂在执行卡与协调者卡上(F4)。 */
234856
- gapThread: "\u53BB\u7F3A\u53E3\u7EBF",
235429
+ /**
235430
+ * **只读进房**那一档:留言线、缺口线(执行人 ↔ 协调者)、上报讨论房(旁观者 / 已关掉)共用这一个词。
235431
+ *
235432
+ * 2026-09-22 奕璇拍板把旧的两个名字**并成一个**:缺口线那两处原来写「去缺口线」
235433
+ * (`BUTTON.gapThread`),其余只读入口原来写「去查看」——三处点进去做的是同一件事:
235434
+ * **进这条线看聊天记录**,用户视角没有第二、第三个名字。目标(`open_thread` /
235435
+ * `discuss_escalation` 与 target)一个字没动,只是不再自造第二个词。
235436
+ * 于是卡上只剩三种按钮:「去处理」(等人动手)、「查看留言」(只读进房)、
235437
+ * 「重新运行」(连败熔断升级卡的主按钮)。
235438
+ */
235439
+ view: "\u67E5\u770B\u7559\u8A00",
234857
235440
  /** 连败熔断升级卡的主按钮(F7):重开这一步,并把那条上报一并关掉。 */
234858
235441
  rerun: "\u91CD\u65B0\u8FD0\u884C"
234859
235442
  };
@@ -234866,6 +235449,7 @@ var init_activity2 = __esm({
234866
235449
  escalationInProgress: "\u5904\u7406\u4E2D"
234867
235450
  };
234868
235451
  COORDINATOR_CARD_PREFIX = "coordinator:";
235452
+ coordinatorCardId = (gapId) => `${COORDINATOR_CARD_PREFIX}${gapId}`;
234869
235453
  RETRY_EXHAUSTED_ESC_PREFIX = "esc:retry-exhausted:";
234870
235454
  REVIEW_EXHAUSTED_ESC_PREFIX2 = "esc:review-exhausted:";
234871
235455
  isStopLoss = (kind) => kind === "retry_exhausted" || kind === "output_stall";
@@ -234886,10 +235470,12 @@ function buildWorkorderActivityTrace(input) {
234886
235470
  const reviews = activityReviews(snap, input.events ?? []);
234887
235471
  const reviewTarget = reviewCardTarget(cardId, reviews);
234888
235472
  if (reviewTarget) return buildReviewHistory(input, reviewTarget.targetWorkId, reviewTarget.reviewerActorId, reviews);
234889
- if (cardId.startsWith(COORDINATOR_CARD_PREFIX)) {
234890
- const runId = cardId.slice(COORDINATOR_CARD_PREFIX.length);
234891
- const run = (input.coordinatorRuns ?? []).find((r) => r.id === runId);
234892
- if (!run) return empty2;
235473
+ const coordinatorGapId = coordinatorCardGapId(cardId);
235474
+ if (coordinatorGapId !== void 0) {
235475
+ const gap2 = snap.issues.find((i) => i.id === coordinatorGapId && i.kind === "gap");
235476
+ const gapNodeId = gap2 ? gap2.aboutNodeId ?? gap2.raisedByNodeId : null;
235477
+ const run = gap2 && gapNodeId ? coordinatorRunOfGap(gap2, (input.coordinatorRuns ?? []).filter((r) => r.nodeId === gapNodeId)) : void 0;
235478
+ if (!run) return { ...empty2, notice: "\u534F\u8C03\u8005\u5904\u7406\u8FD9\u6761\u7F3A\u53E3\u7684\u90A3\u4E00\u6BB5\u6CA1\u6709\u7559\u4E0B\u8FD0\u884C\u8BB0\u5F55\uFF0C\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u8F68\u8FF9\u3002" };
234893
235479
  const node2 = snap.nodes.find((n) => n.id === run.nodeId);
234894
235480
  const spec = node2?.spec?.trim();
234895
235481
  return {
@@ -235166,11 +235752,14 @@ var init_activity_trace = __esm({
235166
235752
  });
235167
235753
 
235168
235754
  // ../server/src/domains/collab/inbox.ts
235755
+ function commandCardLine(command2) {
235756
+ return command2.displayLabel?.trim() || stripInternalIds(command2.effectLabel ?? "");
235757
+ }
235169
235758
  function changeConfirmSummary(r) {
235170
235759
  if (r.continuation) return `\u81EA\u52A8\u7EED\u8DD1\u5DF2\u5230\u9650\uFF0C\u7B49\u5F85\u4F60\u5BA1\u6838\uFF1A${r.plan.reason}`;
235171
235760
  const why = r.plan.reason.slice(0, 100);
235172
235761
  if (r.kind === "command" && r.command) {
235173
- return `\u7BA1\u7406\u8005\u8BF7\u6C42\u6267\u884C\u4E00\u4E2A\u9AD8\u5371\u64CD\u4F5C\uFF0C\u7B49\u4F60\u6388\u6743\uFF1A${r.command.effectLabel}`;
235762
+ return `\u7BA1\u7406\u8005\u8BF7\u6C42\u6267\u884C\u4E00\u4E2A\u9AD8\u5371\u64CD\u4F5C\uFF0C\u7B49\u4F60\u6388\u6743\uFF1A${commandCardLine(r.command)}`;
235174
235763
  }
235175
235764
  const ops = r.plan.ops ?? [];
235176
235765
  if (ops.length > 0 && ops.every((o) => o.action === "resolveEscalation")) {
@@ -235346,7 +235935,7 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
235346
235935
  // 仍回落工单详情。
235347
235936
  ...r.origin ? { chatSessionId: r.origin } : {},
235348
235937
  ...anchorArt ? { title: nodeLabel(anchorArt) } : {},
235349
- detail: `${actorName(r.proposedBy) || "\u7BA1\u7406\u8005"}\uFF1A${r.command?.effectLabel ?? firstSentence2(r.plan.reason)}`,
235938
+ detail: `${actorName(r.proposedBy) || "\u7BA1\u7406\u8005"}\uFF1A${r.command ? commandCardLine(r.command) : firstSentence2(r.plan.reason)}`,
235350
235939
  ...r.kind !== "command" && opCount > 0 ? { impact: `\u6D89\u53CA ${opCount} \u5904\u6539\u52A8${r.nonMonotonicOps.length > 0 ? " \xB7 \u542B\u9500\u6BC1\u6027" : ""}` } : {},
235351
235940
  actor: actorRef2(r.proposedBy),
235352
235941
  ...overdueField(r.at)
@@ -237118,6 +237707,16 @@ function effectivePoints(run) {
237118
237707
  }
237119
237708
  return points;
237120
237709
  }
237710
+ function ledgerRunModel(run) {
237711
+ const onRow = resolvedModel(run.effectiveModel);
237712
+ if (onRow) return onRow;
237713
+ let best = null;
237714
+ for (const p2 of run.points) {
237715
+ const m2 = resolvedModel(p2.model);
237716
+ if (m2 && (!best || p2.seq < best.seq)) best = { seq: p2.seq, model: m2 };
237717
+ }
237718
+ return best?.model ?? null;
237719
+ }
237121
237720
  function distribute(total, weights) {
237122
237721
  const sum = weights.reduce((s2, w2) => s2 + w2, 0);
237123
237722
  if (sum <= 0) return weights.map(() => 0);
@@ -237460,14 +238059,15 @@ function runtimeRows(facts, relations) {
237460
238059
  if (!v2.relevant) continue;
237461
238060
  const key2 = keyOf4(v2.run.runtimeInstanceId, v2.run.runtimeKind);
237462
238061
  getCell(cells, key2).addView(v2);
237463
- modelsOf(key2).cell(v2.run.effectiveModel).addView(v2);
238062
+ modelsOf(key2).cell(ledgerRunModel(v2.run)).addView(v2);
237464
238063
  }
237465
238064
  for (const r of facts.rounds) {
237466
238065
  const key2 = keyOf4(r.runtimeInstanceId, r.runtimeKind);
237467
238066
  const c = getCell(cells, key2);
237468
238067
  c.active = true;
237469
238068
  c.runCount += 1;
237470
- const m2 = modelsOf(key2).cell(runById.get(r.firstRunId)?.effectiveModel ?? null);
238069
+ const firstRun = runById.get(r.firstRunId);
238070
+ const m2 = modelsOf(key2).cell(firstRun ? ledgerRunModel(firstRun) : null);
237471
238071
  m2.active = true;
237472
238072
  m2.runCount += 1;
237473
238073
  }
@@ -241900,9 +242500,17 @@ function bucketLabelOf(slice, index2) {
241900
242500
  function runtimeUsageFromLedger(runtimeInstanceId, slice) {
241901
242501
  const rows = /* @__PURE__ */ new Map();
241902
242502
  const runById = new Map(slice.runs.map((r) => [r.id, r]));
242503
+ const modelByRun = /* @__PURE__ */ new Map();
242504
+ const modelOf = (run) => {
242505
+ const hit = modelByRun.get(run.id);
242506
+ if (hit !== void 0) return hit;
242507
+ const m2 = ledgerRunModel(run);
242508
+ modelByRun.set(run.id, m2);
242509
+ return m2;
242510
+ };
241903
242511
  const rowOf = (bucketIndex2, run) => {
241904
242512
  const bucket2 = bucketLabelOf(slice, bucketIndex2);
241905
- const model = resolvedModel(run.effectiveModel);
242513
+ const model = modelOf(run);
241906
242514
  const key2 = `${bucket2}\0${run.actorId}\0${model ?? ""}`;
241907
242515
  let row = rows.get(key2);
241908
242516
  if (!row) {
@@ -242898,11 +243506,11 @@ var init_roles = __esm({
242898
243506
  });
242899
243507
 
242900
243508
  // ../server/src/node-store.ts
242901
- var fs25, MemoryNodeStore, FileNodeStore;
243509
+ var fs26, MemoryNodeStore, FileNodeStore;
242902
243510
  var init_node_store = __esm({
242903
243511
  "../server/src/node-store.ts"() {
242904
243512
  "use strict";
242905
- fs25 = __toESM(require("node:fs"), 1);
243513
+ fs26 = __toESM(require("node:fs"), 1);
242906
243514
  MemoryNodeStore = class {
242907
243515
  nodes = /* @__PURE__ */ new Map();
242908
243516
  runtimes = /* @__PURE__ */ new Map();
@@ -243027,16 +243635,16 @@ var init_node_store = __esm({
243027
243635
  static async open(file) {
243028
243636
  const s2 = new _FileNodeStore(file);
243029
243637
  try {
243030
- const snap = JSON.parse(fs25.readFileSync(file, "utf8"));
243638
+ const snap = JSON.parse(fs26.readFileSync(file, "utf8"));
243031
243639
  for (const n of snap.nodes ?? []) s2.nodes.set(n.id, n);
243032
243640
  for (const r of snap.runtimes ?? []) s2.runtimes.set(r.id, r);
243033
243641
  } catch {
243034
- fs25.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
243642
+ fs26.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
243035
243643
  }
243036
243644
  return s2;
243037
243645
  }
243038
243646
  flush() {
243039
- fs25.writeFileSync(this.file, JSON.stringify({
243647
+ fs26.writeFileSync(this.file, JSON.stringify({
243040
243648
  nodes: [...this.nodes.values()],
243041
243649
  runtimes: [...this.runtimes.values()]
243042
243650
  }, null, 2));
@@ -243827,9 +244435,9 @@ function prepareReadonlyGitBundles(input) {
243827
244435
  if (repos.length === 0) return null;
243828
244436
  const targets = collectTargets(repos, input.pinnedRevisions);
243829
244437
  if (targets.length === 0) return null;
243830
- const bundleRoot = input.bundleRoot ?? os7.tmpdir();
243831
- fs26.mkdirSync(bundleRoot, { recursive: true });
243832
- const root = fs26.mkdtempSync(path24.join(bundleRoot, "oasis-git-bundles-"));
244438
+ const bundleRoot = input.bundleRoot ?? os8.tmpdir();
244439
+ fs27.mkdirSync(bundleRoot, { recursive: true });
244440
+ const root = fs27.mkdtempSync(path24.join(bundleRoot, "oasis-git-bundles-"));
243833
244441
  const files = {};
243834
244442
  const binaryFiles = {};
243835
244443
  const metas = [];
@@ -243837,7 +244445,7 @@ function prepareReadonlyGitBundles(input) {
243837
244445
  for (const target of targets) {
243838
244446
  const generated = createBundle(root, target, input);
243839
244447
  files[generated.metaPath] = JSON.stringify(generated.meta, null, 2) + "\n";
243840
- binaryFiles[generated.bundlePath] = fs26.readFileSync(generated.absoluteBundlePath).toString("base64");
244448
+ binaryFiles[generated.bundlePath] = fs27.readFileSync(generated.absoluteBundlePath).toString("base64");
243841
244449
  metas.push(generated.meta);
243842
244450
  }
243843
244451
  files["inputs/git-bundles/MANIFEST.json"] = JSON.stringify({
@@ -243858,10 +244466,10 @@ function prepareReadonlyGitBundles(input) {
243858
244466
  return {
243859
244467
  files,
243860
244468
  binaryFiles,
243861
- cleanup: () => fs26.rmSync(root, { recursive: true, force: true })
244469
+ cleanup: () => fs27.rmSync(root, { recursive: true, force: true })
243862
244470
  };
243863
244471
  } catch (err) {
243864
- fs26.rmSync(root, { recursive: true, force: true });
244472
+ fs27.rmSync(root, { recursive: true, force: true });
243865
244473
  throw err;
243866
244474
  }
243867
244475
  }
@@ -243920,7 +244528,7 @@ function createBundle(root, target, input) {
243920
244528
  if (!HEX40.test(commit)) throw new GitReadonlyBundleError("COMMIT_NOT_FOUND", `commit \u683C\u5F0F\u4E0D\u662F 40 \u4F4D sha: ${commit.slice(0, 48)}`);
243921
244529
  const safeRepo = repo.id.replace(/[^a-zA-Z0-9_-]+/g, "_");
243922
244530
  const work = path24.join(root, safeRepo);
243923
- fs26.mkdirSync(work, { recursive: true });
244531
+ fs27.mkdirSync(work, { recursive: true });
243924
244532
  const source = ensureSourceRepo(work, repo, commit, input.env);
243925
244533
  const tree = git2(source, ["rev-parse", `${commit}^{tree}`], input.env, "COMMIT_NOT_FOUND").trim();
243926
244534
  if (target.expectedTree && target.expectedTree.toLowerCase() !== tree.toLowerCase()) {
@@ -243947,8 +244555,8 @@ function createBundle(root, target, input) {
243947
244555
  } catch {
243948
244556
  }
243949
244557
  }
243950
- const bytes2 = fs26.statSync(absBundle).size;
243951
- const digest = (0, import_node_crypto62.createHash)("sha256").update(fs26.readFileSync(absBundle)).digest("hex");
244558
+ const bytes2 = fs27.statSync(absBundle).size;
244559
+ const digest = (0, import_node_crypto62.createHash)("sha256").update(fs27.readFileSync(absBundle)).digest("hex");
243952
244560
  const relBase = `inputs/git-bundles/${safeRepo}-${commit.slice(0, 12)}`;
243953
244561
  return {
243954
244562
  absoluteBundlePath: absBundle,
@@ -243972,7 +244580,7 @@ function createBundle(root, target, input) {
243972
244580
  };
243973
244581
  }
243974
244582
  function verifyBundleReadable(dir, bundlePath, _bundleRef, commit, expectedTree, env) {
243975
- fs26.rmSync(dir, { recursive: true, force: true });
244583
+ fs27.rmSync(dir, { recursive: true, force: true });
243976
244584
  try {
243977
244585
  (0, import_node_child_process16.execFileSync)("git", ["clone", bundlePath, dir], { stdio: "ignore", ...env ? { env } : {} });
243978
244586
  git2(dir, ["cat-file", "-e", `${commit}^{commit}`], env, "COMMIT_NOT_FOUND");
@@ -244014,8 +244622,8 @@ function ensureSourceRepo(work, repo, commit, env) {
244014
244622
  }
244015
244623
  }
244016
244624
  function gitInitBare(dir, env) {
244017
- if (fs26.existsSync(path24.join(dir, "objects"))) return;
244018
- fs26.mkdirSync(dir, { recursive: true });
244625
+ if (fs27.existsSync(path24.join(dir, "objects"))) return;
244626
+ fs27.mkdirSync(dir, { recursive: true });
244019
244627
  (0, import_node_child_process16.execFileSync)("git", ["init", "--bare", dir], { stdio: "ignore", ...env ? { env } : {} });
244020
244628
  }
244021
244629
  function git2(cwd, args, env, fallback, timeout) {
@@ -244044,14 +244652,14 @@ function classifyGitError(fallback, err, where) {
244044
244652
  function oneLine(msg) {
244045
244653
  return msg.split("\n").map((s2) => s2.trim()).filter(Boolean).slice(0, 3).join(" ");
244046
244654
  }
244047
- var import_node_child_process16, import_node_crypto62, fs26, os7, path24, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
244655
+ var import_node_child_process16, import_node_crypto62, fs27, os8, path24, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
244048
244656
  var init_readonly_bundle = __esm({
244049
244657
  "../server/src/git/readonly-bundle.ts"() {
244050
244658
  "use strict";
244051
244659
  import_node_child_process16 = require("node:child_process");
244052
244660
  import_node_crypto62 = require("node:crypto");
244053
- fs26 = __toESM(require("node:fs"), 1);
244054
- os7 = __toESM(require("node:os"), 1);
244661
+ fs27 = __toESM(require("node:fs"), 1);
244662
+ os8 = __toESM(require("node:os"), 1);
244055
244663
  path24 = __toESM(require("node:path"), 1);
244056
244664
  init_remote_util();
244057
244665
  GitReadonlyBundleError = class extends Error {
@@ -246776,6 +247384,19 @@ function createChatSessionsDomain(opts) {
246776
247384
  const binding = await bindingFor(companyId, aiActorId).catch(() => null);
246777
247385
  return binding?.nodeId ?? null;
246778
247386
  }
247387
+ async function agentDefaultRuntime(companyId, aiActorId) {
247388
+ const binding = await bindingFor(companyId, aiActorId).catch(() => null);
247389
+ let model = null;
247390
+ try {
247391
+ model = opts.resolveActorModel ? await opts.resolveActorModel(companyId, aiActorId) : (await registry2.latestConfig(aiActorId))?.model ?? null;
247392
+ } catch {
247393
+ model = null;
247394
+ }
247395
+ return {
247396
+ binding: binding ? { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind } : null,
247397
+ model: normalizeModel(model)
247398
+ };
247399
+ }
246779
247400
  function normalizeModel(raw) {
246780
247401
  return typeof raw === "string" && raw.trim() ? raw.trim() : null;
246781
247402
  }
@@ -246943,7 +247564,16 @@ function createChatSessionsDomain(opts) {
246943
247564
  if (isDeletedActor(aiActor)) {
246944
247565
  throw new ApiError(409, ACTOR_DELETED_ERROR_CODE, ACTOR_DELETED_CHAT_MESSAGE);
246945
247566
  }
246946
- const runtimeId = await currentRuntimeId(req.auth.companyId, body2.aiActorId) ?? "unknown";
247567
+ const agentDefault = await agentDefaultRuntime(req.auth.companyId, body2.aiActorId);
247568
+ const initial = chatSessionInitialRuntime({
247569
+ binding: agentDefault.binding,
247570
+ agentModel: agentDefault.model,
247571
+ requested: {
247572
+ override: normalizeOverride(body2.overrideNodeId) && normalizeOverride(body2.overrideRuntimeKind) ? { nodeId: normalizeOverride(body2.overrideNodeId), runtimeKind: normalizeOverride(body2.overrideRuntimeKind) } : null,
247573
+ ...Object.prototype.hasOwnProperty.call(body2, "model") ? { model: normalizeModel(body2.model) } : {}
247574
+ }
247575
+ });
247576
+ const runtimeId = agentDefault.binding?.nodeId ?? "unknown";
246947
247577
  const now = (/* @__PURE__ */ new Date()).toISOString();
246948
247578
  const session = {
246949
247579
  id: (0, import_node_crypto65.randomUUID)(),
@@ -246960,12 +247590,12 @@ function createChatSessionsDomain(opts) {
246960
247590
  // 两个入口页面行为一致。区别于 linkWorkOrder(出处关联,建单脑暴也有、不构成绑定)。
246961
247591
  workspace: sessionWorkspaceToStore(body2.workspace),
246962
247592
  projectId: sessionProjectIdToStore(body2.projectId),
246963
- // 会话级模型覆盖(口径 A):建会话时就带上选择;空串/空白归 null = 沿用旧解析,不写员工全局配置。
246964
- model: normalizeModel(body2.model),
246965
- /* 会话级 runtime 覆盖(ADR 0604):欢迎态在 chip 带上选好的节点/runtime 随 create 一起写进去
246966
- (与项目、模型同一刻、同一条路),不做「发完再 PATCH」。两列都为 null = 跟随员工 binding。 */
246967
- overrideNodeId: normalizeOverride(body2.overrideNodeId),
246968
- overrideRuntimeKind: normalizeOverride(body2.overrideRuntimeKind),
247593
+ /* 三列一起由 `chatSessionInitialRuntime` 给(ADR 0613):用户在欢迎态挑过就用他挑的,
247594
+ 没挑就抄员工此刻的默认运行配置。**员工没绑定 = 三列留空**,逐字回到 ADR 0604 的旧语义
247595
+ (跟随员工 binding),绑上之后那条路照旧管用。 */
247596
+ model: initial.model,
247597
+ overrideNodeId: initial.overrideNodeId,
247598
+ overrideRuntimeKind: initial.overrideRuntimeKind,
246969
247599
  // ADR-0058:分析会话指向被分析 run;非空即"这是分析会话"(不进主聊天列表、进 trace 分析视图)。
246970
247600
  analyzedRunId: body2.analyzedRunId ?? null
246971
247601
  };
@@ -247065,6 +247695,19 @@ function createChatSessionsDomain(opts) {
247065
247695
  });
247066
247696
  return { status: 200, body: await store2.getSession(req.params.id) };
247067
247697
  });
247698
+ router.post("/api/chat-sessions/:id/runtime/reset-to-agent-default", async (req) => {
247699
+ const store2 = await storeFor(req);
247700
+ const session = await store2.getSession(req.params.id);
247701
+ if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
247702
+ const agentDefault = await agentDefaultRuntime(req.auth.companyId, session.aiActorId);
247703
+ const initial = chatSessionInitialRuntime({ binding: agentDefault.binding, agentModel: agentDefault.model });
247704
+ await store2.updateSession(req.params.id, {
247705
+ overrideNodeId: initial.overrideNodeId,
247706
+ overrideRuntimeKind: initial.overrideRuntimeKind,
247707
+ model: initial.model
247708
+ });
247709
+ return { status: 200, body: await store2.getSession(req.params.id) };
247710
+ });
247068
247711
  router.delete("/api/chat-sessions/:id", async (req) => {
247069
247712
  const store2 = await storeFor(req);
247070
247713
  const session = await store2.getSession(req.params.id);
@@ -247226,6 +247869,14 @@ function createChatSessionsDomain(opts) {
247226
247869
  snapshotEntries.splice(at + 1, 0, entry);
247227
247870
  }
247228
247871
  }
247872
+ const outputCards = await sessionOutputCards(snapshotEntries, store2, chatSessionId).catch((e) => {
247873
+ console.warn(`[chat] \u4EA7\u51FA\u5361\u73B0\u62FC\u5931\u8D25\uFF08session=${chatSessionId}\uFF09\uFF0C\u8FD9\u6B21\u5FEB\u7167\u4E0D\u5E26\u5361: ${String(e)}`);
247874
+ return [];
247875
+ });
247876
+ for (const { after, entry } of outputCards) {
247877
+ const at = snapshotEntries.findIndex((e) => e.itemId === after);
247878
+ snapshotEntries.splice(at + 1, 0, entry);
247879
+ }
247229
247880
  const oldestOrd = snapshotEntries.find((e) => e.ord !== null)?.ord ?? null;
247230
247881
  const body2 = {
247231
247882
  chatSessionId,
@@ -247343,6 +247994,9 @@ function createChatSessionsDomain(opts) {
247343
247994
  ...expert?.avatar ? { expertAvatar: expert.avatar } : {},
247344
247995
  state: record9.state,
247345
247996
  ...record9.failureKind ? { failureKind: record9.failureKind } : {},
247997
+ /* 给人看的那一档由服务端算(Bug 0221):委派卡、徽章、子会话面板三处渲染同一个值。 */
247998
+ ending: delegationEndingOfRecord(record9, delegationArtifactsView(record9)),
247999
+ settledSeq: record9.settledSeq,
247346
248000
  ...record9.label ? { label: record9.label } : {},
247347
248001
  ...record9.taskDigest ? { taskDigest: record9.taskDigest } : {},
247348
248002
  parentMessageId: record9.parentMessageId,
@@ -247748,6 +248402,44 @@ function createChatSessionsDomain(opts) {
247748
248402
  });
247749
248403
  };
247750
248404
  }
248405
+ async function sessionOutputCards(entries, store, chatSessionId) {
248406
+ const lastEntryOf = /* @__PURE__ */ new Map();
248407
+ for (const e of entries) {
248408
+ if (e.role === "assistant" && e.messageId) lastEntryOf.set(e.messageId, e);
248409
+ }
248410
+ if (lastEntryOf.size === 0) return [];
248411
+ const ids2 = [...lastEntryOf.keys()];
248412
+ const rows = store.listMessageOutputParts ? await store.listMessageOutputParts(chatSessionId, ids2) : (await store.listMessages(chatSessionId)).filter((m2) => m2.role === "assistant" && lastEntryOf.has(m2.id)).map((m2) => ({ messageId: m2.id, outputs: Array.isArray(m2.parts) ? m2.parts.filter((p2) => p2?.type === "outputs") : [] }));
248413
+ const out = [];
248414
+ for (const { messageId, outputs } of rows) {
248415
+ const anchor = lastEntryOf.get(messageId);
248416
+ const files = sessionOutputFilesOfParts(outputs);
248417
+ if (!anchor || files.length === 0) continue;
248418
+ const anchorPayload = anchor.payload && typeof anchor.payload === "object" && !Array.isArray(anchor.payload) ? anchor.payload : {};
248419
+ out.push({
248420
+ after: anchor.itemId,
248421
+ entry: {
248422
+ itemId: sessionOutputsItemId(messageId),
248423
+ turnId: anchor.turnId,
248424
+ itemType: "control",
248425
+ role: "assistant",
248426
+ status: "completed",
248427
+ itemVersion: anchor.itemVersion,
248428
+ seq: anchor.seq,
248429
+ ord: anchor.ord,
248430
+ messageId,
248431
+ updatedAt: anchor.updatedAt,
248432
+ payload: {
248433
+ code: SESSION_OUTPUTS_ITEM_CODE,
248434
+ files: files.map((f2) => ({ path: f2.path, name: f2.name })),
248435
+ ...anchorPayload.createdAt !== void 0 ? { createdAt: anchorPayload.createdAt } : {},
248436
+ ...anchorPayload.completedAt !== void 0 ? { completedAt: anchorPayload.completedAt } : {}
248437
+ }
248438
+ }
248439
+ });
248440
+ }
248441
+ return out;
248442
+ }
247751
248443
  async function submissionCardProjection(entries, deps) {
247752
248444
  const out = { add: [], resolved: [] };
247753
248445
  const turns = deps.turns;
@@ -247906,6 +248598,7 @@ var init_chat_sessions2 = __esm({
247906
248598
  init_company_data_store_router();
247907
248599
  init_workorder_terminal();
247908
248600
  init_session_workdir();
248601
+ init_session_runtime_target();
247909
248602
  init_uncategorized_project();
247910
248603
  init_chat_submission();
247911
248604
  init_project_bundle();
@@ -251741,14 +252434,41 @@ function daemonMetaOf(hub, nodeId) {
251741
252434
  if (typeof connectedDaemons !== "function") return void 0;
251742
252435
  return connectedDaemons.call(hub).find((candidate) => candidate.daemonId === nodeId)?.meta;
251743
252436
  }
251744
- function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, confirm) {
252437
+ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, sessionReadyWaitMs, confirm) {
251745
252438
  const key2 = input.requireConsumptionReceipt ? input.clientMessageKey : void 0;
251746
- if (!key2 || !confirm) return sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs);
251747
- return sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs).then((result) => ({ ...result, receiptProbe: { nodeId, dispatchId, confirm: () => confirm(key2) } }));
252439
+ if (!key2 || !confirm) return sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, sessionReadyWaitMs);
252440
+ return sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, sessionReadyWaitMs).then((result) => ({ ...result, receiptProbe: { nodeId, dispatchId, confirm: () => confirm(key2) } }));
252441
+ }
252442
+ async function waitSessionReady(hub, dispatchId, entry, timeoutMs) {
252443
+ const deadline = Date.now() + timeoutMs;
252444
+ const ready = () => hub.hasSession?.(dispatchId) === true && entry.deliveryState === "started";
252445
+ while (!entry.exited && !ready()) {
252446
+ const left = deadline - Date.now();
252447
+ if (left <= 0) return;
252448
+ await new Promise((resolve11) => {
252449
+ let settled = false;
252450
+ const finish = () => {
252451
+ if (settled) return;
252452
+ settled = true;
252453
+ const i = entry.readyWaiters?.indexOf(finish) ?? -1;
252454
+ if (i >= 0) entry.readyWaiters.splice(i, 1);
252455
+ resolve11();
252456
+ };
252457
+ (entry.readyWaiters ??= []).push(finish);
252458
+ if (hub.hasSession?.(dispatchId) !== true) void hub.waitForSession(dispatchId, left).then(finish, finish);
252459
+ else {
252460
+ const t = setTimeout(finish, left);
252461
+ t.unref?.();
252462
+ }
252463
+ });
252464
+ }
251748
252465
  }
251749
- function sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
252466
+ function sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, sessionReadyWaitMs) {
251750
252467
  const unknownIfInterrupting = input.interrupt ? { interrupt: "unknown" } : {};
251751
252468
  if (entry.exited) return Promise.resolve({ accepted: false, reason: "session-closing", ...unknownIfInterrupting });
252469
+ if (input.requireConsumptionReceipt && sessionReadyWaitMs > 0 && (hub.hasSession?.(dispatchId) === false || entry.deliveryState !== "started") && typeof hub.waitForSession === "function" && daemonMetaOf(hub, nodeId)?.supportsInputReceiptQuery === true) {
252470
+ return waitSessionReady(hub, dispatchId, entry, sessionReadyWaitMs).then(() => sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, 0));
252471
+ }
251752
252472
  if (input.requireConsumptionReceipt && !hub.supportsInputConsumptionReceipts?.(dispatchId)) {
251753
252473
  return Promise.resolve({ accepted: false, reason: "consumption-unsupported" });
251754
252474
  }
@@ -251785,6 +252505,7 @@ var init_daemon_adapter = __esm({
251785
252505
  this.nodeLostGraceMs = opts.nodeLostGraceMs ?? graceFromEnv(12e4);
251786
252506
  this.ackTimeoutMs = opts.ackTimeoutMs ?? ackMsFromEnv(15e3);
251787
252507
  this.appendTimeoutMs = opts.appendTimeoutMs ?? 1e4;
252508
+ this.sessionReadyWaitMs = opts.sessionReadyWaitMs ?? 1e4;
251788
252509
  this.receiptQueryTimeoutMs = opts.receiptQueryTimeoutMs ?? 5e3;
251789
252510
  this.log = opts.log ?? ((m2) => console.warn(m2));
251790
252511
  this.health = opts.health;
@@ -251806,6 +252527,7 @@ var init_daemon_adapter = __esm({
251806
252527
  ackTimeoutMs;
251807
252528
  /** ADR-0093:append_input 回执时限;超时(老节点忽略帧)→ unsupported,上层降级 kill+resume。 */
251808
252529
  appendTimeoutMs;
252530
+ sessionReadyWaitMs;
251809
252531
  log;
251810
252532
  health;
251811
252533
  stashEnabled;
@@ -251958,6 +252680,7 @@ var init_daemon_adapter = __esm({
251958
252680
  clearTimeout(w2.timer);
251959
252681
  w2.resolve({ accepted: false, reason: w2.requireConsumptionReceipt ? "unconfirmed" : "session-closing" });
251960
252682
  }
252683
+ for (const w2 of entry.readyWaiters?.splice(0) ?? []) w2();
251961
252684
  if (info.reason === "server-unreachable") this.health?.recordUnreachable(entry.nodeId, info.reason);
251962
252685
  else this.health?.recordReachable(entry.nodeId);
251963
252686
  for (const cb of entry.exitCbs.splice(0)) cb(info);
@@ -252017,6 +252740,7 @@ var init_daemon_adapter = __esm({
252017
252740
  entry.ackTimer = void 0;
252018
252741
  }
252019
252742
  if (entry) entry.deliveryState = state;
252743
+ if (entry && state === "started") for (const w2 of entry.readyWaiters?.splice(0) ?? []) w2();
252020
252744
  }
252021
252745
  onNodeDown(daemonId) {
252022
252746
  if (this.reapTimers.has(daemonId)) return;
@@ -252225,6 +252949,7 @@ var init_daemon_adapter = __esm({
252225
252949
  }
252226
252950
  const hub = this.hub;
252227
252951
  const appendTimeoutMs = this.appendTimeoutMs;
252952
+ const sessionReadyWaitMs = this.sessionReadyWaitMs;
252228
252953
  const confirm = (key2) => this.confirmInputReceipt(nodeId, dispatchId, key2);
252229
252954
  return {
252230
252955
  id: dispatchId,
@@ -252258,7 +252983,7 @@ var init_daemon_adapter = __esm({
252258
252983
  } : {},
252259
252984
  // ADR-0093 对话追加:经 WS 把输入送到节点上的会话,等 append_result 回执;节点不认帧
252260
252985
  // (老节点)则超时 unsupported,上层降级 kill+resume。
252261
- appendInput: (input) => remoteAppendInput(hub, nodeId, dispatchId, entry, input, appendTimeoutMs, confirm)
252986
+ appendInput: (input) => remoteAppendInput(hub, nodeId, dispatchId, entry, input, appendTimeoutMs, sessionReadyWaitMs, confirm)
252262
252987
  };
252263
252988
  }
252264
252989
  recover(dispatchId, nodeId, runtimeKind, actorId) {
@@ -252316,6 +253041,7 @@ var init_daemon_adapter = __esm({
252316
253041
  };
252317
253042
  const hub = this.hub;
252318
253043
  const appendTimeoutMs = this.appendTimeoutMs;
253044
+ const sessionReadyWaitMs = this.sessionReadyWaitMs;
252319
253045
  const confirm = (key2) => this.confirmInputReceipt(nodeId, dispatchId, key2);
252320
253046
  return {
252321
253047
  id: dispatchId,
@@ -252355,7 +253081,7 @@ var init_daemon_adapter = __esm({
252355
253081
  }
252356
253082
  } : {},
252357
253083
  // ADR-0093 对话追加(重挂句柄同 spawn 句柄)。
252358
- appendInput: (input) => remoteAppendInput(hub, nodeId, dispatchId, entry, input, appendTimeoutMs, confirm)
253084
+ appendInput: (input) => remoteAppendInput(hub, nodeId, dispatchId, entry, input, appendTimeoutMs, sessionReadyWaitMs, confirm)
252359
253085
  };
252360
253086
  }
252361
253087
  };
@@ -252669,22 +253395,22 @@ var init_engine = __esm({
252669
253395
  });
252670
253396
 
252671
253397
  // ../server/src/mirror/fs-state.ts
252672
- var fs29, FsMirrorStateStore;
253398
+ var fs30, FsMirrorStateStore;
252673
253399
  var init_fs_state = __esm({
252674
253400
  "../server/src/mirror/fs-state.ts"() {
252675
253401
  "use strict";
252676
- fs29 = __toESM(require("node:fs"), 1);
253402
+ fs30 = __toESM(require("node:fs"), 1);
252677
253403
  FsMirrorStateStore = class {
252678
253404
  constructor(file) {
252679
253405
  this.file = file;
252680
- if (fs29.existsSync(file)) {
252681
- const obj2 = JSON.parse(fs29.readFileSync(file, "utf8"));
253406
+ if (fs30.existsSync(file)) {
253407
+ const obj2 = JSON.parse(fs30.readFileSync(file, "utf8"));
252682
253408
  for (const [k2, v2] of Object.entries(obj2)) this.map.set(k2, v2);
252683
253409
  }
252684
253410
  }
252685
253411
  map = /* @__PURE__ */ new Map();
252686
253412
  flush() {
252687
- fs29.writeFileSync(this.file, JSON.stringify(Object.fromEntries(this.map), null, 2));
253413
+ fs30.writeFileSync(this.file, JSON.stringify(Object.fromEntries(this.map), null, 2));
252688
253414
  }
252689
253415
  async get(artifactId) {
252690
253416
  return this.map.get(artifactId) ?? null;
@@ -258781,7 +259507,7 @@ var init_postgres_trace = __esm({
258781
259507
  */
258782
259508
  async listRunFailures(workOrderId) {
258783
259509
  const r = await this.pool.query(
258784
- `SELECT node_id, actor_id, error_message, COALESCE(started_at, created_at) AS at
259510
+ `SELECT node_id, error_message, COALESCE(started_at, created_at) AS at
258785
259511
  FROM ${this.s}.agent_runs
258786
259512
  WHERE work_order_id = $1 AND node_id IS NOT NULL
258787
259513
  AND error_message IS NOT NULL AND error_message <> ''
@@ -258790,7 +259516,6 @@ var init_postgres_trace = __esm({
258790
259516
  );
258791
259517
  return r.rows.map((row) => ({
258792
259518
  nodeId: row.node_id,
258793
- actorId: row.actor_id ?? null,
258794
259519
  startedAt: new Date(row.at).toISOString(),
258795
259520
  message: row.error_message
258796
259521
  }));
@@ -262064,6 +262789,34 @@ var init_postgres_chat_sessions = __esm({
262064
262789
  attachments: row.attachments ?? []
262065
262790
  }));
262066
262791
  }
262792
+ /**
262793
+ * `/items` 快照出产出卡的窄读(Bug 0217):按主键批量命中,在库里把 `parts` 展开、只挑 `type='outputs'`
262794
+ * 那几段聚回一个数组送出来——整轮过程时间线不出库。只看 assistant 行(outputs 段只在助手轮收尾写)。
262795
+ */
262796
+ async listMessageOutputParts(sessionId, messageIds) {
262797
+ if (messageIds.length === 0) return [];
262798
+ const r = await this.pool.query(
262799
+ `SELECT m.id, o.outputs
262800
+ FROM ${this.s}.chat_messages m
262801
+ JOIN ${this.s}.chat_sessions s ON s.id = m.session_id
262802
+ CROSS JOIN LATERAL (
262803
+ SELECT jsonb_agg(p ORDER BY i) AS outputs
262804
+ FROM jsonb_array_elements(CASE WHEN jsonb_typeof(m.parts) = 'array' THEN m.parts ELSE '[]'::jsonb END)
262805
+ WITH ORDINALITY AS e(p, i)
262806
+ WHERE p->>'type' = 'outputs'
262807
+ ) o
262808
+ WHERE m.id = ANY($1::text[])
262809
+ AND m.session_id = $2
262810
+ AND s.company_id = $3
262811
+ AND m.role = 'assistant'
262812
+ AND o.outputs IS NOT NULL`,
262813
+ [[...messageIds], sessionId, this.companyId]
262814
+ );
262815
+ return r.rows.map((row) => ({
262816
+ messageId: row.id,
262817
+ outputs: Array.isArray(row.outputs) ? row.outputs : []
262818
+ }));
262819
+ }
262067
262820
  /**
262068
262821
  * 末尾 N 轮视窗(详情读侧默认):先定位「从尾部数第 N 条 user 消息」的 seq 作为窗口起点,再取
262069
262822
  * seq >= 起点的全部消息。语义与内存版 windowMessagesByTurns 一致,但把裁剪下推到 SQL——长会话
@@ -263501,6 +264254,68 @@ var init_chat_items_ord_backfill = __esm({
263501
264254
  }
263502
264255
  });
263503
264256
 
264257
+ // ../storage/src/chat-session-runtime-pin-backfill.ts
264258
+ async function hasChatSessions(pool, schema) {
264259
+ const r = await pool.query(`SELECT to_regclass($1) IS NOT NULL AS ok`, [`${schema}.chat_sessions`]);
264260
+ return r.rows[0]?.ok === true;
264261
+ }
264262
+ async function pinChatSessionsToAgentDefault(pool, schema, opts) {
264263
+ const s2 = quoteIdent2(schema);
264264
+ const dryRun = opts.dryRun === true;
264265
+ const base = {
264266
+ schema,
264267
+ companyId: opts.companyId,
264268
+ scanned: 0,
264269
+ pinned: 0,
264270
+ leftUnbound: 0,
264271
+ unresolved: 0,
264272
+ dryRun
264273
+ };
264274
+ if (!await hasChatSessions(pool, schema)) return base;
264275
+ const pending = await pool.query(
264276
+ `SELECT ai_actor_id, count(*)::int AS n
264277
+ FROM ${s2}.chat_sessions
264278
+ WHERE company_id = $1
264279
+ AND (override_node_id IS NULL OR override_runtime_kind IS NULL)
264280
+ GROUP BY ai_actor_id`,
264281
+ [opts.companyId]
264282
+ );
264283
+ for (const row of pending.rows) {
264284
+ base.scanned += row.n;
264285
+ let binding;
264286
+ try {
264287
+ binding = await opts.resolveBinding(row.ai_actor_id);
264288
+ } catch {
264289
+ base.unresolved += row.n;
264290
+ continue;
264291
+ }
264292
+ if (!binding?.nodeId || !binding?.runtimeKind) {
264293
+ base.leftUnbound += row.n;
264294
+ continue;
264295
+ }
264296
+ if (dryRun) {
264297
+ base.pinned += row.n;
264298
+ continue;
264299
+ }
264300
+ const upd = await pool.query(
264301
+ `UPDATE ${s2}.chat_sessions
264302
+ SET override_node_id = $3, override_runtime_kind = $4
264303
+ WHERE company_id = $1
264304
+ AND ai_actor_id = $2
264305
+ AND (override_node_id IS NULL OR override_runtime_kind IS NULL)`,
264306
+ [opts.companyId, row.ai_actor_id, binding.nodeId, binding.runtimeKind]
264307
+ );
264308
+ base.pinned += upd.rowCount ?? 0;
264309
+ }
264310
+ return base;
264311
+ }
264312
+ var init_chat_session_runtime_pin_backfill = __esm({
264313
+ "../storage/src/chat-session-runtime-pin-backfill.ts"() {
264314
+ "use strict";
264315
+ init_pg_ident();
264316
+ }
264317
+ });
264318
+
263504
264319
  // ../storage/src/postgres-delegations.ts
263505
264320
  function toRecord2(row) {
263506
264321
  return {
@@ -267869,7 +268684,7 @@ var init_cutover_aware_trace = __esm({
267869
268684
  const s2 = await this.pick();
267870
268685
  return typeof s2.runtimeRunActivity === "function" ? s2.runtimeRunActivity(runtimeInstanceId) : null;
267871
268686
  }
267872
- /** 协调运行(任务动态协调者卡):落点 store 没实现就当没有这份数据,不出卡、不报错。 */
268687
+ /** 协调运行(协调者卡的轨迹入口):落点 store 没实现就当没有这份数据,卡照出、只是没图标,不报错。 */
267873
268688
  async listCoordinatorRuns(workOrderId) {
267874
268689
  const store = await this.pick();
267875
268690
  return await store.listCoordinatorRuns?.(workOrderId) ?? [];
@@ -268196,7 +269011,7 @@ var init_cutover_aware_trace = __esm({
268196
269011
  /**
268197
269012
  * 协调运行按工单扇出(任务动态协调者卡)。与 `FanOutDispatchLedger.listByWorkorder` 同理:
268198
269013
  * **一张工单只属于一家公司**,所以逐家查出来的并集恰好就是那一家的行;漏扇会让非默认公司的
268199
- * 协调者卡整片空白。某家查不动只记一笔,不让整块读失败。
269014
+ * 协调者卡全都点不进轨迹。某家查不动只记一笔,不让整块读失败。
268200
269015
  */
268201
269016
  async listCoordinatorRuns(workOrderId) {
268202
269017
  const out = [];
@@ -271148,7 +271963,8 @@ async function backfillUsageLedger(ledger, opts) {
271148
271963
  manifestId: opts.dryRun ? null : opts.manifestId,
271149
271964
  usagePoints: emptyStep(),
271150
271965
  runtimeStartedAt: emptyStep(),
271151
- chat: { ...emptyStep(), notFound: 0, ambiguous: 0, turnFilled: 0 }
271966
+ chat: { ...emptyStep(), notFound: 0, ambiguous: 0, turnFilled: 0 },
271967
+ claudeResume: null
271152
271968
  };
271153
271969
  const sample = (step, id) => {
271154
271970
  if (step.samples.length < sampleSize) step.samples.push(id);
@@ -271286,8 +272102,134 @@ async function backfillUsageLedger(ledger, opts) {
271286
272102
  }
271287
272103
  });
271288
272104
  }
272105
+ if (opts.claudeResumeBefore) {
272106
+ report.claudeResume = await fixClaudeResumeUsage(ledger.pool, s2, opts.claudeResumeBefore, {
272107
+ dryRun: opts.dryRun,
272108
+ manifestId: opts.manifestId,
272109
+ batch,
272110
+ sample: (step, id) => sample(step, id),
272111
+ now
272112
+ });
272113
+ }
271289
272114
  return report;
271290
272115
  }
272116
+ function claudeResumeNetUsage(raw) {
272117
+ const out = [];
272118
+ let prev = null;
272119
+ for (const u of raw) {
272120
+ const cost = reportedCost(u["costUsdMicros"]);
272121
+ if (cost === null) {
272122
+ out.push(u);
272123
+ continue;
272124
+ }
272125
+ const prevCost = prev ? reportedCost(prev["costUsdMicros"]) : null;
272126
+ const cumulative = prev !== null && prevCost !== null && cost >= prevCost && BUCKETS.every(([k2]) => usageBucket(u[k2]) >= usageBucket(prev[k2]));
272127
+ if (!cumulative) {
272128
+ out.push(u);
272129
+ prev = u;
272130
+ continue;
272131
+ }
272132
+ const input = usageBucket(u["inputTokens"]) - usageBucket(prev["inputTokens"]);
272133
+ const output = usageBucket(u["outputTokens"]) - usageBucket(prev["outputTokens"]);
272134
+ out.push({
272135
+ ...u,
272136
+ inputTokens: input,
272137
+ outputTokens: output,
272138
+ totalTokens: input + output,
272139
+ cacheReadTokens: usageBucket(u["cacheReadTokens"]) - usageBucket(prev["cacheReadTokens"]),
272140
+ cacheCreationTokens: usageBucket(u["cacheCreationTokens"]) - usageBucket(prev["cacheCreationTokens"]),
272141
+ costUsdMicros: cost - prevCost
272142
+ });
272143
+ prev = u;
272144
+ }
272145
+ return out;
272146
+ }
272147
+ async function fixClaudeResumeUsage(pool, s2, before, o) {
272148
+ const report = { ...emptyStep(), sessions: 0, costBefore: 0, costAfter: 0 };
272149
+ const audited = /* @__PURE__ */ new Map();
272150
+ const auditRows = await pool.query(
272151
+ `SELECT run_id, manifest_id, before, after FROM ${s2}.agent_run_usage_backfill_audit
272152
+ WHERE field = 'claude_resume_usage' ORDER BY id`
272153
+ );
272154
+ for (const a of auditRows.rows) {
272155
+ audited.set(a.run_id, { manifest: a.manifest_id, before: a.before, after: a.after });
272156
+ }
272157
+ const { rows } = await pool.query(
272158
+ `SELECT r.id, r.usage, COALESCE(r.runtime_session_id, r.metadata->>'runtimeSessionId') AS rt
272159
+ FROM ${s2}.agent_runs r
272160
+ WHERE r.runtime_kind = 'claude-code' AND ${ENDED_SQL} AND jsonb_typeof(r.usage) = 'object'
272161
+ AND COALESCE(r.runtime_session_id, r.metadata->>'runtimeSessionId') IS NOT NULL
272162
+ AND r.started_at < $1
272163
+ ORDER BY rt, r.started_at, r.id`,
272164
+ [before]
272165
+ );
272166
+ const chains = /* @__PURE__ */ new Map();
272167
+ for (const row of rows) {
272168
+ const current = row.usage;
272169
+ let raw = current;
272170
+ const last = audited.get(row.id);
272171
+ if (last && !last.manifest.endsWith(":undo")) {
272172
+ const after = last.after?.usage;
272173
+ const prior = last.before?.usage;
272174
+ if (after && prior && sameUsage(after, current)) raw = prior;
272175
+ }
272176
+ const rt = row.rt;
272177
+ const chain = chains.get(rt) ?? [];
272178
+ chains.set(rt, chain);
272179
+ chain.push({ id: row.id, current, raw });
272180
+ }
272181
+ report.sessions = chains.size;
272182
+ const plan = [];
272183
+ for (const chain of chains.values()) {
272184
+ const net = claudeResumeNetUsage(chain.map((r) => r.raw));
272185
+ chain.forEach((r, i) => {
272186
+ if (sameUsage(net[i], r.current)) return;
272187
+ plan.push({ id: r.id, current: r.current, next: net[i] });
272188
+ });
272189
+ }
272190
+ report.candidates = plan.length;
272191
+ for (const p2 of plan) {
272192
+ o.sample(report, p2.id);
272193
+ report.costBefore += reportedCost(p2.current["costUsdMicros"]) ?? 0;
272194
+ report.costAfter += reportedCost(p2.next["costUsdMicros"]) ?? 0;
272195
+ }
272196
+ if (o.dryRun) return report;
272197
+ for (let i = 0; i < plan.length; i += o.batch) {
272198
+ const slice = plan.slice(i, i + o.batch);
272199
+ await inTx(pool, async (c) => {
272200
+ const at = o.now();
272201
+ for (const p2 of slice) {
272202
+ const r = await c.query(
272203
+ `UPDATE ${s2}.agent_runs SET usage = $2::jsonb WHERE id = $1 AND usage = $3::jsonb RETURNING id`,
272204
+ [p2.id, JSON.stringify(p2.next), JSON.stringify(p2.current)]
272205
+ );
272206
+ if (!r.rowCount) continue;
272207
+ await setFinalPoint(c, s2, p2.id, p2.next);
272208
+ report.changed += 1;
272209
+ await audit(
272210
+ c,
272211
+ s2,
272212
+ o.manifestId,
272213
+ p2.id,
272214
+ "claude_resume_usage",
272215
+ { usage: p2.current },
272216
+ { usage: p2.next },
272217
+ at
272218
+ );
272219
+ }
272220
+ });
272221
+ }
272222
+ return report;
272223
+ }
272224
+ async function setFinalPoint(c, s2, runId, usage) {
272225
+ const f2 = fourOf(usage);
272226
+ await c.query(
272227
+ `UPDATE ${s2}.agent_run_usage_points
272228
+ SET input_tokens = $3, output_tokens = $4, cache_read_tokens = $5, cache_creation_tokens = $6, cost_usd_micros = $7
272229
+ WHERE run_id = $1 AND seq = $2`,
272230
+ [runId, USAGE_POINT_FINAL_SEQ, f2.inputTokens, f2.outputTokens, f2.cacheReadTokens, f2.cacheCreationTokens, reportedCost(usage["costUsdMicros"])]
272231
+ );
272232
+ }
271291
272233
  function usageLedgerViolationCount(r) {
271292
272234
  return Object.values(r.violations).reduce((n, ids2) => n + ids2.length, 0);
271293
272235
  }
@@ -271381,6 +272323,17 @@ async function undoUsageLedgerBackfill(ledger, manifestId, opts = {}) {
271381
272323
  } else if (before !== null) {
271382
272324
  ok3 = await insertPoint(c, s2, runId, before);
271383
272325
  }
272326
+ } else if (field === "claude_resume_usage") {
272327
+ const prior = before?.usage;
272328
+ const cur = after?.usage;
272329
+ if (prior && cur) {
272330
+ const r = await c.query(
272331
+ `UPDATE ${s2}.agent_runs SET usage = $2::jsonb WHERE id = $1 AND usage = $3::jsonb RETURNING id`,
272332
+ [runId, JSON.stringify(prior), JSON.stringify(cur)]
272333
+ );
272334
+ ok3 = (r.rowCount ?? 0) > 0;
272335
+ if (ok3) await setFinalPoint(c, s2, runId, prior);
272336
+ }
271384
272337
  } else if (field === "runtime_started_at" || field === "chat_session_id" || field === "chat_turn_id") {
271385
272338
  const r = await c.query(
271386
272339
  `UPDATE ${s2}.agent_runs SET ${field} = $2 WHERE id = $1 AND ${field} IS NOT DISTINCT FROM $3`,
@@ -271429,7 +272382,7 @@ async function audit(c, s2, manifestId, runId, field, before, after, createdAt)
271429
272382
  [manifestId, runId, field, before == null ? null : JSON.stringify(before), after == null ? null : JSON.stringify(after), createdAt]
271430
272383
  );
271431
272384
  }
271432
- var import_node_crypto82, BUCKETS, ENDED_SQL, START_EVIDENCE_SQL, CHAT_RUN_SQL, emptyStep, ChatRunLookup;
272385
+ var import_node_crypto82, BUCKETS, ENDED_SQL, START_EVIDENCE_SQL, CHAT_RUN_SQL, emptyStep, fourOf, sameUsage, ChatRunLookup;
271433
272386
  var init_usage_ledger_backfill = __esm({
271434
272387
  "../storage/src/usage-ledger-backfill.ts"() {
271435
272388
  "use strict";
@@ -271452,6 +272405,13 @@ var init_usage_ledger_backfill = __esm({
271452
272405
  )`;
271453
272406
  CHAT_RUN_SQL = `r.id LIKE 'chat-run:%'`;
271454
272407
  emptyStep = () => ({ candidates: 0, changed: 0, samples: [] });
272408
+ fourOf = (u) => ({
272409
+ inputTokens: usageBucket(u["inputTokens"]),
272410
+ outputTokens: usageBucket(u["outputTokens"]),
272411
+ cacheReadTokens: usageBucket(u["cacheReadTokens"]),
272412
+ cacheCreationTokens: usageBucket(u["cacheCreationTokens"])
272413
+ });
272414
+ sameUsage = (a, b2) => BUCKETS.every(([k2]) => usageBucket(a[k2]) === usageBucket(b2[k2])) && reportedCost(a["costUsdMicros"]) === reportedCost(b2["costUsdMicros"]);
271455
272415
  ChatRunLookup = class {
271456
272416
  constructor(sources) {
271457
272417
  this.sources = sources;
@@ -271603,6 +272563,7 @@ __export(src_exports, {
271603
272563
  buildTrigger: () => buildTrigger,
271604
272564
  buildWorkorderIndex: () => buildWorkorderIndex,
271605
272565
  canonicalTypeDefHash: () => canonicalTypeDefHash,
272566
+ claudeResumeNetUsage: () => claudeResumeNetUsage,
271606
272567
  companySchemaName: () => companySchemaName,
271607
272568
  copyActorMemoriesIdempotent: () => copyActorMemoriesIdempotent,
271608
272569
  copyActorsIdempotent: () => copyActorsIdempotent,
@@ -271640,6 +272601,7 @@ __export(src_exports, {
271640
272601
  newUsageBackfillManifestId: () => newUsageBackfillManifestId,
271641
272602
  openUsageLedgerForBackfill: () => openUsageLedgerForBackfill,
271642
272603
  payloadChecksum: () => payloadChecksum,
272604
+ pinChatSessionsToAgentDefault: () => pinChatSessionsToAgentDefault,
271643
272605
  planSessionOrd: () => planSessionOrd,
271644
272606
  provisionCompanyDataPlaneSchema: () => provisionCompanyDataPlaneSchema,
271645
272607
  provisionNewCompanyDataPlane: () => provisionNewCompanyDataPlane,
@@ -271685,6 +272647,7 @@ var init_src10 = __esm({
271685
272647
  init_postgres_chat_turns();
271686
272648
  init_postgres_chat_items();
271687
272649
  init_chat_items_ord_backfill();
272650
+ init_chat_session_runtime_pin_backfill();
271688
272651
  init_postgres_delegations();
271689
272652
  init_postgres_nodes();
271690
272653
  init_postgres_model_prices();
@@ -273013,11 +273976,13 @@ __export(src_exports2, {
273013
273976
  chatBroadcastEnabledFromEnv: () => chatBroadcastEnabledFromEnv,
273014
273977
  chatDelegationEnabledFromEnv: () => chatDelegationEnabledFromEnv,
273015
273978
  chatDispatchScope: () => chatDispatchScope,
273979
+ chatExitModelFields: () => chatExitModelFields,
273016
273980
  chatInterruptCauseOf: () => chatInterruptCauseOf,
273017
273981
  chatInterruptItemId: () => chatInterruptItemId,
273018
273982
  chatItemsDoubleWriteEnabled: () => chatItemsDoubleWriteEnabled,
273019
273983
  chatNodeChanged: () => chatNodeChanged,
273020
273984
  chatRunLedgerFields: () => chatRunLedgerFields,
273985
+ chatSessionInitialRuntime: () => chatSessionInitialRuntime,
273021
273986
  childRoundMessageIds: () => childRoundMessageIds,
273022
273987
  clampObservedAt: () => clampObservedAt,
273023
273988
  classifyPage: () => classifyPage,
@@ -273677,7 +274642,23 @@ function parseUsageLedgerBackfillArgs(flags) {
273677
274642
  if (modes.length !== 1) {
273678
274643
  throw new UsageLedgerBackfillArgError("--dry-run / --execute / --verify / --undo \u5FC5\u987B\u4E14\u53EA\u80FD\u9009\u4E00\u4E2A");
273679
274644
  }
273680
- return { scope: company !== void 0 ? { companyId: company } : { all: true }, mode: modes[0] };
274645
+ const mode = modes[0];
274646
+ let claudeResumeBefore;
274647
+ if (flags.has("claude-resume-before")) {
274648
+ const raw = flags.get("claude-resume-before");
274649
+ if (raw === "" || raw === "true" || Number.isNaN(Date.parse(raw))) {
274650
+ throw new UsageLedgerBackfillArgError("--claude-resume-before \u9700\u8981 ISO \u65F6\u523B\uFF08\u5982 2026-09-23T00:00:00Z\uFF09\uFF1A\u4FEE\u597D\u7684\u8282\u70B9\u5305\u5728\u5168\u90E8\u8282\u70B9\u88C5\u4E0A\u7684\u65F6\u523B");
274651
+ }
274652
+ if (mode.kind !== "dry-run" && mode.kind !== "execute") {
274653
+ throw new UsageLedgerBackfillArgError("--claude-resume-before \u53EA\u80FD\u914D --dry-run \u6216 --execute\uFF08\u64A4\u9500\u7167\u5E38\u7528 --undo\uFF09");
274654
+ }
274655
+ claudeResumeBefore = new Date(raw).toISOString();
274656
+ }
274657
+ return {
274658
+ scope: company !== void 0 ? { companyId: company } : { all: true },
274659
+ mode,
274660
+ ...claudeResumeBefore ? { claudeResumeBefore } : {}
274661
+ };
273681
274662
  }
273682
274663
  async function runUsageLedgerBackfill(flags, io) {
273683
274664
  let args;
@@ -273719,7 +274700,11 @@ ${USAGE_LEDGER_BACKFILL_USAGE}`);
273719
274700
  const dryRun = args.mode.kind === "dry-run";
273720
274701
  const manifestId2 = dryRun ? void 0 : storage.newUsageBackfillManifestId();
273721
274702
  for (const l of ledgers) {
273722
- const r = await storage.backfillUsageLedger(l, { dryRun, ...manifestId2 ? { manifestId: manifestId2 } : {} });
274703
+ const r = await storage.backfillUsageLedger(l, {
274704
+ dryRun,
274705
+ ...manifestId2 ? { manifestId: manifestId2 } : {},
274706
+ ...args.claudeResumeBefore ? { claudeResumeBefore: args.claudeResumeBefore } : {}
274707
+ });
273723
274708
  const n = (step) => dryRun ? `\u5F85\u6539 ${step.candidates}` : `\u5DF2\u6539 ${step.changed}/${step.candidates}`;
273724
274709
  io.out(`\u2014 \u8D26\u672C ${r.label}\uFF08schema=${r.schema}\uFF09`);
273725
274710
  io.out(` 1 \u7528\u91CF\u70B9(backfill)\uFF1A${n(r.usagePoints)}${samples(r.usagePoints.samples)}`);
@@ -273727,6 +274712,12 @@ ${USAGE_LEDGER_BACKFILL_USAGE}`);
273727
274712
  io.out(
273728
274713
  ` 3 \u4F1A\u8BDD/\u8F6E\u6B21\uFF1A${n(r.chat)}\uFF08\u5176\u4E2D\u8865\u8F6E\u6B21 ${r.chat.turnFilled}\uFF1B\u67E5\u4E0D\u5230 ${r.chat.notFound}\u3001\u6B67\u4E49 ${r.chat.ambiguous}\uFF0C\u4FDD\u6301 NULL\uFF09` + samples(r.chat.samples)
273729
274714
  );
274715
+ if (r.claudeResume) {
274716
+ const c = r.claudeResume;
274717
+ io.out(
274718
+ ` 4 Claude \u7EED\u8DD1\u7D2F\u8BA1\u503C\u6263\u56DE\uFF08\u5F00\u8DD1\u65E9\u4E8E ${args.claudeResumeBefore}\uFF0C${c.sessions} \u6761\u4F1A\u8BDD\u7EBF\uFF09\uFF1A${n(c)}\uFF1B\u8FD9\u4E9B run \u7684\u8D39\u7528\u5408\u8BA1 $${(c.costBefore / 1e6).toFixed(2)} \u2192 $${(c.costAfter / 1e6).toFixed(2)}` + samples(c.samples)
274719
+ );
274720
+ }
273730
274721
  }
273731
274722
  if (dryRun) io.out("[usage-ledger-backfill] dry-run\uFF1A\u4E00\u884C\u672A\u5199\u3002\u4E0B\u4E00\u6B65 --execute\u3002");
273732
274723
  else {
@@ -273774,7 +274765,7 @@ var USAGE_LEDGER_BACKFILL_USAGE, UsageLedgerBackfillArgError, MAX_LISTED;
273774
274765
  var init_usage_ledger_backfill_command = __esm({
273775
274766
  "../cli/src/usage-ledger-backfill-command.ts"() {
273776
274767
  "use strict";
273777
- USAGE_LEDGER_BACKFILL_USAGE = "\u7528\u6CD5\uFF1Aoasis usage-ledger-backfill (--company <id> | --all) (--dry-run | --execute | --verify | --undo <manifestId>)";
274768
+ USAGE_LEDGER_BACKFILL_USAGE = "\u7528\u6CD5\uFF1Aoasis usage-ledger-backfill (--company <id> | --all) (--dry-run | --execute | --verify | --undo <manifestId>) [--claude-resume-before <ISO \u65F6\u523B>]";
273778
274769
  UsageLedgerBackfillArgError = class extends Error {
273779
274770
  };
273780
274771
  MAX_LISTED = 50;
@@ -273784,7 +274775,7 @@ var init_usage_ledger_backfill_command = __esm({
273784
274775
  // src/index.ts
273785
274776
  var fs48 = __toESM(require("node:fs"));
273786
274777
  init_src();
273787
- var os14 = __toESM(require("node:os"));
274778
+ var os15 = __toESM(require("node:os"));
273788
274779
  var path43 = __toESM(require("node:path"));
273789
274780
  var import_node_child_process24 = require("node:child_process");
273790
274781
  var import_node_crypto93 = require("node:crypto");
@@ -273792,7 +274783,7 @@ var import_node_crypto93 = require("node:crypto");
273792
274783
  // ../cli/src/cli.ts
273793
274784
  init_src();
273794
274785
  var fs42 = __toESM(require("node:fs"), 1);
273795
- var os12 = __toESM(require("node:os"), 1);
274786
+ var os13 = __toESM(require("node:os"), 1);
273796
274787
  var path37 = __toESM(require("node:path"), 1);
273797
274788
  var import_node_crypto91 = require("node:crypto");
273798
274789
 
@@ -273802,7 +274793,7 @@ init_chat_cancel_attribution();
273802
274793
  init_src();
273803
274794
  init_src11();
273804
274795
  var fs34 = __toESM(require("node:fs"), 1);
273805
- var os8 = __toESM(require("node:os"), 1);
274796
+ var os9 = __toESM(require("node:os"), 1);
273806
274797
  var path28 = __toESM(require("node:path"), 1);
273807
274798
  var import_node_child_process17 = require("node:child_process");
273808
274799
  var import_node_crypto87 = require("node:crypto");
@@ -275772,132 +276763,7 @@ init_src11();
275772
276763
  init_src4();
275773
276764
 
275774
276765
  // ../cli/src/chat-exit.ts
275775
- var fs31 = __toESM(require("node:fs"), 1);
275776
- init_src();
275777
- init_src7();
275778
- init_chat_cancel_attribution();
275779
- function transcriptTail(ref2) {
275780
- try {
275781
- const stat4 = fs31.statSync(ref2);
275782
- const size = Math.min(stat4.size, 4e3);
275783
- const buf = Buffer.alloc(size);
275784
- const fd = fs31.openSync(ref2, "r");
275785
- try {
275786
- fs31.readSync(fd, buf, 0, size, Math.max(0, stat4.size - size));
275787
- } finally {
275788
- fs31.closeSync(fd);
275789
- }
275790
- return buf.toString("utf8").trim();
275791
- } catch {
275792
- return "";
275793
- }
275794
- }
275795
- var SUMMARY_MAX_CHARS = 500;
275796
- function clipLine(line2) {
275797
- return line2.length > SUMMARY_MAX_CHARS ? `${line2.slice(0, SUMMARY_MAX_CHARS)}\u2026` : line2;
275798
- }
275799
- function providerMessage(line2) {
275800
- const m2 = /['"]message['"]\s*:\s*(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")/.exec(line2);
275801
- const text7 = (m2?.[1] ?? m2?.[2])?.trim();
275802
- return text7 ? text7 : void 0;
275803
- }
275804
- var INTERNAL_PREFIX_RE = /^\[[a-z][a-z0-9_]*\]\s*/;
275805
- var QUOTA_RE3 = /hit your (?:\w+ )?limit|usage limit|quota|insufficient[_ ](?:balance|credit|funds)|credit balance|billing|\b402\b|额度|余额/i;
275806
- var RATE_LIMIT_RE = /rate.?limit|too many requests|\b429\b|\b529\b|overloaded|限流/i;
275807
- var AUTH_RE2 = /\b401\b|\b403\b|unauthori[sz]ed|authenticat|oauth|invalid[_ -]?(?:api[_ -]?key|token|x-api-key)|api[_ -]?key[^\n]{0,40}\b(?:invalid|expired|revoked)|token[^\n]{0,30}\b(?:expired|invalid|revoked)|not logged in|\/login|credential|凭证|登录/i;
275808
- var MODEL_RE = /\bmodel\b[^\n]{0,120}?\b(?:not found|not exist|does not exist|not available|not supported|unavailable|unknown)\b|\b(?:unknown|invalid|unsupported) model\b|model_not_found/i;
275809
- var DISK_FULL_RE = /\bENOSPC\b|no space left on device|\bEDQUOT\b|disk quota exceeded|\[Errno 28\]|disk (?:is )?full|磁盘(?:空间)?(?:已满|不足)/i;
275810
- var UNREACHABLE_RE2 = /不在线|socket 非 OPEN|帧未送达|失联/;
275811
- function failureKind(reason, line2) {
275812
- if (line2.startsWith(SESSION_STARTUP_FAILED_PREFIX)) return "startup-failed";
275813
- switch (reason) {
275814
- case "auth-error":
275815
- return "auth";
275816
- case "server-unreachable":
275817
- return "node-unreachable";
275818
- case "timeout":
275819
- return "timeout";
275820
- case "output-limit":
275821
- return "output-limit";
275822
- case "rate-limit":
275823
- return QUOTA_RE3.test(line2) ? "quota" : "rate-limit";
275824
- default:
275825
- break;
275826
- }
275827
- if (!line2) return "unknown";
275828
- if (DISK_FULL_RE.test(line2)) return "disk-full";
275829
- if (QUOTA_RE3.test(line2)) return "quota";
275830
- if (AUTH_RE2.test(line2)) return "auth";
275831
- if (RATE_LIMIT_RE.test(line2)) return "rate-limit";
275832
- if (MODEL_RE.test(line2)) return "model";
275833
- if (UNREACHABLE_RE2.test(line2)) return "node-unreachable";
275834
- return "unknown";
275835
- }
275836
- var chatRebindHint = (what) => `\u5728\u7EC4\u7EC7\u9875\u8FD9\u4F4D Agent \u7684\u300C\u6A21\u578B\u300D\u9875\u7B7E\u91CC${what}`;
275837
- function resetTime(line2) {
275838
- return extractResetHint(line2)?.replace(/^(?:resets?|try again at)\s+/i, "").trim() || void 0;
275839
- }
275840
- function failureSummary(kind, ctx) {
275841
- switch (kind) {
275842
- case "auth":
275843
- return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u7684\u767B\u5F55\u5DF2\u8FC7\u671F\u6216\u51ED\u8BC1\u65E0\u6548\uFF0C\u8C03\u7528\u4E0D\u4E86\u6A21\u578B\u3002\u8BF7\u8282\u70B9\u7BA1\u7406\u5458\u5728\u90A3\u53F0\u673A\u5668\u4E0A\u91CD\u65B0\u767B\u5F55\u8FD9\u4E2A\u8FD0\u884C\u65F6\uFF08\u6216\u66F4\u65B0 API Key\uFF09\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u7740\u6025\u7684\u8BDD\u4E5F\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u53EF\u7528\u7684\u8FD0\u884C\u65F6")}\u3002`;
275844
- case "quota": {
275845
- const at = resetTime(ctx.line);
275846
- return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u6A21\u578B\u989D\u5EA6\u5DF2\u7528\u5B8C${at ? `\uFF0C\u9884\u8BA1 ${at} \u6062\u590D` : ""}\u3002\u8BF7\u7B49\u989D\u5EA6\u6062\u590D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u7740\u6025\u7684\u8BDD\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u989D\u5EA6\u5145\u8DB3\u7684\u8FD0\u884C\u65F6")}\u3002`;
275847
- }
275848
- case "rate-limit":
275849
- return "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u6A21\u578B\u670D\u52A1\u6682\u65F6\u9650\u6D41\u6216\u7E41\u5FD9\u3002\u8BF7\u7B49\u4E00\u4E24\u5206\u949F\u518D\u53D1\u4E00\u6B21\u3002";
275850
- case "node-unreachable":
275851
- return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u8FD9\u4F4D Agent \u7684\u8282\u70B9\u79BB\u7EBF\u6216\u8FDE\u63A5\u4E2D\u65AD\u3002\u8BF7\u786E\u8BA4\u90A3\u53F0\u673A\u5668\u5F00\u7740\u3001\u8282\u70B9\u7A0B\u5E8F\u5728\u8FD0\u884C\u3001\u7F51\u7EDC\u6B63\u5E38\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u4E5F\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u5728\u7EBF\u7684\u8FD0\u884C\u65F6")}\u3002`;
275852
- case "model":
275853
- return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD9\u4E2A\u8FD0\u884C\u65F6\u7528\u4E0D\u4E86\u5F53\u524D\u914D\u7F6E\u7684\u6A21\u578B\u3002\u8BF7${chatRebindHint("\u6362\u4E00\u4E2A\u53EF\u7528\u7684\u6A21\u578B")}\u540E\u518D\u53D1\u4E00\u6B21\u3002`;
275854
- case "timeout":
275855
- return "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD9\u4E00\u8F6E\u8FD0\u884C\u8D85\u8FC7\u4E86\u65F6\u95F4\u4E0A\u9650\uFF0C\u5DF2\u88AB\u7EC8\u6B62\u3002\u53EF\u4EE5\u628A\u4EFB\u52A1\u62C6\u5C0F\u4E00\u4E9B\u518D\u53D1\u3002";
275856
- case "startup-failed":
275857
- return `\u5BF9\u8BDD\u5931\u8D25\uFF1A${ctx.readable || SESSION_STARTUP_FAILED_PREFIX}\u3002\u8FD9\u4E00\u8F6E\u6CA1\u6709\u5F00\u59CB\u8FD0\u884C\uFF0C\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u591A\u534A\u662F\u90A3\u53F0\u8282\u70B9\u8D1F\u8F7D\u8FC7\u9AD8\uFF0C\u8BF7\u7BA1\u7406\u5458\u7ED9\u5B83\u51CF\u8D1F\u6216${chatRebindHint("\u6362\u4E00\u4E2A\u8FD0\u884C\u65F6")}\u3002`;
275858
- case "disk-full":
275859
- return `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u8FD9\u4F4D Agent \u7684\u8282\u70B9\u78C1\u76D8\u7A7A\u95F4\u6EE1\u4E86\uFF0C\u8FD9\u4E00\u8F6E\u5199\u4E0D\u4E86\u6587\u4EF6\u3002\u8BF7\u8282\u70B9\u7BA1\u7406\u5458\u6E05\u7406\u90A3\u53F0\u673A\u5668\u7684\u78C1\u76D8\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u7740\u6025\u7684\u8BDD\u4E5F\u53EF\u4EE5${chatRebindHint("\u6362\u4E00\u4E2A\u8FD0\u884C\u65F6")}\u3002`;
275860
- case "output-limit":
275861
- return "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u6A21\u578B\u8F93\u51FA\u8D85\u51FA\u957F\u5EA6\u4E0A\u9650\uFF0C\u56DE\u590D\u88AB\u622A\u65AD\u4E86\u3002\u53EF\u4EE5\u8BA9\u5B83\u5206\u51E0\u6B21\u8F93\u51FA\uFF0C\u6216\u628A\u4EFB\u52A1\u62C6\u5C0F\u4E00\u4E9B\u518D\u53D1\u3002";
275862
- case "unknown":
275863
- if (ctx.line) {
275864
- return "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u62A5\u9519\u4E86\uFF0C\u6682\u65F6\u6CA1\u8BA4\u51FA\u662F\u4EC0\u4E48\u539F\u56E0\u3002\u53EF\u4EE5\u7A0D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u8BF7\u70B9\u300C\u5C55\u5F00\u8BE6\u60C5\u300D\u628A\u539F\u59CB\u62A5\u9519\u53D1\u7ED9\u7BA1\u7406\u5458\u3002";
275865
- }
275866
- return ctx.hasDetail ? "\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u5F02\u5E38\u9000\u51FA\uFF0C\u6CA1\u6709\u7ED9\u51FA\u62A5\u9519\u539F\u56E0\u3002\u53EF\u4EE5\u7A0D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u8BF7\u70B9\u300C\u5C55\u5F00\u8BE6\u60C5\u300D\u628A\u539F\u59CB\u8BB0\u5F55\u53D1\u7ED9\u7BA1\u7406\u5458\u3002" : `\u5BF9\u8BDD\u5931\u8D25\uFF1A\u8FD0\u884C\u65F6\u5F02\u5E38\u9000\u51FA\uFF08\u9000\u51FA\u7801 ${ctx.code ?? "\u65E0"}\uFF09\uFF0C\u6CA1\u6709\u7559\u4E0B\u62A5\u9519\u4FE1\u606F\u3002\u53EF\u4EE5\u7A0D\u540E\u518D\u53D1\u4E00\u6B21\uFF1B\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\uFF0C\u8BF7\u7BA1\u7406\u5458\u5230\u300C\u8FD0\u884C\u8F68\u8FF9\u300D\u91CC\u67E5\u8FD9\u4E00\u8F6E\u3002`;
275867
- }
275868
- }
275869
- function buildChatExitError(info) {
275870
- const genericLine = `runtime exited with code ${info.code ?? "null"}${info.reason ? ` (${info.reason})` : ""}`;
275871
- let detail = "";
275872
- if (info.reason !== "cancelled") {
275873
- detail = info.errorMessage?.trim() ?? "";
275874
- if (!detail && info.transcriptRef) detail = transcriptTail(info.transcriptRef);
275875
- }
275876
- let summary;
275877
- let cancelledBy;
275878
- if (info.reason === "cancelled") {
275879
- cancelledBy = cancelCauseFromExitMessage(info.errorMessage);
275880
- summary = chatCancelNote(cancelledBy);
275881
- } else {
275882
- const firstErrorLine = (info.errorMessage?.split("\n").map((line2) => line2.trim()).find(Boolean) ?? "").replace(INTERNAL_PREFIX_RE, "");
275883
- const readable = firstErrorLine ? clipLine(providerMessage(firstErrorLine) ?? firstErrorLine) : "";
275884
- summary = failureSummary(failureKind(info.reason, firstErrorLine), {
275885
- readable,
275886
- line: firstErrorLine,
275887
- hasDetail: Boolean(detail),
275888
- code: info.code
275889
- });
275890
- }
275891
- const chatExit = {
275892
- ...info.reason ? { reason: info.reason } : {},
275893
- summary,
275894
- ...cancelledBy ? { cancelledBy } : {},
275895
- ...detail ? { detail: `${genericLine}
275896
-
275897
- ${detail}`.slice(0, 4200) } : {}
275898
- };
275899
- return Object.assign(new Error(summary), { chatExit });
275900
- }
276766
+ init_chat_exit();
275901
276767
 
275902
276768
  // ../cli/src/chat-task-header.ts
275903
276769
  init_src11();
@@ -276690,15 +277556,15 @@ function oasisWrapperScript() {
276690
277556
  const main = path28.join(repoRoot, "packages/cli/src/main.ts");
276691
277557
  const key2 = (0, import_node_crypto87.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
276692
277558
  const uid = typeof process.getuid === "function" ? process.getuid() : "na";
276693
- const stable = path28.join(os8.tmpdir(), `oasis-cli-wrapper-${uid}-${key2}`);
277559
+ const stable = path28.join(os9.tmpdir(), `oasis-cli-wrapper-${uid}-${key2}`);
276694
277560
  let dir;
276695
277561
  try {
276696
277562
  fs34.mkdirSync(stable, { recursive: true, mode: 448 });
276697
277563
  const st = fs34.lstatSync(stable);
276698
277564
  const mine = st.isDirectory() && !st.isSymbolicLink() && (typeof process.getuid !== "function" || st.uid === process.getuid());
276699
- dir = mine ? stable : fs34.mkdtempSync(path28.join(os8.tmpdir(), "oasis-cli-"));
277565
+ dir = mine ? stable : fs34.mkdtempSync(path28.join(os9.tmpdir(), "oasis-cli-"));
276700
277566
  } catch {
276701
- dir = fs34.mkdtempSync(path28.join(os8.tmpdir(), "oasis-cli-"));
277567
+ dir = fs34.mkdtempSync(path28.join(os9.tmpdir(), "oasis-cli-"));
276702
277568
  }
276703
277569
  const wrapper = path28.join(dir, "oasis");
276704
277570
  fs34.writeFileSync(wrapper, `#!/usr/bin/env bash
@@ -276924,7 +277790,8 @@ async function startServe(opts) {
276924
277790
  const reloadTimeoutPolicy = () => {
276925
277791
  try {
276926
277792
  activeTimeoutPolicy = loadPolicyFromFile(resolvePolicyFilePath());
276927
- console.log(`[serve] timeout-policy \u5DF2\u70ED\u8F7D\uFF08version=${activeTimeoutPolicy.version}\uFF09`);
277793
+ syncChatWallClockCopy();
277794
+ console.log(`[serve] timeout-policy \u5DF2\u70ED\u8F7D\uFF08version=${activeTimeoutPolicy.version}\uFF0Cchat \u5899\u949F=${wallClockForKind("chat")}ms\uFF09`);
276928
277795
  return { ok: true, version: activeTimeoutPolicy.version };
276929
277796
  } catch (err) {
276930
277797
  console.error(`[serve] timeout-policy \u70ED\u8F7D\u5931\u8D25\uFF0C\u4FDD\u7559\u65E7\u7B56\u7565\uFF1A${err.message}`);
@@ -276938,6 +277805,8 @@ async function startServe(opts) {
276938
277805
  return (act.cadence.find((s2) => s2.reassignAfter) ?? act.cadence[act.cadence.length - 1]).atMs;
276939
277806
  };
276940
277807
  const wallClockForKind = (runtimeKind) => activeTimeoutPolicy.resolve({ executor: "agent", scenario: "wall-clock", runtimeKind }).wallClockMs ?? 120 * 6e4;
277808
+ const syncChatWallClockCopy = () => setChatWallClockMs(wallClockForKind("chat"));
277809
+ syncChatWallClockCopy();
276941
277810
  const loadJson = (name) => {
276942
277811
  const file = path28.join(opts.dir, name);
276943
277812
  return fs34.existsSync(file) ? JSON.parse(fs34.readFileSync(file, "utf8")) : void 0;
@@ -277059,7 +277928,7 @@ async function startServe(opts) {
277059
277928
  );
277060
277929
  const issuer = createTokenIssuer({ secretFile: path28.join(opts.dir, "session-token-secret") });
277061
277930
  const apiActivity = createApiActivityLedger();
277062
- const operatorActor = `actor:human:${os8.userInfo().username}`;
277931
+ const operatorActor = `actor:human:${os9.userInfo().username}`;
277063
277932
  const operatorToken = issuer.issue(operatorActor, { scopes: [OPERATOR_SCOPE] });
277064
277933
  fs34.writeFileSync(path28.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
277065
277934
  let nodeTokens = createNodeTokenStore(path28.join(opts.dir, "node-tokens.json"));
@@ -278897,6 +279766,37 @@ async function startServe(opts) {
278897
279766
  return plan && plan.injectedHistoryCount > 0 ? plan.runtimeMessage : void 0;
278898
279767
  };
278899
279768
  const resolveCompanyBinding = createCompanyBindingResolver({ actors, defaultCompanyId });
279769
+ if (pgPool) {
279770
+ const pinPool = pgPool;
279771
+ void (async () => {
279772
+ try {
279773
+ const companies2 = await controlPlaneStore.listCompanies();
279774
+ const total = { scanned: 0, pinned: 0, leftUnbound: 0, unresolved: 0 };
279775
+ for (const c of companies2) {
279776
+ const rep = await pinChatSessionsToAgentDefault(pinPool, engineSchemaFor(c.id), {
279777
+ companyId: c.id,
279778
+ // 与派发、与委派回流、与 chat 工作区读口**同一个** resolver 实例——存量钉住的目标必须
279779
+ // 与「不钉时这一轮会派到哪」逐字相同,否则这条迁移本身就改了行为。
279780
+ resolveBinding: async (actorId) => {
279781
+ const b2 = await resolveCompanyBinding(c.id, actorId);
279782
+ return b2 ? { nodeId: b2.nodeId, runtimeKind: b2.runtimeKind } : null;
279783
+ }
279784
+ });
279785
+ total.scanned += rep.scanned;
279786
+ total.pinned += rep.pinned;
279787
+ total.leftUnbound += rep.leftUnbound;
279788
+ total.unresolved += rep.unresolved;
279789
+ }
279790
+ if (total.scanned > 0) {
279791
+ console.log(
279792
+ `[chat-runtime-pin] ADR 0613 \u5B58\u91CF\u4F1A\u8BDD\u9489\u76EE\u6807\uFF1A\u547D\u4E2D ${total.scanned} \u6761\u3001\u5199\u5165 ${total.pinned} \u6761\u3001\u5458\u5DE5\u672A\u7ED1\u5B9A\u7559\u7A7A ${total.leftUnbound} \u6761\u3001binding \u89E3\u6790\u5931\u8D25 ${total.unresolved} \u6761`
279793
+ );
279794
+ }
279795
+ } catch (err) {
279796
+ console.warn(`[chat-runtime-pin] \u5B58\u91CF\u4F1A\u8BDD\u9489\u76EE\u6807\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u542F\u52A8\uFF0C\u4E0B\u6B21\u542F\u52A8\u5E42\u7B49\u91CD\u8BD5\uFF09\uFF1A`, err);
279797
+ }
279798
+ })();
279799
+ }
278900
279800
  const delegationWorkdirLocator = buildDelegationWorkdirLocator({
278901
279801
  // ⭐ 与派发、与 chat-sessions 域**同一个 resolver 实例**(评审 blocker ① 五修):
278902
279802
  // 生产 `resolveCompanyBinding` 在上文构造一次,喂给这里、`dispatchChatImpl`、
@@ -279328,7 +280228,7 @@ async function startServe(opts) {
279328
280228
  // 派发台账:动态卡的 runId 与「执行轨迹」抽屉靠它把 work/review 硬关联到 agent_runs.id。
279329
280229
  // 与上面 createServer 的 `dispatchesOfWorkorder`(timeline 用)是同一个闭包,别分叉。
279330
280230
  dispatchesOfWorkorder: (wid) => fanOutLedger.listByWorkorder(wid),
279331
- // 任务动态**协调者卡**(F2):协调者不走 dispatcher,台账里没有它,只能从 agent_runs 取。
280231
+ // 任务动态**协调者卡**(F2)的轨迹入口:协调者不走 dispatcher,台账里没有它,只能从 agent_runs 取。
279332
280232
  // 扇出理由同 `dispatchesOfWorkorder`:这个回调拿不到公司,而一张工单只属于一家。
279333
280233
  coordinatorRunsOfWorkorder: (wid) => fanOutTrace.listCoordinatorRuns(wid),
279334
280234
  // 连败熔断卡面的「末次:{失败原文首句}」:原话只在 `agent_runs.error_message`,快照里只有分类词。
@@ -279405,6 +280305,13 @@ async function startServe(opts) {
279405
280305
  // store(`public.actor_bindings`),org-registry cutover 之后它是冻结旧镜像——只留给
279406
280306
  // 本域其它老读口,工作区定位一律不许再用它。
279407
280307
  resolveBinding: resolveCompanyBinding,
280308
+ /* ADR 0613:建会话 / 「恢复为员工默认运行配置」这两下要读员工详情页配的型号,按**当前公司**读
280309
+ (与 `resolveBinding` 同一条理由:全局 `registry` 对已切 `actor-config` 的公司是冻结旧镜像)。
280310
+ ⚠ 只有这两下读它;解析每一轮的模型依旧不读员工配置(ADR 0610 D1)。 */
280311
+ resolveActorModel: async (cid, actorId) => {
280312
+ const ctx = await actors.resolveCtx(cid ?? defaultCompanyId);
280313
+ return (await ctx.service.latestConfig(actorId))?.model ?? null;
280314
+ },
279408
280315
  // 按 runId 路由:切换后新会话的事件落在本公司库里,共享 store 会一条都补不出 parts。
279409
280316
  // ⚠ `runRoutedTrace` 声明在本行**之后**:包成箭头函数避 TDZ(本域只需要 listEvents)。
279410
280317
  trace: { listEvents: (runId, o) => runRoutedTrace.listEvents(runId, o) },
@@ -279493,7 +280400,7 @@ async function startServe(opts) {
279493
280400
  liveChat,
279494
280401
  // ADR-0587:列表行派生 `unread`,与 collab 域收件箱读同一份按公司的水位(无 PG 时闭包自己回落单实例)。
279495
280402
  resolveReadMarkers: readMarkerStoreFor,
279496
- // ADR 0609 D4:`GET /api/chat-sessions/:id/usage` 的取数口——**与 collab 域是同一个实例**,
280403
+ // ADR 0610 D4:`GET /api/chat-sessions/:id/usage` 的取数口——**与 collab 域是同一个实例**,
279497
280404
  // 按会话取数与组织统计因此永远同一份账本、同一套归属,不会出现两条路各算各的。
279498
280405
  usageRead,
279499
280406
  defaultCompanyId
@@ -280044,7 +280951,7 @@ async function startServe(opts) {
280044
280951
  // ADR-0078 D3:写入围栏的钥匙
280045
280952
  ...c.artifactId !== void 0 ? { artifactId: c.artifactId } : {},
280046
280953
  ...c.sessionId !== void 0 ? { sessionId: c.sessionId } : {},
280047
- // ADR 0609 D1:签名 scopes 透给域路由,`isOperatorRequest` 据此认出部署级 operator-token。
280954
+ // ADR 0610 D1:签名 scopes 透给域路由,`isOperatorRequest` 据此认出部署级 operator-token。
280048
280955
  ...c.scopes !== void 0 ? { scopes: c.scopes } : {}
280049
280956
  } : null;
280050
280957
  },
@@ -280369,9 +281276,10 @@ async function startServe(opts) {
280369
281276
  const provision = isolatedKnowledgeTurn ? { env: {}, wrapperPaths: [], requiredTools: [], requiredToolVersions: {}, connectorCreds: [], cleanup: async () => void 0 } : await buildActorProvision(actorService, actorId, chatDispatchScope(priorChatSession));
280370
281277
  const actorCtx = await buildActorContext(actorService, actorId).catch(() => null);
280371
281278
  const sessionModel = configuredModel(sessionModelOverride);
280372
- const chatResolvedModel = sessionModel ?? configuredModel(actorCtx?.config?.model);
281279
+ const actorConfiguredModel = isolatedBackground ? configuredModel(actorCtx?.config?.model) : void 0;
281280
+ const chatResolvedModel = sessionModel ?? actorConfiguredModel;
280373
281281
  const chatModel = chatResolvedModel ?? await registryStore.getRuntimeConfig(`runtime:${binding.nodeId}:${binding.runtimeKind}`).then((c) => configuredModel(c?.model)).catch(() => void 0);
280374
- const chatModelSource = sessionModel ? "session_override" : chatResolvedModel ? "actor_config" : chatModel ? "runtime_default" : null;
281282
+ const chatModelSource = sessionModel ? "session_override" : actorConfiguredModel ? "actor_config" : chatModel ? "runtime_default" : null;
280375
281283
  const attachmentFiles = { ...preparedAttachments.files, ...restore ? { "SESSION_FILES.md": restore.index } : {} };
280376
281284
  const attachmentBinary = { ...preparedAttachments.binaryFiles };
280377
281285
  const attachmentPaths = [...preparedAttachments.paths];
@@ -280588,8 +281496,7 @@ async function startServe(opts) {
280588
281496
  startedAt: endedAt
280589
281497
  }]);
280590
281498
  const usage = info.usage ?? null;
280591
- const effectiveModel = info.model ?? chatModel ?? null;
280592
- const modelSource = info.model ? "runtime" : chatModelSource;
281499
+ const modelFields = chatExitModelFields(info.model, chatModel, chatModelSource);
280593
281500
  let nativeSessionMetadata;
280594
281501
  if (info.runtimeSessionId && info.runtimeSessionId !== runtimeSessionId) {
280595
281502
  const run = await (await traceStoreFor(runCompanyId)).getRun(traceRunId).catch(() => null);
@@ -280604,8 +281511,7 @@ async function startServe(opts) {
280604
281511
  ...!isHealthySessionExit(info) && info.reason !== "cancelled" ? { errorCode: info.reason ?? "error", errorMessage: formatExitError(info) } : {},
280605
281512
  transcriptRef: info.transcriptRef ?? null,
280606
281513
  usage,
280607
- effectiveModel,
280608
- modelSource,
281514
+ ...modelFields,
280609
281515
  ...nativeSessionMetadata ? { metadata: nativeSessionMetadata } : {}
280610
281516
  });
280611
281517
  if (usage) await settleRunUsage(await traceStoreFor(runCompanyId), traceRunId);
@@ -280718,7 +281624,11 @@ async function startServe(opts) {
280718
281624
  info.chatSessionId,
280719
281625
  `\u5B50\u4F1A\u8BDD\u8FD9\u4E00\u8F6E\u88AB\u6062\u590D\u5668\u6536\u53E3\u4E3A ${info.status}\uFF08${info.reason}\uFF09`,
280720
281626
  info.companyId,
280721
- { outcome: info.status === "succeeded" ? "succeeded" : "failed", ...info.turnId ? { turnId: info.turnId } : {} }
281627
+ {
281628
+ outcome: info.status === "succeeded" ? "succeeded" : "failed",
281629
+ ...info.turnId ? { turnId: info.turnId } : {},
281630
+ ...info.exitReason ? { exitReason: info.exitReason } : {}
281631
+ }
280722
281632
  );
280723
281633
  };
280724
281634
  const handshakeThrottleOpts = {};
@@ -283181,6 +284091,11 @@ ${nodeFault}` : "");
283181
284091
  const zbCostThresholdMicros = opts.zeroOutputBreaker?.costThresholdMicros ?? (envInt("OASIS_ZERO_OUTPUT_BREAKER_COST_USD") ?? 0) * 1e6;
283182
284092
  if (zbEnabled && zbIntervalMs > 0 && (zbThreshold > 0 || zbCostThresholdMicros > 0)) {
283183
284093
  const zbActions = /* @__PURE__ */ new Set(["produce", "review"]);
284094
+ const engineWorkIdOf = (metadata) => {
284095
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return null;
284096
+ const v2 = metadata["engineWorkId"];
284097
+ return typeof v2 === "string" && v2.trim() ? v2 : null;
284098
+ };
283184
284099
  const listRecentZeroOutputRuns = async (sinceIso) => {
283185
284100
  const items = await fanOutTrace.listRunsAcrossWithCompany(
283186
284101
  { status: "no-output", from: sinceIso, limit: 1e3 },
@@ -283194,7 +284109,10 @@ ${nodeFault}` : "");
283194
284109
  actorId: r.actorId,
283195
284110
  action: r.action,
283196
284111
  startedAt: r.startedAt,
283197
- costUsdMicros: r.usage?.costUsdMicros ?? null
284112
+ costUsdMicros: r.usage?.costUsdMicros ?? null,
284113
+ // ★ 轮次维度(Bug 0211):同一轮里的协议续唤各是一条 run,只数 run 会让上报正文那个数
284114
+ // 比人点开轨迹数到的段数大。**只影响正文措辞,不影响跳闸判据**(阈值仍按 run 数判)。
284115
+ workId: engineWorkIdOf(r.metadata)
283198
284116
  }));
283199
284117
  };
283200
284118
  const latestClosingRemark = async (runId) => {
@@ -285731,7 +286649,7 @@ async function startNode(opts) {
285731
286649
  }
285732
286650
 
285733
286651
  // ../cli/src/exec.ts
285734
- var os11 = __toESM(require("node:os"), 1);
286652
+ var os12 = __toESM(require("node:os"), 1);
285735
286653
  var UnsafeExecArgvError = class extends Error {
285736
286654
  code = "ERR_UNSAFE_EXEC_ARGV";
285737
286655
  constructor(message) {
@@ -285768,7 +286686,7 @@ function planExec(opts) {
285768
286686
  function computeExitCode(code2, signal) {
285769
286687
  if (code2 !== null) return code2;
285770
286688
  if (signal) {
285771
- const num4 = os11.constants.signals[signal];
286689
+ const num4 = os12.constants.signals[signal];
285772
286690
  return 128 + (typeof num4 === "number" ? num4 : 1);
285773
286691
  }
285774
286692
  return 1;
@@ -286018,7 +286936,7 @@ var COMMAND_DECLS = {
286018
286936
  },
286019
286937
  "usage-ledger-backfill": {
286020
286938
  group: "\u57FA\u7840\u8BBE\u65BD",
286021
- usage: "oasis usage-ledger-backfill (--company <id> | --all) (--dry-run | --execute | --verify | --undo <manifestId>)",
286939
+ usage: "oasis usage-ledger-backfill (--company <id> | --all) (--dry-run | --execute | --verify | --undo <manifestId>) [--claude-resume-before <ISO>]",
286022
286940
  description: "\u7528\u91CF\u8D26\u672C\u5386\u53F2\u56DE\u586B\uFF08\u4F7F\u7528\u4E0E\u7EDF\u8BA1\uFF09\uFF1A\u8865\u7EC8\u503C\u7528\u91CF\u70B9\u3001runtime_started_at\u3001chat \u4F1A\u8BDD/\u8F6E\u6B21\uFF1B\u5E42\u7B49\uFF0C\u6539\u52A8\u5199\u5BA1\u8BA1\u8868\uFF0C\u53EF\u6309 manifest \u64A4\u9500\u3002\u4EE5 serve \u7684\u73AF\u5883\u5728\u90E8\u7F72\u673A\u4E0A\u6267\u884C\u3002",
286023
286941
  flags: [
286024
286942
  { name: "company", desc: "\u53EA\u5904\u7406\u8FD9\u5BB6\u516C\u53F8\u7684\u8D26\u672C\uFF08\u4ECD\u5728 legacy \u7684\u516C\u53F8 = \u5171\u4EAB\u8D26\u672C + \u672C\u516C\u53F8 chat \u53CD\u67E5\uFF09" },
@@ -286026,10 +286944,12 @@ var COMMAND_DECLS = {
286026
286944
  { name: "dry-run", desc: "\u53EA\u62A5\u6BCF\u4E2A\u8D26\u672C\u5404\u6B65\u5F85\u6539\u6761\u6570\u548C\u6837\u4F8B run id\uFF0C\u4E00\u884C\u4E0D\u5199", boolean: true },
286027
286945
  { name: "execute", desc: "\u5199\u5165\uFF1B\u7ED3\u675F\u65F6\u6253\u5370 manifestId", boolean: true },
286028
286946
  { name: "verify", desc: "\u6838\u5BF9 I6\uFF08\u7EC8\u503C\u70B9 = agent_runs.usage\u3001\u68C0\u67E5\u70B9\u4E0D\u8D85\u7EC8\u503C\uFF09\u4E0E\u542F\u52A8\u8BC1\u636E\uFF1B\u6709\u8FDD\u4F8B\u9000\u51FA\u7801 1", boolean: true },
286029
- { name: "undo", desc: "\u6309\u5BA1\u8BA1\u8868\u628A\u67D0\u4E2A manifestId \u7684\u6539\u52A8\u6062\u590D\u539F\u503C" }
286947
+ { name: "undo", desc: "\u6309\u5BA1\u8BA1\u8868\u628A\u67D0\u4E2A manifestId \u7684\u6539\u52A8\u6062\u590D\u539F\u503C" },
286948
+ { name: "claude-resume-before", desc: "\u52A0\u8DD1\u7B2C 4 \u6B65\uFF1A\u5F00\u8DD1\u65E9\u4E8E\u8FD9\u4E2A\u65F6\u523B\u7684 claude-code run\uFF0C\u628A\u7EED\u8DD1\u5E26\u8FDB\u6765\u7684\u6574\u6761\u4F1A\u8BDD\u7EBF\u7D2F\u8BA1\u503C\u6263\u56DE\u6210\u672C\u8F6E\u51C0\u503C\uFF08\u53D6\u4FEE\u597D\u7684\u8282\u70B9\u5305\u5728\u5168\u90E8\u8282\u70B9\u88C5\u4E0A\u7684\u65F6\u523B\uFF09" }
286030
286949
  ],
286031
286950
  examples: [
286032
286951
  "oasis usage-ledger-backfill --all --dry-run",
286952
+ "oasis usage-ledger-backfill --all --dry-run --claude-resume-before 2026-09-23T00:00:00Z",
286033
286953
  "oasis usage-ledger-backfill --all --execute",
286034
286954
  "oasis usage-ledger-backfill --all --verify"
286035
286955
  ]
@@ -288372,7 +289292,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
288372
289292
  }
288373
289293
  }
288374
289294
  }
288375
- var CLI_VERSION = "2.2.69" ? "2.2.69" : "dev";
289295
+ var CLI_VERSION = "2.2.70" ? "2.2.70" : "dev";
288376
289296
  function apiHeaders(token, company) {
288377
289297
  const headers = {
288378
289298
  authorization: `Bearer ${token}`,
@@ -288607,7 +289527,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
288607
289527
  const res = await fetch(`${httpBase2}/api/nodes/exchange`, {
288608
289528
  method: "POST",
288609
289529
  headers: { "content-type": "application/json" },
288610
- body: JSON.stringify({ enrollToken, nodeId, hostname: os12.hostname(), serverUrl, ...companyId ? { companyId } : {} })
289530
+ body: JSON.stringify({ enrollToken, nodeId, hostname: os13.hostname(), serverUrl, ...companyId ? { companyId } : {} })
288611
289531
  });
288612
289532
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
288613
289533
  const body2 = await res.json();
@@ -288640,7 +289560,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
288640
289560
  const binPath = path37.join(prefix, "bin", "oasis_test");
288641
289561
  const svcArgs = `node --server-ws ${serverUrl} --dir ${dir}${nodeName ? ` --name ${nodeName}` : ""}`;
288642
289562
  if (process.platform === "linux") {
288643
- const svcFile = path37.join(os12.homedir(), ".config/systemd/user/oasis-node.service");
289563
+ const svcFile = path37.join(os13.homedir(), ".config/systemd/user/oasis-node.service");
288644
289564
  fs42.mkdirSync(path37.dirname(svcFile), { recursive: true });
288645
289565
  if (spawnSync3("systemctl", ["--user", "is-active", "--quiet", "oasis-node"], {}).status === 0)
288646
289566
  spawnSync3("systemctl", ["--user", "stop", "oasis-node"], { stdio: "inherit" });
@@ -288653,7 +289573,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
288653
289573
  `ExecStart=${binPath} ${svcArgs}`,
288654
289574
  // Carry the install-time PATH so the detached daemon detects CLIs in user dirs
288655
289575
  // (~/.npm-global/bin, ~/.local/bin) — systemd --user otherwise gives a minimal PATH.
288656
- `Environment=PATH=${process.env["PATH"] ?? ""}:${path37.join(os12.homedir(), ".npm-global/bin")}:${path37.join(os12.homedir(), ".local/bin")}:/usr/local/bin`,
289576
+ `Environment=PATH=${process.env["PATH"] ?? ""}:${path37.join(os13.homedir(), ".npm-global/bin")}:${path37.join(os13.homedir(), ".local/bin")}:/usr/local/bin`,
288657
289577
  "Restart=on-failure",
288658
289578
  "RestartSec=5",
288659
289579
  "StandardOutput=journal",
@@ -288665,23 +289585,23 @@ async function runCli(argv, println = console.log, progressln = console.error) {
288665
289585
  ].join("\n"));
288666
289586
  spawnSync3("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
288667
289587
  spawnSync3("systemctl", ["--user", "enable", "--now", "oasis-node"], { stdio: "inherit" });
288668
- spawnSync3("loginctl", ["enable-linger", os12.userInfo().username], { stdio: "ignore" });
289588
+ spawnSync3("loginctl", ["enable-linger", os13.userInfo().username], { stdio: "ignore" });
288669
289589
  println("\u2713 Oasis node service started (systemd --user oasis-node)");
288670
289590
  println(" Status : systemctl --user status oasis-node");
288671
289591
  println(" Logs : journalctl --user -u oasis-node -f");
288672
289592
  println(" Stop : systemctl --user stop oasis-node");
288673
289593
  } else if (process.platform === "darwin") {
288674
- const plist = path37.join(os12.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
289594
+ const plist = path37.join(os13.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
288675
289595
  fs42.mkdirSync(path37.dirname(plist), { recursive: true });
288676
289596
  spawnSync3("launchctl", ["unload", plist], { stdio: "ignore" });
288677
289597
  const argXml = [binPath, "node", "--server-ws", serverUrl, "--dir", dir, ...nodeName ? ["--name", nodeName] : []].map((a) => `<string>${a}</string>`).join("");
288678
- fs42.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.oasis.node</string><key>ProgramArguments</key><array>${argXml}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>${os12.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os12.homedir()}/.oasis-node.err</string></dict></plist>`);
289598
+ fs42.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.oasis.node</string><key>ProgramArguments</key><array>${argXml}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>${os13.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os13.homedir()}/.oasis-node.err</string></dict></plist>`);
288679
289599
  spawnSync3("launchctl", ["load", plist], { stdio: "inherit" });
288680
289600
  println("\u2713 Oasis node service started (macOS LaunchAgent com.oasis.node)");
288681
- println(` Logs : tail -f ${os12.homedir()}/.oasis-node.log`);
289601
+ println(` Logs : tail -f ${os13.homedir()}/.oasis-node.log`);
288682
289602
  println(` Stop : launchctl unload ${plist}`);
288683
289603
  } else {
288684
- spawnSync3("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path37.join(os12.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
289604
+ spawnSync3("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path37.join(os13.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
288685
289605
  println(`\u2713 Oasis node started in background (nohup). Logs: ~/.oasis-node.log`);
288686
289606
  println(" Note: no automatic restart on reboot in this environment.");
288687
289607
  }
@@ -288790,8 +289710,8 @@ async function runCli(argv, println = console.log, progressln = console.error) {
288790
289710
  if (command2 === "serve" && positional[0] === "install") {
288791
289711
  const absDir = path37.resolve(dir);
288792
289712
  const name = `oasis-serve-${path37.basename(absDir)}`;
288793
- const unitDir = flags.get("unit-dir") ?? path37.join(os12.homedir(), ".config", "systemd", "user");
288794
- const bin = flags.get("bin") ?? path37.join(os12.homedir(), ".local", "bin", "oasis");
289713
+ const unitDir = flags.get("unit-dir") ?? path37.join(os13.homedir(), ".config", "systemd", "user");
289714
+ const bin = flags.get("bin") ?? path37.join(os13.homedir(), ".local", "bin", "oasis");
288795
289715
  const port = flags.get("port") ?? "7320";
288796
289716
  const extra = (flags.get("dispatch") === "true" ? ` --dispatch true` : "") + (flags.has("model") ? ` --model ${flags.get("model")}` : "");
288797
289717
  const unit = [
@@ -289280,7 +290200,7 @@ Trace: ${run.traceRunId}`);
289280
290200
  if (plan.inject.mode === "env") {
289281
290201
  childEnv[plan.inject.envName] = value2;
289282
290202
  } else {
289283
- tmpDir = await fs42.promises.mkdtemp(path37.join(os12.tmpdir(), "oasis-var-exec-"));
290203
+ tmpDir = await fs42.promises.mkdtemp(path37.join(os13.tmpdir(), "oasis-var-exec-"));
289284
290204
  const keyfile = path37.join(tmpDir, "value");
289285
290205
  await fs42.promises.writeFile(keyfile, value2, { mode: 384 });
289286
290206
  childEnv[plan.inject.fileEnv] = keyfile;
@@ -290166,7 +291086,7 @@ ${res.warning}`);
290166
291086
  for (const a of st.artifacts) println(` - ${a.path}\uFF08${a.kind ?? "?"}\uFF09status=${a.status ?? "?"}${a.mtime ? ` \xB7 ${new Date(a.mtime).toISOString()}` : ""}`);
290167
291087
  if (flags.get("pull") === "true") {
290168
291088
  const odPublic2 = (flags.get("od-public-url") ?? process.env["OASIS_OPEN_DESIGN_PUBLIC_URL"] ?? "https://design.open-friday.com").replace(/\/$/, "");
290169
- const { dir: dir3, entry: entry2 } = await harvestBoard(odBase2, fetch, odPublic2, os12.tmpdir(), st.boardKey, st.runs[0]?.runId ?? "status-pull");
291089
+ const { dir: dir3, entry: entry2 } = await harvestBoard(odBase2, fetch, odPublic2, os13.tmpdir(), st.boardKey, st.runs[0]?.runId ?? "status-pull");
290170
291090
  println(`[design] \u5DF2\u62C9\u5230\u672C\u5730\uFF1A${dir3}\uFF08\u5165\u53E3 ${entry2}\uFF09`);
290171
291091
  println(`[design] \u8BFB\u5B8C\u6EE1\u610F\u5373\u4EA4\u7A3F\uFF1Aoasis propose ${artifactId2} --from-dir ${dir3} --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"`);
290172
291092
  } else {
@@ -291974,10 +292894,10 @@ function runtimeAssetSourceRoots(input) {
291974
292894
 
291975
292895
  // src/paths.ts
291976
292896
  var fs46 = __toESM(require("node:fs"));
291977
- var os13 = __toESM(require("node:os"));
292897
+ var os14 = __toESM(require("node:os"));
291978
292898
  var path41 = __toESM(require("node:path"));
291979
292899
  var DEFAULT_INSTANCE = "default";
291980
- var home = () => os13.homedir();
292900
+ var home = () => os14.homedir();
291981
292901
  var oasisDir = () => path41.join(home(), ".oasis");
291982
292902
  var versionsDir = () => path41.join(oasisDir(), "versions");
291983
292903
  var versionDir = (label2) => path41.join(versionsDir(), label2);
@@ -292178,7 +293098,7 @@ function shimScript() {
292178
293098
  }
292179
293099
 
292180
293100
  // src/index.ts
292181
- var PKG_VERSION = true ? "2.2.69" : "dev";
293101
+ var PKG_VERSION = true ? "2.2.70" : "dev";
292182
293102
  var LOCAL_BIN = localBin();
292183
293103
  var NPM_PREFIX = npmPrefix();
292184
293104
  var INSTANCE = DEFAULT_INSTANCE;
@@ -292284,7 +293204,7 @@ function installSoftware(label2) {
292284
293204
  function setupAutostart(name) {
292285
293205
  const inst = instancePaths(name);
292286
293206
  const cmd = `${process.execPath} "${inst.binFile}" --_daemon --instance ${name}`;
292287
- const home2 = os14.homedir();
293207
+ const home2 = os15.homedir();
292288
293208
  const pnpmHome = process.env["PNPM_HOME"] || path43.join(home2, ".local", "share", "pnpm");
292289
293209
  const extraBins = [
292290
293210
  path43.join(home2, ".npm-global", "bin"),
@@ -292294,7 +293214,7 @@ function setupAutostart(name) {
292294
293214
  ];
292295
293215
  const daemonPath = sanitizeDaemonPath([process.env["PATH"] ?? "", ...extraBins].filter(Boolean).join(":"));
292296
293216
  if (process.platform === "linux") {
292297
- const unitDir = path43.join(os14.homedir(), ".config", "systemd", "user");
293217
+ const unitDir = path43.join(os15.homedir(), ".config", "systemd", "user");
292298
293218
  fs48.mkdirSync(unitDir, { recursive: true });
292299
293219
  const unit = systemdUnitName(name);
292300
293220
  fs48.mkdirSync(path43.dirname(inst.logFile), { recursive: true });
@@ -292319,7 +293239,7 @@ function setupAutostart(name) {
292319
293239
  return false;
292320
293240
  } else if (process.platform === "darwin") {
292321
293241
  const label2 = launchdLabel(name);
292322
- const plist = path43.join(os14.homedir(), "Library", "LaunchAgents", `${label2}.plist`);
293242
+ const plist = path43.join(os15.homedir(), "Library", "LaunchAgents", `${label2}.plist`);
292323
293243
  fs48.mkdirSync(path43.dirname(plist), { recursive: true });
292324
293244
  fs48.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
292325
293245
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -292501,7 +293421,7 @@ async function startOneInstance(o) {
292501
293421
  const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
292502
293422
  method: "POST",
292503
293423
  headers: { "content-type": "application/json" },
292504
- body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os14.hostname(), serverUrl, ...companyId ? { companyId } : {} })
293424
+ body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os15.hostname(), serverUrl, ...companyId ? { companyId } : {} })
292505
293425
  });
292506
293426
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
292507
293427
  const body2 = await res.json();
@@ -292566,7 +293486,7 @@ function derivedNodeIdFor(name) {
292566
293486
  if (name === DEFAULT_INSTANCE) return resolveNodeId();
292567
293487
  const user = (() => {
292568
293488
  try {
292569
- return os14.userInfo().username;
293489
+ return os15.userInfo().username;
292570
293490
  } catch {
292571
293491
  return process.env["USER"] ?? process.env["USERNAME"] ?? "unknown";
292572
293492
  }