oasis_test 0.1.54 → 0.1.57

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 +1008 -319
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -911,8 +911,8 @@ function deriveArtifactId(type, workspace, title, usedIds) {
911
911
  usedIds.add(id);
912
912
  return id;
913
913
  }
914
- function resolveNodeRef(ref, candidates) {
915
- const r = ref.trim();
914
+ function resolveNodeRef(ref2, candidates) {
915
+ const r = ref2.trim();
916
916
  if (r.startsWith("artifact:")) {
917
917
  const hit = candidates.find((n) => n.id === r);
918
918
  return hit ? { ok: true, id: hit.id } : { ok: false, reason: "not_found" };
@@ -1068,16 +1068,16 @@ var init_content_type_handler = __esm({
1068
1068
  const short = (s2) => s2.slice(0, 12);
1069
1069
  const versionLine = (() => {
1070
1070
  if (!(ctx.hasHead && ctx.headExternalRef)) return [];
1071
- const ref = ctx.headExternalRef;
1072
- if (ref.trim().startsWith("{")) {
1071
+ const ref2 = ctx.headExternalRef;
1072
+ if (ref2.trim().startsWith("{")) {
1073
1073
  try {
1074
- const m2 = JSON.parse(ref);
1074
+ const m2 = JSON.parse(ref2);
1075
1075
  const parts = Object.entries(m2).map(([id, sha2]) => `\`${id}\` @ \`${short(sha2)}\``);
1076
1076
  return [`> **\u5F53\u524D\u7248\u672C**\uFF08\u4F60\u4E0A\u4E00\u7248\uFF0C\u5DF2\u5E76\u5165\u5404\u4ED3\u4E3B\u5E72\uFF09\uFF1A${parts.join("\uFF0C")}\u2014\u2014clone \u4E3B\u5E72\u5373\u5728\u5B83\u4E4B\u4E0A\u6539\uFF0C\u522B\u4ECE\u5934\u5199\u3002`];
1077
1077
  } catch {
1078
1078
  }
1079
1079
  }
1080
- return [`> **\u5F53\u524D\u7248\u672C**\uFF08\u4F60\u4E0A\u4E00\u7248\uFF0C\u5DF2\u5E76\u5165\u4E3B\u5E72\uFF09\uFF1A\`${repos[0]?.id ?? "<\u4E3B\u4ED3>"}\` @ \`${short(ref)}\`\u2014\u2014clone \u4E3B\u5E72\u5373\u5728\u5B83\u4E4B\u4E0A\u6539\uFF0C\u522B\u4ECE\u5934\u5199\u3002`];
1080
+ return [`> **\u5F53\u524D\u7248\u672C**\uFF08\u4F60\u4E0A\u4E00\u7248\uFF0C\u5DF2\u5E76\u5165\u4E3B\u5E72\uFF09\uFF1A\`${repos[0]?.id ?? "<\u4E3B\u4ED3>"}\` @ \`${short(ref2)}\`\u2014\u2014clone \u4E3B\u5E72\u5373\u5728\u5B83\u4E4B\u4E0A\u6539\uFF0C\u522B\u4ECE\u5934\u5199\u3002`];
1081
1081
  })();
1082
1082
  return [
1083
1083
  `\u4F60\u4EA7\u51FA\u7684\u662F\u9879\u76EE **${ctx.projectName ?? "?"}** \u7684\u4EE3\u7801\u2014\u2014**\u5185\u5BB9\u76F4\u63A5\u6D3B\u5728 git \u4ED3\u5E93\u91CC**\uFF0C\u4F60\u50CF\u5F00\u53D1\u8005\u4E00\u6837\u5728 git \u91CC\u5E72\uFF0C\u4EA7\u7269\u4E0D\u8FDB Oasis \u6863\u6848\u67DC\u3002`,
@@ -1405,6 +1405,68 @@ var init_collab_view = __esm({
1405
1405
  }
1406
1406
  });
1407
1407
 
1408
+ // ../contract/src/workorder-quality.ts
1409
+ function record(value) {
1410
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1411
+ }
1412
+ function requiredString(value, field) {
1413
+ if (typeof value !== "string" || !value.trim()) {
1414
+ throw new Error(`invalid qa_quality_metrics payload: ${field} must be a non-empty string`);
1415
+ }
1416
+ return value.trim();
1417
+ }
1418
+ function count(value, field) {
1419
+ if (!Number.isInteger(value) || value < 0) {
1420
+ throw new Error(`invalid qa_quality_metrics payload: ${field} must be a non-negative integer`);
1421
+ }
1422
+ return value;
1423
+ }
1424
+ function counts(value, field) {
1425
+ const input = record(value);
1426
+ if (!input) throw new Error(`invalid qa_quality_metrics payload: ${field} must be an object`);
1427
+ const passed = count(input["passed"], `${field}.passed`);
1428
+ const total = count(input["total"], `${field}.total`);
1429
+ if (passed > total) throw new Error(`invalid qa_quality_metrics payload: ${field}.passed cannot exceed total`);
1430
+ return { passed, total };
1431
+ }
1432
+ function testCounts(value, field) {
1433
+ const input = record(value);
1434
+ if (!input) throw new Error(`invalid qa_quality_metrics payload: ${field} must be an object`);
1435
+ const base = counts(input, field);
1436
+ const failed = count(input["failed"] ?? 0, `${field}.failed`);
1437
+ const blocked = count(input["blocked"] ?? 0, `${field}.blocked`);
1438
+ if (base.passed + failed + blocked > base.total) {
1439
+ throw new Error(`invalid qa_quality_metrics payload: ${field} passed + failed + blocked cannot exceed total`);
1440
+ }
1441
+ return { ...base, failed, blocked };
1442
+ }
1443
+ function parseWorkorderQualityMetricsPayload(value) {
1444
+ const input = record(value);
1445
+ if (!input || input["kind"] !== "qa_quality_metrics") return null;
1446
+ const schemaVersion = input["schema_version"] ?? input["schemaVersion"];
1447
+ if (schemaVersion !== 1) {
1448
+ throw new Error("invalid qa_quality_metrics payload: schema_version must be 1");
1449
+ }
1450
+ const checklistRefRaw = input["checklist_ref"] ?? input["checklistRef"];
1451
+ if (checklistRefRaw !== void 0 && (typeof checklistRefRaw !== "string" || !checklistRefRaw.trim())) {
1452
+ throw new Error("invalid qa_quality_metrics payload: checklist_ref must be a non-empty string when provided");
1453
+ }
1454
+ return {
1455
+ kind: "qa_quality_metrics",
1456
+ schemaVersion: 1,
1457
+ suiteRunId: requiredString(input["suite_run_id"] ?? input["suiteRunId"], "suite_run_id"),
1458
+ ...typeof checklistRefRaw === "string" ? { checklistRef: checklistRefRaw.trim() } : {},
1459
+ functionalCoverage: counts(input["functional_coverage"] ?? input["functionalCoverage"], "functional_coverage"),
1460
+ p0Tests: testCounts(input["p0_tests"] ?? input["p0Tests"], "p0_tests"),
1461
+ p1Tests: testCounts(input["p1_tests"] ?? input["p1Tests"], "p1_tests")
1462
+ };
1463
+ }
1464
+ var init_workorder_quality = __esm({
1465
+ "../contract/src/workorder-quality.ts"() {
1466
+ "use strict";
1467
+ }
1468
+ });
1469
+
1408
1470
  // ../contract/src/schema.ts
1409
1471
  var init_schema = __esm({
1410
1472
  "../contract/src/schema.ts"() {
@@ -1462,9 +1524,9 @@ var init_storage = __esm({
1462
1524
  });
1463
1525
 
1464
1526
  // ../contract/src/avatar.ts
1465
- function isAgentAvatarPresetRef(ref) {
1466
- if (typeof ref !== "string" || !ref.startsWith(AGENT_AVATAR_PRESET_PREFIX)) return false;
1467
- const id = ref.slice(AGENT_AVATAR_PRESET_PREFIX.length);
1527
+ function isAgentAvatarPresetRef(ref2) {
1528
+ if (typeof ref2 !== "string" || !ref2.startsWith(AGENT_AVATAR_PRESET_PREFIX)) return false;
1529
+ const id = ref2.slice(AGENT_AVATAR_PRESET_PREFIX.length);
1468
1530
  return AGENT_AVATAR_PRESET_IDS.includes(id);
1469
1531
  }
1470
1532
  var AGENT_AVATAR_PRESET_PREFIX, AGENT_AVATAR_PRESET_IDS;
@@ -1576,6 +1638,7 @@ var init_src = __esm({
1576
1638
  init_company();
1577
1639
  init_annotation();
1578
1640
  init_collab_view();
1641
+ init_workorder_quality();
1579
1642
  init_schema();
1580
1643
  init_errors();
1581
1644
  init_storage();
@@ -1636,13 +1699,13 @@ async function reachableBlobs(oplog, blobs) {
1636
1699
  const reachable = /* @__PURE__ */ new Set();
1637
1700
  for await (const { op } of oplog.readAll()) {
1638
1701
  const payload = op.payload;
1639
- const ref = payload.contentRef;
1640
- if (typeof ref !== "string") continue;
1702
+ const ref2 = payload.contentRef;
1703
+ if (typeof ref2 !== "string") continue;
1641
1704
  if (payload.contentKind === "external-pin") continue;
1642
1705
  if (payload.contentKind === "manifest") {
1643
- for (const h of await manifestRefs(blobs, ref)) reachable.add(h);
1706
+ for (const h of await manifestRefs(blobs, ref2)) reachable.add(h);
1644
1707
  } else {
1645
- reachable.add(ref);
1708
+ reachable.add(ref2);
1646
1709
  }
1647
1710
  }
1648
1711
  return reachable;
@@ -1664,10 +1727,10 @@ async function checkCitationExists(input) {
1664
1727
  if (input.revision.contentKind && input.revision.contentKind !== "inline-blob") return [];
1665
1728
  const text = new TextDecoder().decode(await input.blobs.get(input.revision.contentRef));
1666
1729
  const findings = [];
1667
- for (const ref of new Set(text.match(CITATION_RE) ?? [])) {
1668
- const exists = ref.startsWith("artifact:") ? input.model.artifacts.has(ref) : input.model.revisions.has(ref);
1730
+ for (const ref2 of new Set(text.match(CITATION_RE) ?? [])) {
1731
+ const exists = ref2.startsWith("artifact:") ? input.model.artifacts.has(ref2) : input.model.revisions.has(ref2);
1669
1732
  if (!exists) {
1670
- findings.push({ check: "citation_exists", detail: `\u51ED\u7A7A/\u60AC\u7A7A\u5F15\u7528\uFF1A${ref}` });
1733
+ findings.push({ check: "citation_exists", detail: `\u51ED\u7A7A/\u60AC\u7A7A\u5F15\u7528\uFF1A${ref2}` });
1671
1734
  }
1672
1735
  }
1673
1736
  return findings;
@@ -2802,7 +2865,7 @@ var init_kernel = __esm({
2802
2865
  return id;
2803
2866
  });
2804
2867
  const candidates = [...existing, ...batch];
2805
- const ref = (r, what) => {
2868
+ const ref2 = (r, what) => {
2806
2869
  if (r === void 0) return void 0;
2807
2870
  if (!ws) return r;
2808
2871
  const res = resolveNodeRef(r, candidates);
@@ -2815,15 +2878,15 @@ ${res.candidates.map((c) => ` - ${c.type}:${c.title ?? "(\u65E0title)"} \u2192
2815
2878
  const ops = plan.ops.map((op, i) => {
2816
2879
  switch (op.action) {
2817
2880
  case "spawn":
2818
- return { ...op, id: spawnIds[i], ...op.workspace === void 0 && ws ? { workspace: ws } : {}, ...op.inputs ? { inputs: op.inputs.map((e) => ({ to: ref(e.to, "spawn \u4F9D\u8D56"), ...e.required !== void 0 ? { required: e.required } : {} })) } : {} };
2881
+ return { ...op, id: spawnIds[i], ...op.workspace === void 0 && ws ? { workspace: ws } : {}, ...op.inputs ? { inputs: op.inputs.map((e) => ({ to: ref2(e.to, "spawn \u4F9D\u8D56"), ...e.required !== void 0 ? { required: e.required } : {} })) } : {} };
2819
2882
  case "link":
2820
2883
  case "unlink":
2821
2884
  case "pin":
2822
- return { ...op, artifactId: ref(op.artifactId, op.action), to: ref(op.to, `${op.action} \u4E0A\u6E38`) };
2885
+ return { ...op, artifactId: ref2(op.artifactId, op.action), to: ref2(op.to, `${op.action} \u4E0A\u6E38`) };
2823
2886
  case "annotate":
2824
- return { ...op, artifactId: ref(op.artifactId, "annotate"), ...op.raisedFrom ? { raisedFrom: ref(op.raisedFrom, "annotate raisedFrom") } : {} };
2887
+ return { ...op, artifactId: ref2(op.artifactId, "annotate"), ...op.raisedFrom ? { raisedFrom: ref2(op.raisedFrom, "annotate raisedFrom") } : {} };
2825
2888
  default:
2826
- return { ...op, artifactId: ref(op.artifactId, op.action) };
2889
+ return { ...op, artifactId: ref2(op.artifactId, op.action) };
2827
2890
  }
2828
2891
  });
2829
2892
  return { ...plan, ops };
@@ -4014,7 +4077,10 @@ function computeReviewJobs(model) {
4014
4077
  }
4015
4078
  return out;
4016
4079
  }
4017
- var import_node_crypto2, DEFAULT_PREDISPATCH_TIMEOUT_MS, SpawnAttemptStale, SEED_ROOT_SUCCESSFUL_PRODUCE_LIMIT, CANVAS_RUNTIME_KIND, NON_PRODUCE_FALLBACK_RUNTIME_KIND, Dispatcher;
4080
+ function isSelfHealingExit(reason) {
4081
+ return reason !== void 0 && SELF_HEALING_EXITS.has(reason);
4082
+ }
4083
+ var import_node_crypto2, DEFAULT_PREDISPATCH_TIMEOUT_MS, SpawnAttemptStale, SEED_ROOT_SUCCESSFUL_PRODUCE_LIMIT, SELF_HEALING_EXITS, CANVAS_RUNTIME_KIND, NON_PRODUCE_FALLBACK_RUNTIME_KIND, Dispatcher;
4018
4084
  var init_dispatcher = __esm({
4019
4085
  "../core/src/dispatcher.ts"() {
4020
4086
  "use strict";
@@ -4032,6 +4098,7 @@ var init_dispatcher = __esm({
4032
4098
  }
4033
4099
  };
4034
4100
  SEED_ROOT_SUCCESSFUL_PRODUCE_LIMIT = 5;
4101
+ SELF_HEALING_EXITS = /* @__PURE__ */ new Set(["rate-limit"]);
4035
4102
  CANVAS_RUNTIME_KIND = "open-design";
4036
4103
  NON_PRODUCE_FALLBACK_RUNTIME_KIND = "claude-code";
4037
4104
  Dispatcher = class {
@@ -4065,7 +4132,7 @@ var init_dispatcher = __esm({
4065
4132
  } catch {
4066
4133
  }
4067
4134
  }
4068
- /** 熔断中的 job(B3 表面化:/api/view dispatches 的 fused 区) */
4135
+ /** 熔断中的 job(B3 表面化:/api/view dispatches 的 fused 区;lastReason 供自动分诊做诊断上下文) */
4069
4136
  fusedJobs() {
4070
4137
  return [...this.strikes.entries()].filter(([, st]) => st.fused).map(([jobKey, st]) => ({
4071
4138
  jobKey,
@@ -4073,9 +4140,24 @@ var init_dispatcher = __esm({
4073
4140
  actor: st.actor,
4074
4141
  action: st.action,
4075
4142
  consecutiveFailures: st.count,
4076
- fusedAt: st.fusedAt
4143
+ fusedAt: st.fusedAt,
4144
+ ...st.lastReason ? { lastReason: st.lastReason } : {}
4077
4145
  }));
4078
4146
  }
4147
+ /**
4148
+ * 长期饥饿的自愈类 job(rate-limit 持续退避超 starvationMs 仍未恢复):自愈类从不熔断、进不了 fusedJobs,
4149
+ * 若配额**长期**不足就成了静默卡死——本访问器供 SLA 扫描把它升级为**算力缺口**(提案"长期不足才=算力缺口")。
4150
+ */
4151
+ starvingJobs(starvationMs, now) {
4152
+ const out = [];
4153
+ for (const [jobKey, st] of this.strikes) {
4154
+ if (st.fused || !isSelfHealingExit(st.lastReason) || !st.firstFailedAt) continue;
4155
+ const sinceMs = now - Date.parse(st.firstFailedAt);
4156
+ if (sinceMs <= starvationMs) continue;
4157
+ out.push({ jobKey, artifactId: st.artifactId, actor: st.actor, action: st.action, consecutiveFailures: st.count, lastReason: st.lastReason, sinceMs });
4158
+ }
4159
+ return out;
4160
+ }
4079
4161
  /** v1 调度准入:当前被并发上限挡下、等待重评的 produce 会话数(/api/view dispatches 表面化)。 */
4080
4162
  queuedCount() {
4081
4163
  return this.queued.size;
@@ -4315,19 +4397,22 @@ var init_dispatcher = __esm({
4315
4397
  }
4316
4398
  recordSpawnFailure(jobKey, spec, reason) {
4317
4399
  const seqAtVerdict = this.opts.kernel.model.lastSeq.get(spec.artifactId) ?? 0;
4318
- const consecutive = (this.strikes.get(jobKey)?.count ?? 0) + 1;
4400
+ const prev = this.strikes.get(jobKey);
4401
+ const consecutive = (prev?.count ?? 0) + 1;
4319
4402
  const max = this.opts.backoff?.maxConsecutiveFailures ?? 3;
4320
4403
  const base = this.opts.backoff?.baseMs ?? 3e4;
4321
- const fused = consecutive >= max;
4404
+ const cap = this.opts.backoff?.maxBackoffMs ?? 30 * 6e4;
4405
+ const fused = consecutive >= max && !isSelfHealingExit(reason);
4322
4406
  this.strikes.set(jobKey, {
4323
4407
  artifactId: spec.artifactId,
4324
4408
  actor: spec.actor,
4325
4409
  action: spec.action,
4326
4410
  count: consecutive,
4327
- nextEligibleAt: Date.now() + base * 2 ** (consecutive - 1),
4411
+ nextEligibleAt: Date.now() + Math.min(base * 2 ** (consecutive - 1), cap),
4328
4412
  fused,
4329
4413
  ...fused ? { fusedAt: (/* @__PURE__ */ new Date()).toISOString() } : {},
4330
4414
  seqAtVerdict,
4415
+ firstFailedAt: prev?.firstFailedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4331
4416
  lastReason: reason
4332
4417
  });
4333
4418
  if (fused) {
@@ -4401,7 +4486,7 @@ var init_dispatcher = __esm({
4401
4486
  }
4402
4487
  }
4403
4488
  const retry = this.strikes.get(jobKey);
4404
- const lastFailure = retry && retry.count > 0 && retry.lastReason ? { reason: retry.lastReason } : void 0;
4489
+ const lastFailure = retry && retry.count > 0 && retry.lastReason && !isSelfHealingExit(retry.lastReason) ? { reason: retry.lastReason } : void 0;
4405
4490
  const ws = kernel.model.artifacts.get(spec.artifactId)?.workspace;
4406
4491
  const codeRepo = ws ? await this.opts.resolveCodeRepo?.(ws) ?? null : null;
4407
4492
  this.assertSpawnAttemptCurrent(jobKey, attempt);
@@ -4588,19 +4673,23 @@ var init_dispatcher = __esm({
4588
4673
  if (progressed) {
4589
4674
  this.strikes.delete(jobKey);
4590
4675
  } else {
4591
- consecutive = (this.strikes.get(jobKey)?.count ?? 0) + 1;
4676
+ const prev = this.strikes.get(jobKey);
4677
+ consecutive = (prev?.count ?? 0) + 1;
4592
4678
  const max = this.opts.backoff?.maxConsecutiveFailures ?? 3;
4593
4679
  const base = this.opts.backoff?.baseMs ?? 3e4;
4594
- const fused = consecutive >= max;
4680
+ const cap = this.opts.backoff?.maxBackoffMs ?? 30 * 6e4;
4681
+ const fused = consecutive >= max && !isSelfHealingExit(info.reason);
4595
4682
  this.strikes.set(jobKey, {
4596
4683
  artifactId: spec.artifactId,
4597
4684
  actor: spec.actor,
4598
4685
  action: spec.action,
4599
4686
  count: consecutive,
4600
- nextEligibleAt: Date.now() + base * 2 ** (consecutive - 1),
4687
+ // 退避封顶(maxBackoffMs,默认 30min):自愈类不熔断→consecutive 可无界增长,不封顶则间隔爆炸。
4688
+ nextEligibleAt: Date.now() + Math.min(base * 2 ** (consecutive - 1), cap),
4601
4689
  fused,
4602
4690
  ...fused ? { fusedAt: (/* @__PURE__ */ new Date()).toISOString() } : {},
4603
4691
  seqAtVerdict: seqAtExit,
4692
+ firstFailedAt: prev?.firstFailedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4604
4693
  ...info.reason ? { lastReason: info.reason } : {}
4605
4694
  });
4606
4695
  if (fused) {
@@ -4981,22 +5070,22 @@ var init_ci_provider = __esm({
4981
5070
  const { stdout } = await execFile("git", args, { cwd: this.repoDir(repo), env: process.env });
4982
5071
  return stdout.trim();
4983
5072
  }
4984
- async ensurePr(ref, meta) {
4985
- if (!ref.repo.remote) throw new Error(`repo ${ref.repo.id} \u65E0 remote\uFF0C\u65E0\u6CD5\u5F00 PR`);
4986
- await this.git(ref.repo, ["fetch", "--quiet", "--prune", "origin"]);
4987
- await this.git(ref.repo, ["push", "--quiet", "--force-with-lease", "origin", `${ref.sha}:refs/heads/${ref.branch}`]);
4988
- const existing = await this.gh(ref.repo, ["pr", "list", "--head", ref.branch, "--base", ref.repo.develop, "--state", "open", "--json", "number,url"]);
5073
+ async ensurePr(ref2, meta) {
5074
+ if (!ref2.repo.remote) throw new Error(`repo ${ref2.repo.id} \u65E0 remote\uFF0C\u65E0\u6CD5\u5F00 PR`);
5075
+ await this.git(ref2.repo, ["fetch", "--quiet", "--prune", "origin"]);
5076
+ await this.git(ref2.repo, ["push", "--quiet", "--force-with-lease", "origin", `${ref2.sha}:refs/heads/${ref2.branch}`]);
5077
+ const existing = await this.gh(ref2.repo, ["pr", "list", "--head", ref2.branch, "--base", ref2.repo.develop, "--state", "open", "--json", "number,url"]);
4989
5078
  const list = JSON.parse(existing || "[]");
4990
5079
  if (list[0]) return list[0];
4991
- const url = await this.gh(ref.repo, ["pr", "create", "--base", ref.repo.develop, "--head", ref.branch, "--title", meta.title, "--body", meta.body]);
4992
- const created = JSON.parse(await this.gh(ref.repo, ["pr", "view", ref.branch, "--json", "number,url"]));
4993
- this.opts.log?.(`[ci] opened PR ${created.url} (${ref.branch} \u2192 ${ref.repo.develop})`);
5080
+ const url = await this.gh(ref2.repo, ["pr", "create", "--base", ref2.repo.develop, "--head", ref2.branch, "--title", meta.title, "--body", meta.body]);
5081
+ const created = JSON.parse(await this.gh(ref2.repo, ["pr", "view", ref2.branch, "--json", "number,url"]));
5082
+ this.opts.log?.(`[ci] opened PR ${created.url} (${ref2.branch} \u2192 ${ref2.repo.develop})`);
4994
5083
  return created.url ? created : { number: created.number, url };
4995
5084
  }
4996
- async status(ref) {
5085
+ async status(ref2) {
4997
5086
  let raw;
4998
5087
  try {
4999
- raw = await this.gh(ref.repo, ["pr", "view", ref.branch, "--json", "statusCheckRollup,createdAt"]);
5088
+ raw = await this.gh(ref2.repo, ["pr", "view", ref2.branch, "--json", "statusCheckRollup,createdAt"]);
5000
5089
  } catch (e) {
5001
5090
  return { state: "pending", summary: `\u8BFB PR \u72B6\u6001\u5931\u8D25\uFF08\u4E0B\u8F6E\u91CD\u8BD5\uFF09\uFF1A${String(e)}` };
5002
5091
  }
@@ -5007,7 +5096,7 @@ var init_ci_provider = __esm({
5007
5096
  const ageMs = parsed.createdAt ? Date.now() - Date.parse(parsed.createdAt) : Infinity;
5008
5097
  if (ageMs < grace) return { state: "pending" };
5009
5098
  if (this.opts.failClosedWhenNoChecks) return { state: "failure", summary: "\u8BE5\u4ED3\u672A\u914D\u4EFB\u4F55 Actions check\uFF08fail-closed\uFF09\u2014\u2014\u8BF7\u8865 CI \u6216\u6539\u914D\u7F6E" };
5010
- this.opts.log?.(`[ci] ${ref.repo.id} PR ${ref.branch} \u5F00\u4E86 ${Math.round(ageMs / 1e3)}s \u4ECD\u65E0 check\uFF0C\u5224\u65E0 CI\u3001\u95F8\u7A7A\u8FC7\uFF08\u544A\u8B66\uFF09`);
5099
+ this.opts.log?.(`[ci] ${ref2.repo.id} PR ${ref2.branch} \u5F00\u4E86 ${Math.round(ageMs / 1e3)}s \u4ECD\u65E0 check\uFF0C\u5224\u65E0 CI\u3001\u95F8\u7A7A\u8FC7\uFF08\u544A\u8B66\uFF09`);
5011
5100
  return { state: "success", summary: "\u65E0 CI check\uFF0C\u95F8\u7A7A\u8FC7" };
5012
5101
  }
5013
5102
  const failed = [];
@@ -5029,7 +5118,7 @@ var init_ci_provider = __esm({
5029
5118
  const url = c.detailsUrl ?? c.targetUrl ?? "";
5030
5119
  let block = ` - ${name} \u2192 ${concl}${url ? `
5031
5120
  ${url}` : ""}`;
5032
- const tail = await this.failedLogTail(ref.repo, url);
5121
+ const tail = await this.failedLogTail(ref2.repo, url);
5033
5122
  if (tail) block += `
5034
5123
  \u5931\u8D25\u65E5\u5FD7\uFF08\u672B\u5C3E\uFF09\uFF1A
5035
5124
  ${tail.split("\n").map((l) => " " + l).join("\n")}`;
@@ -5052,20 +5141,20 @@ ${blocks.join("\n")}` };
5052
5141
  return "";
5053
5142
  }
5054
5143
  }
5055
- async merge(ref) {
5144
+ async merge(ref2) {
5056
5145
  try {
5057
- await this.gh(ref.repo, ["pr", "merge", ref.branch, "--merge", "--delete-branch"]);
5058
- this.opts.log?.(`[ci] merged PR ${ref.branch} \u2192 ${ref.repo.develop}`);
5146
+ await this.gh(ref2.repo, ["pr", "merge", ref2.branch, "--merge", "--delete-branch"]);
5147
+ this.opts.log?.(`[ci] merged PR ${ref2.branch} \u2192 ${ref2.repo.develop}`);
5059
5148
  return { merged: true };
5060
5149
  } catch (e) {
5061
5150
  return { merged: false, reason: String(e) };
5062
5151
  }
5063
5152
  }
5064
- async isMerged(ref) {
5065
- if (!ref.repo.remote) return false;
5153
+ async isMerged(ref2) {
5154
+ if (!ref2.repo.remote) return false;
5066
5155
  try {
5067
- await this.git(ref.repo, ["fetch", "--quiet", "origin", ref.repo.develop]);
5068
- await this.git(ref.repo, ["merge-base", "--is-ancestor", ref.sha, `origin/${ref.repo.develop}`]);
5156
+ await this.git(ref2.repo, ["fetch", "--quiet", "origin", ref2.repo.develop]);
5157
+ await this.git(ref2.repo, ["merge-base", "--is-ancestor", ref2.sha, `origin/${ref2.repo.develop}`]);
5069
5158
  return true;
5070
5159
  } catch {
5071
5160
  return false;
@@ -5092,8 +5181,8 @@ function collectUmbrellaCommits(repos, featBranch, env) {
5092
5181
  const refs = /* @__PURE__ */ new Map();
5093
5182
  for (const line of ls.trim().split("\n")) {
5094
5183
  if (!line) continue;
5095
- const [sha2, ref] = line.split(" ");
5096
- if (sha2 && ref) refs.set(ref, sha2);
5184
+ const [sha2, ref2] = line.split(" ");
5185
+ if (sha2 && ref2) refs.set(ref2, sha2);
5097
5186
  }
5098
5187
  const featSha = refs.get(`refs/heads/${featBranch}`);
5099
5188
  const devSha = refs.get(`refs/heads/${r.develop}`);
@@ -5168,9 +5257,9 @@ function autoMergeWorkIntoFeat(args) {
5168
5257
  }
5169
5258
  }
5170
5259
  }
5171
- const has = (ref) => {
5260
+ const has = (ref2) => {
5172
5261
  try {
5173
- git(["rev-parse", "--verify", "--quiet", ref]);
5262
+ git(["rev-parse", "--verify", "--quiet", ref2]);
5174
5263
  return true;
5175
5264
  } catch {
5176
5265
  return false;
@@ -12662,7 +12751,7 @@ function intersection(left, right) {
12662
12751
  right
12663
12752
  });
12664
12753
  }
12665
- function record(keyType, valueType, params) {
12754
+ function record2(keyType, valueType, params) {
12666
12755
  return new ZodRecord2({
12667
12756
  type: "record",
12668
12757
  keyType,
@@ -13471,7 +13560,7 @@ var init_types2 = __esm({
13471
13560
  });
13472
13561
  FormElicitationCapabilitySchema = intersection(object({
13473
13562
  applyDefaults: boolean2().optional()
13474
- }), record(string2(), unknown()));
13563
+ }), record2(string2(), unknown()));
13475
13564
  ElicitationCapabilitySchema = preprocess((value) => {
13476
13565
  if (value && typeof value === "object" && !Array.isArray(value)) {
13477
13566
  if (Object.keys(value).length === 0) {
@@ -13482,7 +13571,7 @@ var init_types2 = __esm({
13482
13571
  }, intersection(object({
13483
13572
  form: FormElicitationCapabilitySchema.optional(),
13484
13573
  url: AssertObjectSchema.optional()
13485
- }), record(string2(), unknown()).optional()));
13574
+ }), record2(string2(), unknown()).optional()));
13486
13575
  ClientTasksCapabilitySchema = looseObject({
13487
13576
  /**
13488
13577
  * Present if the client supports listing tasks.
@@ -13535,7 +13624,7 @@ var init_types2 = __esm({
13535
13624
  /**
13536
13625
  * Experimental, non-standard capabilities that the client supports.
13537
13626
  */
13538
- experimental: record(string2(), AssertObjectSchema).optional(),
13627
+ experimental: record2(string2(), AssertObjectSchema).optional(),
13539
13628
  /**
13540
13629
  * Present if the client supports sampling from an LLM.
13541
13630
  */
@@ -13570,7 +13659,7 @@ var init_types2 = __esm({
13570
13659
  /**
13571
13660
  * Extensions that the client supports. Keys are extension identifiers (vendor-prefix/extension-name).
13572
13661
  */
13573
- extensions: record(string2(), AssertObjectSchema).optional()
13662
+ extensions: record2(string2(), AssertObjectSchema).optional()
13574
13663
  });
13575
13664
  InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
13576
13665
  /**
@@ -13588,7 +13677,7 @@ var init_types2 = __esm({
13588
13677
  /**
13589
13678
  * Experimental, non-standard capabilities that the server supports.
13590
13679
  */
13591
- experimental: record(string2(), AssertObjectSchema).optional(),
13680
+ experimental: record2(string2(), AssertObjectSchema).optional(),
13592
13681
  /**
13593
13682
  * Present if the server supports sending log messages to the client.
13594
13683
  */
@@ -13635,7 +13724,7 @@ var init_types2 = __esm({
13635
13724
  /**
13636
13725
  * Extensions that the server supports. Keys are extension identifiers (vendor-prefix/extension-name).
13637
13726
  */
13638
- extensions: record(string2(), AssertObjectSchema).optional()
13727
+ extensions: record2(string2(), AssertObjectSchema).optional()
13639
13728
  });
13640
13729
  InitializeResultSchema = ResultSchema.extend({
13641
13730
  /**
@@ -13773,7 +13862,7 @@ var init_types2 = __esm({
13773
13862
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
13774
13863
  * for notes on _meta usage.
13775
13864
  */
13776
- _meta: record(string2(), unknown()).optional()
13865
+ _meta: record2(string2(), unknown()).optional()
13777
13866
  });
13778
13867
  TextResourceContentsSchema = ResourceContentsSchema.extend({
13779
13868
  /**
@@ -13967,7 +14056,7 @@ var init_types2 = __esm({
13967
14056
  /**
13968
14057
  * Arguments to use for templating the prompt.
13969
14058
  */
13970
- arguments: record(string2(), string2()).optional()
14059
+ arguments: record2(string2(), string2()).optional()
13971
14060
  });
13972
14061
  GetPromptRequestSchema = RequestSchema.extend({
13973
14062
  method: literal("prompts/get"),
@@ -13987,7 +14076,7 @@ var init_types2 = __esm({
13987
14076
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
13988
14077
  * for notes on _meta usage.
13989
14078
  */
13990
- _meta: record(string2(), unknown()).optional()
14079
+ _meta: record2(string2(), unknown()).optional()
13991
14080
  });
13992
14081
  ImageContentSchema = object({
13993
14082
  type: literal("image"),
@@ -14007,7 +14096,7 @@ var init_types2 = __esm({
14007
14096
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14008
14097
  * for notes on _meta usage.
14009
14098
  */
14010
- _meta: record(string2(), unknown()).optional()
14099
+ _meta: record2(string2(), unknown()).optional()
14011
14100
  });
14012
14101
  AudioContentSchema = object({
14013
14102
  type: literal("audio"),
@@ -14027,7 +14116,7 @@ var init_types2 = __esm({
14027
14116
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14028
14117
  * for notes on _meta usage.
14029
14118
  */
14030
- _meta: record(string2(), unknown()).optional()
14119
+ _meta: record2(string2(), unknown()).optional()
14031
14120
  });
14032
14121
  ToolUseContentSchema = object({
14033
14122
  type: literal("tool_use"),
@@ -14045,12 +14134,12 @@ var init_types2 = __esm({
14045
14134
  * Arguments to pass to the tool.
14046
14135
  * Must conform to the tool's inputSchema.
14047
14136
  */
14048
- input: record(string2(), unknown()),
14137
+ input: record2(string2(), unknown()),
14049
14138
  /**
14050
14139
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14051
14140
  * for notes on _meta usage.
14052
14141
  */
14053
- _meta: record(string2(), unknown()).optional()
14142
+ _meta: record2(string2(), unknown()).optional()
14054
14143
  });
14055
14144
  EmbeddedResourceSchema = object({
14056
14145
  type: literal("resource"),
@@ -14063,7 +14152,7 @@ var init_types2 = __esm({
14063
14152
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14064
14153
  * for notes on _meta usage.
14065
14154
  */
14066
- _meta: record(string2(), unknown()).optional()
14155
+ _meta: record2(string2(), unknown()).optional()
14067
14156
  });
14068
14157
  ResourceLinkSchema = ResourceSchema.extend({
14069
14158
  type: literal("resource_link")
@@ -14153,7 +14242,7 @@ var init_types2 = __esm({
14153
14242
  */
14154
14243
  inputSchema: object({
14155
14244
  type: literal("object"),
14156
- properties: record(string2(), AssertObjectSchema).optional(),
14245
+ properties: record2(string2(), AssertObjectSchema).optional(),
14157
14246
  required: array(string2()).optional()
14158
14247
  }).catchall(unknown()),
14159
14248
  /**
@@ -14163,7 +14252,7 @@ var init_types2 = __esm({
14163
14252
  */
14164
14253
  outputSchema: object({
14165
14254
  type: literal("object"),
14166
- properties: record(string2(), AssertObjectSchema).optional(),
14255
+ properties: record2(string2(), AssertObjectSchema).optional(),
14167
14256
  required: array(string2()).optional()
14168
14257
  }).catchall(unknown()).optional(),
14169
14258
  /**
@@ -14178,7 +14267,7 @@ var init_types2 = __esm({
14178
14267
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14179
14268
  * for notes on _meta usage.
14180
14269
  */
14181
- _meta: record(string2(), unknown()).optional()
14270
+ _meta: record2(string2(), unknown()).optional()
14182
14271
  });
14183
14272
  ListToolsRequestSchema = PaginatedRequestSchema.extend({
14184
14273
  method: literal("tools/list")
@@ -14199,7 +14288,7 @@ var init_types2 = __esm({
14199
14288
  *
14200
14289
  * If the Tool defines an outputSchema, this field MUST be present in the result, and contain a JSON object that matches the schema.
14201
14290
  */
14202
- structuredContent: record(string2(), unknown()).optional(),
14291
+ structuredContent: record2(string2(), unknown()).optional(),
14203
14292
  /**
14204
14293
  * Whether the tool call ended in an error.
14205
14294
  *
@@ -14227,7 +14316,7 @@ var init_types2 = __esm({
14227
14316
  /**
14228
14317
  * Arguments to pass to the tool.
14229
14318
  */
14230
- arguments: record(string2(), unknown()).optional()
14319
+ arguments: record2(string2(), unknown()).optional()
14231
14320
  });
14232
14321
  CallToolRequestSchema = RequestSchema.extend({
14233
14322
  method: literal("tools/call"),
@@ -14329,7 +14418,7 @@ var init_types2 = __esm({
14329
14418
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14330
14419
  * for notes on _meta usage.
14331
14420
  */
14332
- _meta: record(string2(), unknown()).optional()
14421
+ _meta: record2(string2(), unknown()).optional()
14333
14422
  });
14334
14423
  SamplingContentSchema = discriminatedUnion("type", [TextContentSchema, ImageContentSchema, AudioContentSchema]);
14335
14424
  SamplingMessageContentBlockSchema = discriminatedUnion("type", [
@@ -14346,7 +14435,7 @@ var init_types2 = __esm({
14346
14435
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14347
14436
  * for notes on _meta usage.
14348
14437
  */
14349
- _meta: record(string2(), unknown()).optional()
14438
+ _meta: record2(string2(), unknown()).optional()
14350
14439
  });
14351
14440
  CreateMessageRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({
14352
14441
  messages: array(SamplingMessageSchema),
@@ -14534,7 +14623,7 @@ var init_types2 = __esm({
14534
14623
  */
14535
14624
  requestedSchema: object({
14536
14625
  type: literal("object"),
14537
- properties: record(string2(), PrimitiveSchemaDefinitionSchema),
14626
+ properties: record2(string2(), PrimitiveSchemaDefinitionSchema),
14538
14627
  required: array(string2()).optional()
14539
14628
  })
14540
14629
  });
@@ -14586,7 +14675,7 @@ var init_types2 = __esm({
14586
14675
  * Per MCP spec, content is "typically omitted" for decline/cancel actions.
14587
14676
  * We normalize null to undefined for leniency while maintaining type compatibility.
14588
14677
  */
14589
- content: preprocess((val) => val === null ? void 0 : val, record(string2(), union([string2(), number2(), boolean2(), array(string2())])).optional())
14678
+ content: preprocess((val) => val === null ? void 0 : val, record2(string2(), union([string2(), number2(), boolean2(), array(string2())])).optional())
14590
14679
  });
14591
14680
  ResourceTemplateReferenceSchema = object({
14592
14681
  type: literal("ref/resource"),
@@ -14621,7 +14710,7 @@ var init_types2 = __esm({
14621
14710
  /**
14622
14711
  * Previously-resolved variables in a URI template or prompt.
14623
14712
  */
14624
- arguments: record(string2(), string2()).optional()
14713
+ arguments: record2(string2(), string2()).optional()
14625
14714
  }).optional()
14626
14715
  });
14627
14716
  CompleteRequestSchema = RequestSchema.extend({
@@ -14657,7 +14746,7 @@ var init_types2 = __esm({
14657
14746
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
14658
14747
  * for notes on _meta usage.
14659
14748
  */
14660
- _meta: record(string2(), unknown()).optional()
14749
+ _meta: record2(string2(), unknown()).optional()
14661
14750
  });
14662
14751
  ListRootsRequestSchema = RequestSchema.extend({
14663
14752
  method: literal("roots/list"),
@@ -17306,20 +17395,20 @@ var require_resolve = __commonJS({
17306
17395
  return false;
17307
17396
  }
17308
17397
  function countKeys(schema) {
17309
- let count = 0;
17398
+ let count2 = 0;
17310
17399
  for (const key in schema) {
17311
17400
  if (key === "$ref")
17312
17401
  return Infinity;
17313
- count++;
17402
+ count2++;
17314
17403
  if (SIMPLE_INLINED.has(key))
17315
17404
  continue;
17316
17405
  if (typeof schema[key] == "object") {
17317
- (0, util_1.eachItem)(schema[key], (sch) => count += countKeys(sch));
17406
+ (0, util_1.eachItem)(schema[key], (sch) => count2 += countKeys(sch));
17318
17407
  }
17319
- if (count === Infinity)
17408
+ if (count2 === Infinity)
17320
17409
  return Infinity;
17321
17410
  }
17322
- return count;
17411
+ return count2;
17323
17412
  }
17324
17413
  function getFullPath(resolver, id = "", normalize) {
17325
17414
  if (normalize !== false)
@@ -17363,26 +17452,26 @@ var require_resolve = __commonJS({
17363
17452
  addAnchor.call(this, sch.$anchor);
17364
17453
  addAnchor.call(this, sch.$dynamicAnchor);
17365
17454
  baseIds[jsonPtr] = innerBaseId;
17366
- function addRef(ref) {
17455
+ function addRef(ref2) {
17367
17456
  const _resolve = this.opts.uriResolver.resolve;
17368
- ref = normalizeId(innerBaseId ? _resolve(innerBaseId, ref) : ref);
17369
- if (schemaRefs.has(ref))
17370
- throw ambiguos(ref);
17371
- schemaRefs.add(ref);
17372
- let schOrRef = this.refs[ref];
17457
+ ref2 = normalizeId(innerBaseId ? _resolve(innerBaseId, ref2) : ref2);
17458
+ if (schemaRefs.has(ref2))
17459
+ throw ambiguos(ref2);
17460
+ schemaRefs.add(ref2);
17461
+ let schOrRef = this.refs[ref2];
17373
17462
  if (typeof schOrRef == "string")
17374
17463
  schOrRef = this.refs[schOrRef];
17375
17464
  if (typeof schOrRef == "object") {
17376
- checkAmbiguosRef(sch, schOrRef.schema, ref);
17377
- } else if (ref !== normalizeId(fullPath)) {
17378
- if (ref[0] === "#") {
17379
- checkAmbiguosRef(sch, localRefs[ref], ref);
17380
- localRefs[ref] = sch;
17465
+ checkAmbiguosRef(sch, schOrRef.schema, ref2);
17466
+ } else if (ref2 !== normalizeId(fullPath)) {
17467
+ if (ref2[0] === "#") {
17468
+ checkAmbiguosRef(sch, localRefs[ref2], ref2);
17469
+ localRefs[ref2] = sch;
17381
17470
  } else {
17382
- this.refs[ref] = fullPath;
17471
+ this.refs[ref2] = fullPath;
17383
17472
  }
17384
17473
  }
17385
- return ref;
17474
+ return ref2;
17386
17475
  }
17387
17476
  function addAnchor(anchor) {
17388
17477
  if (typeof anchor == "string") {
@@ -17393,12 +17482,12 @@ var require_resolve = __commonJS({
17393
17482
  }
17394
17483
  });
17395
17484
  return localRefs;
17396
- function checkAmbiguosRef(sch1, sch2, ref) {
17485
+ function checkAmbiguosRef(sch1, sch2, ref2) {
17397
17486
  if (sch2 !== void 0 && !equal(sch1, sch2))
17398
- throw ambiguos(ref);
17487
+ throw ambiguos(ref2);
17399
17488
  }
17400
- function ambiguos(ref) {
17401
- return new Error(`reference "${ref}" resolves to more than one schema`);
17489
+ function ambiguos(ref2) {
17490
+ return new Error(`reference "${ref2}" resolves to more than one schema`);
17402
17491
  }
17403
17492
  }
17404
17493
  exports2.getSchemaRefs = getSchemaRefs;
@@ -17936,9 +18025,9 @@ var require_ref_error = __commonJS({
17936
18025
  Object.defineProperty(exports2, "__esModule", { value: true });
17937
18026
  var resolve_1 = require_resolve();
17938
18027
  var MissingRefError = class extends Error {
17939
- constructor(resolver, baseId, ref, msg) {
17940
- super(msg || `can't resolve reference ${ref} from id ${baseId}`);
17941
- this.missingRef = (0, resolve_1.resolveUrl)(resolver, baseId, ref);
18028
+ constructor(resolver, baseId, ref2, msg) {
18029
+ super(msg || `can't resolve reference ${ref2} from id ${baseId}`);
18030
+ this.missingRef = (0, resolve_1.resolveUrl)(resolver, baseId, ref2);
17942
18031
  this.missingSchema = (0, resolve_1.normalizeId)((0, resolve_1.getFullPath)(resolver, this.missingRef));
17943
18032
  }
17944
18033
  };
@@ -18064,22 +18153,22 @@ var require_compile = __commonJS({
18064
18153
  }
18065
18154
  }
18066
18155
  exports2.compileSchema = compileSchema;
18067
- function resolveRef2(root, baseId, ref) {
18156
+ function resolveRef2(root, baseId, ref2) {
18068
18157
  var _a;
18069
- ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
18070
- const schOrFunc = root.refs[ref];
18158
+ ref2 = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref2);
18159
+ const schOrFunc = root.refs[ref2];
18071
18160
  if (schOrFunc)
18072
18161
  return schOrFunc;
18073
- let _sch = resolve5.call(this, root, ref);
18162
+ let _sch = resolve5.call(this, root, ref2);
18074
18163
  if (_sch === void 0) {
18075
- const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
18164
+ const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref2];
18076
18165
  const { schemaId } = this.opts;
18077
18166
  if (schema)
18078
18167
  _sch = new SchemaEnv({ schema, schemaId, root, baseId });
18079
18168
  }
18080
18169
  if (_sch === void 0)
18081
18170
  return;
18082
- return root.refs[ref] = inlineOrCompile.call(this, _sch);
18171
+ return root.refs[ref2] = inlineOrCompile.call(this, _sch);
18083
18172
  }
18084
18173
  exports2.resolveRef = resolveRef2;
18085
18174
  function inlineOrCompile(sch) {
@@ -18097,14 +18186,14 @@ var require_compile = __commonJS({
18097
18186
  function sameSchemaEnv(s1, s2) {
18098
18187
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
18099
18188
  }
18100
- function resolve5(root, ref) {
18189
+ function resolve5(root, ref2) {
18101
18190
  let sch;
18102
- while (typeof (sch = this.refs[ref]) == "string")
18103
- ref = sch;
18104
- return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
18191
+ while (typeof (sch = this.refs[ref2]) == "string")
18192
+ ref2 = sch;
18193
+ return sch || this.schemas[ref2] || resolveSchema.call(this, root, ref2);
18105
18194
  }
18106
- function resolveSchema(root, ref) {
18107
- const p2 = this.opts.uriResolver.parse(ref);
18195
+ function resolveSchema(root, ref2) {
18196
+ const p2 = this.opts.uriResolver.parse(ref2);
18108
18197
  const refPath = (0, resolve_1._getFullPath)(this.opts.uriResolver, p2);
18109
18198
  let baseId = (0, resolve_1.getFullPath)(this.opts.uriResolver, root.baseId, void 0);
18110
18199
  if (Object.keys(root.schema).length > 0 && refPath === baseId) {
@@ -18122,7 +18211,7 @@ var require_compile = __commonJS({
18122
18211
  return;
18123
18212
  if (!schOrRef.validate)
18124
18213
  compileSchema.call(this, schOrRef);
18125
- if (id === (0, resolve_1.normalizeId)(ref)) {
18214
+ if (id === (0, resolve_1.normalizeId)(ref2)) {
18126
18215
  const { schema } = schOrRef;
18127
18216
  const { schemaId } = this.opts;
18128
18217
  const schId = schema[schemaId];
@@ -19209,26 +19298,26 @@ var require_core = __commonJS({
19209
19298
  return _compileAsync.call(this, sch);
19210
19299
  }
19211
19300
  }
19212
- function checkLoaded({ missingSchema: ref, missingRef }) {
19213
- if (this.refs[ref]) {
19214
- throw new Error(`AnySchema ${ref} is loaded but ${missingRef} cannot be resolved`);
19301
+ function checkLoaded({ missingSchema: ref2, missingRef }) {
19302
+ if (this.refs[ref2]) {
19303
+ throw new Error(`AnySchema ${ref2} is loaded but ${missingRef} cannot be resolved`);
19215
19304
  }
19216
19305
  }
19217
- async function loadMissingSchema(ref) {
19218
- const _schema = await _loadSchema.call(this, ref);
19219
- if (!this.refs[ref])
19306
+ async function loadMissingSchema(ref2) {
19307
+ const _schema = await _loadSchema.call(this, ref2);
19308
+ if (!this.refs[ref2])
19220
19309
  await loadMetaSchema.call(this, _schema.$schema);
19221
- if (!this.refs[ref])
19222
- this.addSchema(_schema, ref, meta);
19310
+ if (!this.refs[ref2])
19311
+ this.addSchema(_schema, ref2, meta);
19223
19312
  }
19224
- async function _loadSchema(ref) {
19225
- const p2 = this._loading[ref];
19313
+ async function _loadSchema(ref2) {
19314
+ const p2 = this._loading[ref2];
19226
19315
  if (p2)
19227
19316
  return p2;
19228
19317
  try {
19229
- return await (this._loading[ref] = loadSchema(ref));
19318
+ return await (this._loading[ref2] = loadSchema(ref2));
19230
19319
  } finally {
19231
- delete this._loading[ref];
19320
+ delete this._loading[ref2];
19232
19321
  }
19233
19322
  }
19234
19323
  }
@@ -20491,8 +20580,8 @@ var require_contains = __commonJS({
20491
20580
  cxt.result(valid, () => cxt.reset());
20492
20581
  function validateItemsWithCount() {
20493
20582
  const schValid = gen.name("_valid");
20494
- const count = gen.let("count", 0);
20495
- validateItems(schValid, () => gen.if(schValid, () => checkLimits(count)));
20583
+ const count2 = gen.let("count", 0);
20584
+ validateItems(schValid, () => gen.if(schValid, () => checkLimits(count2)));
20496
20585
  }
20497
20586
  function validateItems(_valid, block) {
20498
20587
  gen.forRange("i", 0, len, (i) => {
@@ -20505,16 +20594,16 @@ var require_contains = __commonJS({
20505
20594
  block();
20506
20595
  });
20507
20596
  }
20508
- function checkLimits(count) {
20509
- gen.code((0, codegen_1._)`${count}++`);
20597
+ function checkLimits(count2) {
20598
+ gen.code((0, codegen_1._)`${count2}++`);
20510
20599
  if (max === void 0) {
20511
- gen.if((0, codegen_1._)`${count} >= ${min2}`, () => gen.assign(valid, true).break());
20600
+ gen.if((0, codegen_1._)`${count2} >= ${min2}`, () => gen.assign(valid, true).break());
20512
20601
  } else {
20513
- gen.if((0, codegen_1._)`${count} > ${max}`, () => gen.assign(valid, false).break());
20602
+ gen.if((0, codegen_1._)`${count2} > ${max}`, () => gen.assign(valid, false).break());
20514
20603
  if (min2 === 1)
20515
20604
  gen.assign(valid, true);
20516
20605
  else
20517
- gen.if((0, codegen_1._)`${count} >= ${min2}`, () => gen.assign(valid, true));
20606
+ gen.if((0, codegen_1._)`${count2} >= ${min2}`, () => gen.assign(valid, true));
20518
20607
  }
20519
20608
  }
20520
20609
  }
@@ -21387,12 +21476,12 @@ var require_discriminator = __commonJS({
21387
21476
  for (let i = 0; i < oneOf.length; i++) {
21388
21477
  let sch = oneOf[i];
21389
21478
  if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
21390
- const ref = sch.$ref;
21391
- sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
21479
+ const ref2 = sch.$ref;
21480
+ sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref2);
21392
21481
  if (sch instanceof compile_1.SchemaEnv)
21393
21482
  sch = sch.schema;
21394
21483
  if (sch === void 0)
21395
- throw new ref_error_1.default(it.opts.uriResolver, it.baseId, ref);
21484
+ throw new ref_error_1.default(it.opts.uriResolver, it.baseId, ref2);
21396
21485
  }
21397
21486
  const propSch = (_a = sch === null || sch === void 0 ? void 0 : sch.properties) === null || _a === void 0 ? void 0 : _a[tagName];
21398
21487
  if (typeof propSch != "object") {
@@ -22316,12 +22405,12 @@ var init_workorder_drafts = __esm({
22316
22405
  });
22317
22406
 
22318
22407
  // ../server/src/governance/draft-edit.ts
22319
- function resolveRef(ref, draft) {
22320
- const res = resolveNodeRef(ref, candidatesOf(draft));
22408
+ function resolveRef(ref2, draft) {
22409
+ const res = resolveNodeRef(ref2, candidatesOf(draft));
22321
22410
  if (res.ok) return res.id;
22322
22411
  if (res.reason === "ambiguous")
22323
- throw new Error(`\u300C${ref}\u300D\u5728\u672C\u8349\u6848\u5339\u914D\u5230\u591A\u4E2A\u8282\u70B9\uFF0C\u8BF7\u66F4\u5177\u4F53\uFF08type:title \u6216 id\uFF09\uFF1A${res.candidates.map((c) => `${c.type}:${c.title ?? "?"}`).join("\u3001")}`);
22324
- throw new Error(`\u300C${ref}\u300D\u5728\u672C\u8349\u6848\u91CC\u627E\u4E0D\u5230\u5BF9\u5E94\u8282\u70B9\uFF08\u53EF\u7528 type / type:title / id\uFF09`);
22412
+ throw new Error(`\u300C${ref2}\u300D\u5728\u672C\u8349\u6848\u5339\u914D\u5230\u591A\u4E2A\u8282\u70B9\uFF0C\u8BF7\u66F4\u5177\u4F53\uFF08type:title \u6216 id\uFF09\uFF1A${res.candidates.map((c) => `${c.type}:${c.title ?? "?"}`).join("\u3001")}`);
22413
+ throw new Error(`\u300C${ref2}\u300D\u5728\u672C\u8349\u6848\u91CC\u627E\u4E0D\u5230\u5BF9\u5E94\u8282\u70B9\uFF08\u53EF\u7528 type / type:title / id\uFF09`);
22325
22414
  }
22326
22415
  function editDraftPlan(draft, op) {
22327
22416
  switch (op.action) {
@@ -22454,6 +22543,82 @@ var init_node_state = __esm({
22454
22543
  }
22455
22544
  });
22456
22545
 
22546
+ // ../server/src/domains/collab/stall.ts
22547
+ function shortId(id) {
22548
+ return id.length > 22 ? `${id.slice(0, 14)}\u2026` : id;
22549
+ }
22550
+ function stallOf(model, id, runtime) {
22551
+ const a = model.artifacts.get(id);
22552
+ if (!a) return null;
22553
+ const ownerIsAgent = a.owner.startsWith("actor:agent:");
22554
+ if (runtime?.status === "running") {
22555
+ return { kind: "running", detail: `${runtime.action ?? "produce"} \u5728\u8DD1`, ...runtime.startedAt ? { since: runtime.startedAt } : {} };
22556
+ }
22557
+ if (isHeld(model, id)) {
22558
+ const h = holdOf(model, id);
22559
+ return { kind: "held", action: "resume", detail: `\u5DF2\u6682\u505C\u6D3E\u53D1${h?.reason ? `\uFF1A${h.reason}` : ""}\u2014\u2014\u5F85\u6062\u590D`, ...h?.at ? { since: h.at } : {} };
22560
+ }
22561
+ if (runtime && (runtime.status === "failed" || runtime.status === "fused" || runtime.status === "escalated")) {
22562
+ const label = runtime.status === "escalated" ? "\u5DF2\u5347\u7EA7\u3001\u5F85\u4EBA\u4ECB\u5165" : runtime.status === "fused" ? "\u8FDE\u8D25\u7194\u65AD\u3001\u505C\u6D3E\u5F85\u5904\u7406" : "\u8FD0\u884C\u51FA\u9519";
22563
+ return {
22564
+ kind: "failed",
22565
+ detail: `${label}${runtime.reason ? `\uFF08${runtime.reason}\uFF09` : ""}`,
22566
+ ...runtime.reason ? { reason: runtime.reason } : {},
22567
+ ...runtime.finishedAt ? { since: runtime.finishedAt } : {}
22568
+ };
22569
+ }
22570
+ const head = queueOf(model, id)[0];
22571
+ if (head) {
22572
+ const authors = declaredHumanAuthorsOf(model, head);
22573
+ if (authors.size > 0) {
22574
+ const approved = new Set((model.reviews.get(head.id) ?? []).filter((v2) => v2.verdict === "approve").map((v2) => v2.author));
22575
+ const missing = [...authors].filter((x2) => !approved.has(x2));
22576
+ if (missing.length > 0) {
22577
+ return {
22578
+ kind: "waiting_human",
22579
+ action: "accept",
22580
+ actorId: missing[0],
22581
+ ref: head.id,
22582
+ detail: `\u7B49 ${shortId(missing[0])} \u9A8C\u6536 ${shortId(head.id)} \u7248${missing.length > 1 ? `\uFF08\u5171 ${missing.length} \u4EBA\uFF09` : ""}`,
22583
+ since: head.createdAt
22584
+ };
22585
+ }
22586
+ }
22587
+ }
22588
+ const gate = pendingConcludeAttempt(model, id);
22589
+ if (gate?.gate) {
22590
+ const votedBy = new Set((model.reviews.get(gate.head) ?? []).map((v2) => v2.author));
22591
+ const humanEligible = gate.gate.eligible.filter((r) => r.startsWith("actor:human:") && !votedBy.has(r));
22592
+ if (humanEligible.length > 0) {
22593
+ return { kind: "waiting_human", action: "review", actorId: humanEligible[0], detail: `\u7B49 ${shortId(humanEligible[0])} \u5BA1\u6279\uFF08quorum=${gate.gate.quorum}\uFF09`, since: gate.at };
22594
+ }
22595
+ }
22596
+ if (hasOpenHumanChangeRequest(model, id) || headRejected(model, id)) {
22597
+ return ownerIsAgent ? { kind: "waiting_agent", action: "rework", actorId: a.owner, detail: "\u6709\u672A\u5904\u7406\u7684\u53D8\u66F4\u8BF7\u6C42\uFF0C\u5F85\u8FD4\u5DE5" } : { kind: "waiting_human", action: "rework", actorId: a.owner, detail: "\u6709\u672A\u5904\u7406\u7684\u53D8\u66F4\u8BF7\u6C42\uFF0C\u5F85\u4F60\u8FD4\u5DE5" };
22598
+ }
22599
+ const escs = unresolvedEscalationsOf(model, id);
22600
+ if (escs.length > 0) {
22601
+ return { kind: "waiting_human", action: "discuss", ref: id, detail: `\u5DF2\u4E0A\u62A5\u5F85\u4EBA\u4ECB\u5165\uFF1A${escs.at(-1).reason.slice(0, 60)}`, since: escs.at(-1).at };
22602
+ }
22603
+ const gaps = unresolvedGapsOf(model, id);
22604
+ if (gaps.length > 0) {
22605
+ return { kind: "waiting_human", action: "resolve_gap", detail: `\u5361\u5728\u7F3A\u53E3\u4E0A\u5F85\u4EBA\u8865\uFF1A${gaps[0].description.slice(0, 60)}` };
22606
+ }
22607
+ const b2 = blockedOf(model, id);
22608
+ if (b2.blocked) {
22609
+ const dep = a.inputs.find((e) => e.required && !isConcluded(model, e.to));
22610
+ if (dep) return { kind: "waiting_system", ref: dep.to, detail: `\u7B49\u4E0A\u6E38\u6536\u53E3\uFF1A${shortId(dep.to)}` };
22611
+ return { kind: "waiting_system", detail: `\u963B\u585E\u4E2D\uFF1A${b2.reasons[0] ?? "\u5F85\u524D\u7F6E\u5C31\u7EEA"}` };
22612
+ }
22613
+ return null;
22614
+ }
22615
+ var init_stall = __esm({
22616
+ "../server/src/domains/collab/stall.ts"() {
22617
+ "use strict";
22618
+ init_src2();
22619
+ }
22620
+ });
22621
+
22457
22622
  // ../server/src/coordinator/identity.ts
22458
22623
  function isCoordinatorAgentName(name) {
22459
22624
  return COORDINATOR_NAME_RE.test(name);
@@ -22990,7 +23155,7 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
22990
23155
  arr.push(a);
22991
23156
  byWorkspace.set(a.workspace, arr);
22992
23157
  }
22993
- const ref = (id) => {
23158
+ const ref2 = (id) => {
22994
23159
  const extra = resolveActor?.(id);
22995
23160
  return {
22996
23161
  id,
@@ -23018,8 +23183,8 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
23018
23183
  // 一句话目标:优先取结构化 spec.goal;缺省时前端回退 description 首段(投影不替前端做回退,只给真值)
23019
23184
  ...spec?.goal ? { goal: spec.goal } : {},
23020
23185
  stage,
23021
- owner: ref(seed.owner),
23022
- participants: participantIds.map(ref),
23186
+ owner: ref2(seed.owner),
23187
+ participants: participantIds.map(ref2),
23023
23188
  artifactCount: arts.length,
23024
23189
  progress: { concluded, total: arts.length },
23025
23190
  updatedAt: lastOps[lastOps.length - 1] ?? "",
@@ -23081,7 +23246,7 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
23081
23246
  if (arts.length === 0) return null;
23082
23247
  const summary = buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPausedOf).find((w2) => w2.id === workspaceId);
23083
23248
  if (!summary) return null;
23084
- const ref = (id) => {
23249
+ const ref2 = (id) => {
23085
23250
  const extra = resolveActor?.(id);
23086
23251
  return {
23087
23252
  id,
@@ -23090,7 +23255,7 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
23090
23255
  ...extra?.avatar ? { avatar: extra.avatar } : {}
23091
23256
  };
23092
23257
  };
23093
- const runtimeByArtifact = buildRuntimeByArtifact(dispatchJournal, ref);
23258
+ const runtimeByArtifact = buildRuntimeByArtifact(dispatchJournal, ref2);
23094
23259
  const nodes = arts.map((a) => {
23095
23260
  const info = nodeInfo(model, a);
23096
23261
  const observedRuntime = runtimeByArtifact.get(a.id);
@@ -23105,6 +23270,11 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
23105
23270
  ...e.upstreamCancelled ? { upstreamCancelled: true } : {}
23106
23271
  })) : [];
23107
23272
  const pv = reviewerPreview?.(a.id);
23273
+ const stallRaw = stallOf(model, a.id, runtime);
23274
+ const stall = stallRaw ? (() => {
23275
+ const { actorId, ...rest } = stallRaw;
23276
+ return { ...rest, ...actorId ? { actor: ref2(actorId) } : {} };
23277
+ })() : void 0;
23108
23278
  return {
23109
23279
  id: a.id,
23110
23280
  type: a.type,
@@ -23113,20 +23283,43 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
23113
23283
  currentRev: a.currentRev,
23114
23284
  queue: info.queueLen,
23115
23285
  blocked: blockedOf(model, a.id).blocked,
23116
- owner: ref(a.owner),
23286
+ owner: ref2(a.owner),
23117
23287
  // 封存原因透出:stage 只到 "sealed" 粒度,原因(accepted/cancelled/frozen)另给,供前端区分展示。
23118
23288
  ...lifecycle !== "active" ? { sealedReason: lifecycle.slice("sealed:".length) } : {},
23119
23289
  ...runtime ? { runtime } : {},
23120
- ...pv ? { reviewers: pv.reviewers.map((r) => ({ actor: ref(r.actor), source: r.source })), quorum: pv.quorum, typeName: pv.typeName } : {},
23290
+ ...pv ? { reviewers: pv.reviewers.map((r) => ({ actor: ref2(r.actor), source: r.source })), quorum: pv.quorum, typeName: pv.typeName } : {},
23121
23291
  // 过期事实(提案 rework-cascade-rollout):无条件透出,前端 DAG 挂「上游已更新」徽标。
23122
- ...outdated.length > 0 ? { outdated } : {}
23292
+ ...outdated.length > 0 ? { outdated } : {},
23293
+ // 停滞四态(看得清):为什么不动 + 谁欠什么 + 去处理锚;null 时不带。
23294
+ ...stall ? { stall } : {}
23123
23295
  };
23124
23296
  });
23125
23297
  const edges = arts.flatMap(
23126
23298
  (a) => a.inputs.map((e) => ({ from: a.id, to: e.to, pinned: e.pinned, required: e.required }))
23127
23299
  );
23128
- const activity = buildActivity(model, arts, ref);
23129
- const coordinatorActivity = buildCoordinatorActivity(arts, ops, ref);
23300
+ const stallSummary = (() => {
23301
+ const counts2 = { running: 0, held: 0, failed: 0, waitingHuman: 0, waitingAgent: 0, waitingSystem: 0 };
23302
+ const sev = { failed: 5, held: 4, waiting_human: 3, waiting_agent: 2, waiting_system: 1, running: 0 };
23303
+ const stuck = [];
23304
+ for (const n of nodes) {
23305
+ const st = n.stall;
23306
+ if (!st) continue;
23307
+ if (st.kind === "running") counts2.running++;
23308
+ else if (st.kind === "held") counts2.held++;
23309
+ else if (st.kind === "failed") counts2.failed++;
23310
+ else if (st.kind === "waiting_human") counts2.waitingHuman++;
23311
+ else if (st.kind === "waiting_agent") counts2.waitingAgent++;
23312
+ else counts2.waitingSystem++;
23313
+ if (st.kind !== "running") {
23314
+ stuck.push({ id: n.id, label: n.label, kind: st.kind, ...st.action ? { action: st.action } : {}, ...st.ref ? { ref: st.ref } : {}, detail: st.detail, ...st.actor ? { actor: st.actor } : {} });
23315
+ }
23316
+ }
23317
+ stuck.sort((a, b2) => sev[b2.kind] - sev[a.kind]);
23318
+ const total = counts2.running + counts2.held + counts2.failed + counts2.waitingHuman + counts2.waitingAgent + counts2.waitingSystem;
23319
+ return total > 0 ? { counts: counts2, stuck } : void 0;
23320
+ })();
23321
+ const activity = buildActivity(model, arts, ref2);
23322
+ const coordinatorActivity = buildCoordinatorActivity(arts, ops, ref2);
23130
23323
  const acceptance = buildAcceptance(model, arts);
23131
23324
  const planning = planningStatusOf(model, arts);
23132
23325
  const spec = workorderSpecOf(model, arts);
@@ -23140,11 +23333,12 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
23140
23333
  acceptance,
23141
23334
  ...managerActorId ? { managerActorId } : {},
23142
23335
  ...spec?.acceptanceCriteria ? { acceptanceCriteria: spec.acceptanceCriteria } : {},
23336
+ ...stallSummary ? { stallSummary } : {},
23143
23337
  planning
23144
23338
  };
23145
23339
  }
23146
23340
  function buildWorkorderDraftDetail(draft, resolveActor) {
23147
- const ref = (id) => {
23341
+ const ref2 = (id) => {
23148
23342
  const extra = resolveActor?.(id);
23149
23343
  return { id, ...extra?.name ? { name: extra.name } : {}, ...extra?.role ? { role: extra.role } : {}, ...extra?.avatar ? { avatar: extra.avatar } : {} };
23150
23344
  };
@@ -23157,7 +23351,7 @@ function buildWorkorderDraftDetail(draft, resolveActor) {
23157
23351
  currentRev: null,
23158
23352
  queue: 0,
23159
23353
  blocked: false,
23160
- owner: ref(op.owner ?? draft.draftedBy)
23354
+ owner: ref2(op.owner ?? draft.draftedBy)
23161
23355
  }));
23162
23356
  const edges = [
23163
23357
  ...spawns.flatMap((op) => (op.inputs ?? []).map((inp) => ({ from: op.id, to: inp.to, pinned: null, required: inp.required ?? true }))),
@@ -23172,8 +23366,8 @@ function buildWorkorderDraftDetail(draft, resolveActor) {
23172
23366
  ...draft.seed.brief && draft.seed.brief !== title ? { description: draft.seed.brief } : {},
23173
23367
  ...draft.seed.goal ? { goal: draft.seed.goal } : {},
23174
23368
  stage: "draft",
23175
- owner: ref(draft.draftedBy),
23176
- participants: participantIds.map((id) => ref(id)),
23369
+ owner: ref2(draft.draftedBy),
23370
+ participants: participantIds.map((id) => ref2(id)),
23177
23371
  artifactCount: nodes.length,
23178
23372
  progress: { concluded: 0, total: nodes.length },
23179
23373
  updatedAt: draft.at,
@@ -23221,7 +23415,7 @@ function withRuntimeActivity(model, artifactId, runtime) {
23221
23415
  );
23222
23416
  return lastActivityAt ? { ...runtime, lastActivityAt } : runtime;
23223
23417
  }
23224
- function buildRuntimeByArtifact(entries, ref) {
23418
+ function buildRuntimeByArtifact(entries, ref2) {
23225
23419
  const byJob = /* @__PURE__ */ new Map();
23226
23420
  for (const entry of entries) {
23227
23421
  if (entry.kind === "dispatched") {
@@ -23229,7 +23423,7 @@ function buildRuntimeByArtifact(entries, ref) {
23229
23423
  artifactId: entry.artifactId,
23230
23424
  status: "running",
23231
23425
  action: entry.action,
23232
- actor: ref(entry.actor),
23426
+ actor: ref2(entry.actor),
23233
23427
  startedAt: entry.at,
23234
23428
  at: entry.at
23235
23429
  });
@@ -23239,7 +23433,7 @@ function buildRuntimeByArtifact(entries, ref) {
23239
23433
  artifactId: entry.artifactId,
23240
23434
  status: "running",
23241
23435
  action: entry.action,
23242
- actor: ref(entry.actor),
23436
+ actor: ref2(entry.actor),
23243
23437
  startedAt: prev?.startedAt ?? entry.at,
23244
23438
  ...entry.interruptedAt ? { interruptedAt: entry.interruptedAt } : {},
23245
23439
  recoveredAt: entry.recoveredAt,
@@ -23251,7 +23445,7 @@ function buildRuntimeByArtifact(entries, ref) {
23251
23445
  artifactId: entry.artifactId,
23252
23446
  status: entry.progressed ? "completed" : "failed",
23253
23447
  action: entry.action,
23254
- actor: ref(entry.actor),
23448
+ actor: ref2(entry.actor),
23255
23449
  finishedAt: entry.at,
23256
23450
  durationMs: entry.durationMs,
23257
23451
  ...entry.reason ? { reason: entry.reason } : {},
@@ -23263,7 +23457,7 @@ function buildRuntimeByArtifact(entries, ref) {
23263
23457
  artifactId: entry.artifactId,
23264
23458
  status: "fused",
23265
23459
  action: entry.action,
23266
- actor: ref(entry.actor),
23460
+ actor: ref2(entry.actor),
23267
23461
  finishedAt: entry.at,
23268
23462
  consecutiveFailures: entry.consecutiveFailures,
23269
23463
  at: entry.at
@@ -23272,7 +23466,7 @@ function buildRuntimeByArtifact(entries, ref) {
23272
23466
  byJob.set(`escalated::${entry.artifactId}`, {
23273
23467
  artifactId: entry.artifactId,
23274
23468
  status: "escalated",
23275
- actor: ref(entry.to),
23469
+ actor: ref2(entry.to),
23276
23470
  finishedAt: entry.at,
23277
23471
  reason: entry.reason,
23278
23472
  at: entry.at
@@ -23301,7 +23495,7 @@ function buildRuntimeByArtifact(entries, ref) {
23301
23495
  function activitySummaryNote(note) {
23302
23496
  return note.length <= ACTIVITY_NOTE_MAX_CHARS ? note : `${note.slice(0, ACTIVITY_NOTE_MAX_CHARS)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF0C\u539F\u6587 ${note.length} \u5B57\uFF0C\u5168\u6587\u89C1 oplog\uFF09`;
23303
23497
  }
23304
- function buildActivity(model, arts, ref) {
23498
+ function buildActivity(model, arts, ref2) {
23305
23499
  const events = [];
23306
23500
  for (const a of arts) {
23307
23501
  for (const r of revisionsOf(model, a.id)) {
@@ -23309,7 +23503,7 @@ function buildActivity(model, arts, ref) {
23309
23503
  events.push({
23310
23504
  seq: model.proposeSeq.get(r.id) ?? 0,
23311
23505
  at: r.createdAt,
23312
- actor: ref(r.author),
23506
+ actor: ref2(r.author),
23313
23507
  kind: "propose",
23314
23508
  artifactId: a.id,
23315
23509
  revisionId: r.id,
@@ -23322,7 +23516,7 @@ function buildActivity(model, arts, ref) {
23322
23516
  events.push({
23323
23517
  seq: vote.seq,
23324
23518
  at: vote.at,
23325
- actor: ref(vote.author),
23519
+ actor: ref2(vote.author),
23326
23520
  kind: vote.verdict === "approve" ? "review-approve" : "review-request-changes",
23327
23521
  artifactId: a.id,
23328
23522
  revisionId: r.id,
@@ -23334,7 +23528,7 @@ function buildActivity(model, arts, ref) {
23334
23528
  events.push({
23335
23529
  seq: m2.seq,
23336
23530
  at: m2.at,
23337
- actor: ref(m2.forced?.by ?? a.owner),
23531
+ actor: ref2(m2.forced?.by ?? a.owner),
23338
23532
  kind: "conclude",
23339
23533
  artifactId: a.id,
23340
23534
  revisionId: m2.head,
@@ -23345,7 +23539,7 @@ function buildActivity(model, arts, ref) {
23345
23539
  events.push({
23346
23540
  seq: 0,
23347
23541
  at: annotation.thread[0]?.ts ?? "",
23348
- actor: ref(annotation.author),
23542
+ actor: ref2(annotation.author),
23349
23543
  kind: "annotate",
23350
23544
  artifactId: a.id,
23351
23545
  summary: annotation.body.split("\n").find((line) => line.trim() && !line.startsWith("OASIS_PLANNING_ISSUES ")) ?? "\u6279\u6CE8"
@@ -23354,7 +23548,7 @@ function buildActivity(model, arts, ref) {
23354
23548
  }
23355
23549
  return events.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
23356
23550
  }
23357
- function buildCoordinatorActivity(arts, ops, ref) {
23551
+ function buildCoordinatorActivity(arts, ops, ref2) {
23358
23552
  const inWorkspace = new Set(arts.map((a) => a.id));
23359
23553
  const managerRaw = arts.find((a) => a.type === "brief")?.fields?.["manager"];
23360
23554
  const managerActor = typeof managerRaw === "string" ? managerRaw : null;
@@ -23369,11 +23563,11 @@ function buildCoordinatorActivity(arts, ops, ref) {
23369
23563
  if ((kind === "spawn_artifact" || kind === "link_input") && (!firstRevisionAt || op.timestamp < firstRevisionAt)) {
23370
23564
  continue;
23371
23565
  }
23372
- const detail = coordinatorEditDetail(kind, op.payload, ref);
23566
+ const detail = coordinatorEditDetail(kind, op.payload, ref2);
23373
23567
  edits.push({
23374
23568
  seq: op.seq,
23375
23569
  at: op.timestamp,
23376
- actor: ref(op.actor),
23570
+ actor: ref2(op.actor),
23377
23571
  kind,
23378
23572
  artifactId: op.artifactId,
23379
23573
  label: COORDINATOR_EDIT_LABEL[kind],
@@ -23382,7 +23576,7 @@ function buildCoordinatorActivity(arts, ops, ref) {
23382
23576
  }
23383
23577
  return edits.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
23384
23578
  }
23385
- function coordinatorEditDetail(kind, payload, ref) {
23579
+ function coordinatorEditDetail(kind, payload, ref2) {
23386
23580
  const str2 = (v2) => typeof v2 === "string" && v2.trim() ? v2 : void 0;
23387
23581
  switch (kind) {
23388
23582
  case "link_input":
@@ -23392,7 +23586,7 @@ function coordinatorEditDetail(kind, payload, ref) {
23392
23586
  return str2(payload.title) ?? str2(payload.type);
23393
23587
  case "assign_owner": {
23394
23588
  const owner = str2(payload.owner);
23395
- return owner ? ref(owner).name ?? owner : void 0;
23589
+ return owner ? ref2(owner).name ?? owner : void 0;
23396
23590
  }
23397
23591
  case "bump_pin": {
23398
23592
  const to = str2(payload.to);
@@ -23435,6 +23629,7 @@ var init_workorder_detail = __esm({
23435
23629
  "use strict";
23436
23630
  init_src2();
23437
23631
  init_node_state();
23632
+ init_stall();
23438
23633
  init_workorders();
23439
23634
  ACTIVITY_NOTE_MAX_CHARS = 500;
23440
23635
  COORDINATOR_EDIT_KINDS = /* @__PURE__ */ new Set([
@@ -25177,7 +25372,7 @@ async function startOasisServer(opts) {
25177
25372
  const chatStore = opts.chatSession;
25178
25373
  const ws = review.workspace ?? engine.kernel.model.artifacts.get(review.anchor)?.workspace ?? null;
25179
25374
  let target = review.origin ?? discussSessions.get(`draft:${review.draftId}`) ?? void 0;
25180
- let record5 = target && chatStore ? await chatStore.getSession(target).catch(() => null) : null;
25375
+ let record6 = target && chatStore ? await chatStore.getSession(target).catch(() => null) : null;
25181
25376
  if (!target && chatStore && ws) {
25182
25377
  const brief = [...engine.kernel.model.artifacts.values()].find((x2) => x2.type === "brief" && x2.workspace === ws);
25183
25378
  if (brief) {
@@ -25186,7 +25381,7 @@ async function startOasisServer(opts) {
25186
25381
  const wos = await chatStore.listWorkOrdersForSession(s2.id).catch(() => []);
25187
25382
  if (wos.includes(ws)) {
25188
25383
  target = s2.id;
25189
- record5 = s2;
25384
+ record6 = s2;
25190
25385
  break;
25191
25386
  }
25192
25387
  }
@@ -25197,7 +25392,7 @@ async function startOasisServer(opts) {
25197
25392
  if (chatStore) {
25198
25393
  await chatStore.appendMessage({ id: randomUUID18(), sessionId: target, role: "user", content: message, createdAt: (/* @__PURE__ */ new Date()).toISOString() }).catch(() => void 0);
25199
25394
  }
25200
- const rt = record5?.runtimeSessionId ?? opts.retainedSession?.({ draftId: review.draftId });
25395
+ const rt = record6?.runtimeSessionId ?? opts.retainedSession?.({ draftId: review.draftId });
25201
25396
  const session = await opts.dispatchChat({
25202
25397
  actorId: review.proposedBy,
25203
25398
  message,
@@ -25380,9 +25575,9 @@ async function startOasisServer(opts) {
25380
25575
  if (opts.workorderDrafts) {
25381
25576
  let ws = typeof workspace === "string" && workspace.trim() ? workspace.trim() : void 0;
25382
25577
  if (!ws) {
25383
- const ref = op.artifactId;
25384
- if (typeof ref === "string" && ref.startsWith("artifact:"))
25385
- ws = opts.workorderDrafts.list().find((d) => d.plan.ops.some((o) => o.action === "spawn" && o.id === ref))?.workspace;
25578
+ const ref2 = op.artifactId;
25579
+ if (typeof ref2 === "string" && ref2.startsWith("artifact:"))
25580
+ ws = opts.workorderDrafts.list().find((d) => d.plan.ops.some((o) => o.action === "spawn" && o.id === ref2))?.workspace;
25386
25581
  }
25387
25582
  const draft = ws ? opts.workorderDrafts.forWorkspace(ws) : void 0;
25388
25583
  if (draft) {
@@ -26443,14 +26638,14 @@ var init_tokens = __esm({
26443
26638
  };
26444
26639
  isTokenClaims = (value) => {
26445
26640
  if (value === null || typeof value !== "object") return false;
26446
- const record5 = value;
26447
- if (typeof record5["actor"] !== "string" || !record5["actor"].startsWith("actor:")) return false;
26448
- if (typeof record5["issuedAt"] !== "number" || !Number.isFinite(record5["issuedAt"])) return false;
26449
- if (record5["expiresAt"] !== void 0 && (typeof record5["expiresAt"] !== "number" || !Number.isFinite(record5["expiresAt"]))) return false;
26450
- if (record5["dispatchSeq"] !== void 0 && (typeof record5["dispatchSeq"] !== "number" || !Number.isFinite(record5["dispatchSeq"]))) return false;
26451
- if (record5["scopes"] !== void 0 && (!Array.isArray(record5["scopes"]) || record5["scopes"].some((scope) => typeof scope !== "string"))) return false;
26641
+ const record6 = value;
26642
+ if (typeof record6["actor"] !== "string" || !record6["actor"].startsWith("actor:")) return false;
26643
+ if (typeof record6["issuedAt"] !== "number" || !Number.isFinite(record6["issuedAt"])) return false;
26644
+ if (record6["expiresAt"] !== void 0 && (typeof record6["expiresAt"] !== "number" || !Number.isFinite(record6["expiresAt"]))) return false;
26645
+ if (record6["dispatchSeq"] !== void 0 && (typeof record6["dispatchSeq"] !== "number" || !Number.isFinite(record6["dispatchSeq"]))) return false;
26646
+ if (record6["scopes"] !== void 0 && (!Array.isArray(record6["scopes"]) || record6["scopes"].some((scope) => typeof scope !== "string"))) return false;
26452
26647
  for (const key of ["sessionId", "artifactId", "action", "nodeId", "runtimeKind", "companyId", "chatSessionId"]) {
26453
- if (record5[key] !== void 0 && typeof record5[key] !== "string") return false;
26648
+ if (record6[key] !== void 0 && typeof record6[key] !== "string") return false;
26454
26649
  }
26455
26650
  return true;
26456
26651
  };
@@ -26795,7 +26990,7 @@ var init_memory_trace_store = __esm({
26795
26990
  return { items: page.map((r) => this.decorate(structuredClone(r))), nextCursor };
26796
26991
  }
26797
26992
  async listUsageRuns(filter) {
26798
- return [...this.runs.values()].filter((run) => run.usage !== null && run.startedAt >= filter.from && run.startedAt <= filter.to).map((run) => ({
26993
+ return [...this.runs.values()].filter((run) => run.usage != null && run.startedAt >= filter.from && run.startedAt <= filter.to).map((run) => ({
26799
26994
  actorId: run.actorId,
26800
26995
  artifactId: run.artifactId,
26801
26996
  startedAt: run.startedAt,
@@ -27044,14 +27239,14 @@ var init_memory_trace_store = __esm({
27044
27239
  this.usageAggregates.delete(key);
27045
27240
  }
27046
27241
  }
27047
- let count = 0;
27242
+ let count2 = 0;
27048
27243
  for (const run of this.runs.values()) {
27049
27244
  if (run.usage == null) continue;
27050
27245
  if (run.startedAt < from || run.startedAt >= to) continue;
27051
27246
  await this.updateUsageAggregates(run);
27052
- count++;
27247
+ count2++;
27053
27248
  }
27054
- return count;
27249
+ return count2;
27055
27250
  }
27056
27251
  /** day 滚动:把 hourly 聚合(已存在)按日再汇总——内存版直接从 hourly 行重算 day 行。 */
27057
27252
  async rollupHourlyToDaily(day) {
@@ -27424,6 +27619,19 @@ var init_src3 = __esm({
27424
27619
  }
27425
27620
  });
27426
27621
 
27622
+ // ../contract/src/chat-session.ts
27623
+ function deriveLastTurnQuality(lastAssistant) {
27624
+ const status = lastAssistant?.status;
27625
+ if (status === void 0 || status === "running") return void 0;
27626
+ if (status === "error") return "abnormal";
27627
+ return lastAssistant && lastAssistant.content.trim().length > 0 ? "clean" : "abnormal";
27628
+ }
27629
+ var init_chat_session = __esm({
27630
+ "../contract/src/chat-session.ts"() {
27631
+ "use strict";
27632
+ }
27633
+ });
27634
+
27427
27635
  // ../server/src/domains/projects/projection.ts
27428
27636
  function decodeDocumentDescription(description) {
27429
27637
  if (description === void 0) return {};
@@ -27998,6 +28206,7 @@ var DEFAULT_AGENT_ACTOR, MemoryProjectStateStore, MemoryArtifactStateStore, Memo
27998
28206
  var init_service = __esm({
27999
28207
  "../server/src/domains/projects/service.ts"() {
28000
28208
  "use strict";
28209
+ init_src();
28001
28210
  init_src2();
28002
28211
  init_projection();
28003
28212
  init_workorders();
@@ -28661,9 +28870,9 @@ var init_service = __esm({
28661
28870
  ...input.revision.continuation ? { continuation: input.revision.continuation } : {}
28662
28871
  });
28663
28872
  await this.syncProjection();
28664
- const record5 = await this.artifacts.getRevision(kernelRevision.id);
28665
- if (!record5) throw new Error(`revision projection missing after register: ${kernelRevision.id}`);
28666
- return record5;
28873
+ const record6 = await this.artifacts.getRevision(kernelRevision.id);
28874
+ if (!record6) throw new Error(`revision projection missing after register: ${kernelRevision.id}`);
28875
+ return record6;
28667
28876
  }
28668
28877
  async approveArtifactRevision(input) {
28669
28878
  if (input.actorKind !== "human") throw namedError("AgentReviewForbidden", "agents cannot approve artifact revisions");
@@ -28766,9 +28975,9 @@ var init_service = __esm({
28766
28975
  ...input.revisionId ? { revisionId: input.revisionId } : {}
28767
28976
  });
28768
28977
  await this.syncProjection();
28769
- const record5 = await this.artifacts.getAnnotation(kernelAnnotation.id);
28770
- if (!record5) throw new Error(`annotation projection missing after create: ${kernelAnnotation.id}`);
28771
- return record5;
28978
+ const record6 = await this.artifacts.getAnnotation(kernelAnnotation.id);
28979
+ if (!record6) throw new Error(`annotation projection missing after create: ${kernelAnnotation.id}`);
28980
+ return record6;
28772
28981
  }
28773
28982
  async resolveArtifactAnnotation(input) {
28774
28983
  const artifact = await this.artifacts.getArtifact(input.artifactId);
@@ -28784,11 +28993,16 @@ var init_service = __esm({
28784
28993
  ...input.comment?.trim() ? { note: input.comment.trim() } : {}
28785
28994
  });
28786
28995
  await this.syncProjection();
28787
- const record5 = await this.artifacts.getAnnotation(input.annotationId);
28788
- if (!record5) throw new Error(`annotation projection missing after resolve: ${input.annotationId}`);
28789
- return record5;
28996
+ const record6 = await this.artifacts.getAnnotation(input.annotationId);
28997
+ if (!record6) throw new Error(`annotation projection missing after resolve: ${input.annotationId}`);
28998
+ return record6;
28790
28999
  }
28791
29000
  async registerNodeOutput(input) {
29001
+ const status = input.status ?? "completed";
29002
+ const qualityMetrics = parseWorkorderQualityMetricsPayload(input.payload);
29003
+ if (qualityMetrics && status !== "completed") {
29004
+ throw new Error("invalid qa_quality_metrics payload: quality metrics require status=completed");
29005
+ }
28792
29006
  await this.assertProjectExecutable(input.projectId);
28793
29007
  if (!input.outputId.trim()) throw new Error("output_id is required");
28794
29008
  if (!input.workOrderId.trim()) throw new Error("work_order_id is required");
@@ -28802,7 +29016,7 @@ var init_service = __esm({
28802
29016
  workOrderId: input.workOrderId,
28803
29017
  nodeId: input.nodeId,
28804
29018
  projectId: input.projectId,
28805
- status: input.status ?? "completed",
29019
+ status,
28806
29020
  documents,
28807
29021
  ...input.error ? { error: input.error } : {},
28808
29022
  payload: { ...input.payload ?? {} },
@@ -29024,6 +29238,7 @@ var init_dev_store = __esm({
29024
29238
  path4 = __toESM(require("node:path"), 1);
29025
29239
  init_src();
29026
29240
  init_src3();
29241
+ init_chat_session();
29027
29242
  init_src3();
29028
29243
  init_service();
29029
29244
  NdjsonOplogStore = class _NdjsonOplogStore {
@@ -29446,7 +29661,13 @@ var init_dev_store = __esm({
29446
29661
  this.sessions.set(s2.id, s2);
29447
29662
  }
29448
29663
  async listSessions(humanActorId) {
29449
- return [...this.sessions.values()].filter((s2) => s2.humanActorId === humanActorId).sort((a, b2) => b2.touchedAt.localeCompare(a.touchedAt));
29664
+ return [...this.sessions.values()].filter((s2) => s2.humanActorId === humanActorId).sort((a, b2) => b2.touchedAt.localeCompare(a.touchedAt)).map((s2) => {
29665
+ const msgs = this.messages.get(s2.id) ?? [];
29666
+ let lastAssistant;
29667
+ for (const m2 of msgs) if (m2.role === "assistant" && (!lastAssistant || m2.seq > lastAssistant.seq)) lastAssistant = m2;
29668
+ const quality = deriveLastTurnQuality(lastAssistant);
29669
+ return quality ? { ...s2, lastTurnQuality: quality } : s2;
29670
+ });
29450
29671
  }
29451
29672
  async getSession(id) {
29452
29673
  return this.sessions.get(id) ?? null;
@@ -29463,10 +29684,10 @@ var init_dev_store = __esm({
29463
29684
  async appendMessage(m2) {
29464
29685
  const list = this.messages.get(m2.sessionId) ?? [];
29465
29686
  const seq = (list.length ? Math.max(...list.map((x2) => x2.seq)) : 0) + 1;
29466
- const record5 = { ...m2, seq };
29467
- list.push(record5);
29687
+ const record6 = { ...m2, seq };
29688
+ list.push(record6);
29468
29689
  this.messages.set(m2.sessionId, list);
29469
- return record5;
29690
+ return record6;
29470
29691
  }
29471
29692
  async updateMessage(id, patch) {
29472
29693
  for (const list of this.messages.values()) {
@@ -29524,9 +29745,9 @@ var init_dev_store = __esm({
29524
29745
  this.save();
29525
29746
  }
29526
29747
  async appendMessage(m2) {
29527
- const record5 = await super.appendMessage(m2);
29748
+ const record6 = await super.appendMessage(m2);
29528
29749
  this.save();
29529
- return record5;
29750
+ return record6;
29530
29751
  }
29531
29752
  async updateMessage(id, patch) {
29532
29753
  await super.updateMessage(id, patch);
@@ -33839,6 +34060,10 @@ var init_daemon_hub = __esm({
33839
34060
  connectedDaemons() {
33840
34061
  return [...this.daemons.values()];
33841
34062
  }
34063
+ /** 最近一帧(含 pong 心跳)的时刻(ms)——节点心跳时钟,供节点健康派生判"心跳新鲜"。缺 = 未连接/已清。 */
34064
+ lastSeenAt(daemonId) {
34065
+ return this.lastSeen.get(daemonId);
34066
+ }
33842
34067
  /** Nodes that disconnected within the last GHOST_TTL_MS ms. */
33843
34068
  recentlyDisconnected() {
33844
34069
  const cutoff = Date.now() - _DaemonHub.GHOST_TTL_MS;
@@ -34213,19 +34438,19 @@ var init_service2 = __esm({
34213
34438
  async upsertActor(a, by) {
34214
34439
  const existing = await this.opts.store.getActor(a.id);
34215
34440
  const roles = a.roles ?? existing?.roles ?? [];
34216
- const record5 = { ...a, roles, createdAt: existing?.createdAt ?? a.createdAt ?? this.now() };
34217
- if (isActiveCoordinatorAgent(record5)) {
34218
- const existingCoordinator = (await this.opts.store.listActors({ kind: "agent", status: "active" })).find((actor) => actor.id !== record5.id && isActiveCoordinatorAgent(actor));
34441
+ const record6 = { ...a, roles, createdAt: existing?.createdAt ?? a.createdAt ?? this.now() };
34442
+ if (isActiveCoordinatorAgent(record6)) {
34443
+ const existingCoordinator = (await this.opts.store.listActors({ kind: "agent", status: "active" })).find((actor) => actor.id !== record6.id && isActiveCoordinatorAgent(actor));
34219
34444
  if (existingCoordinator) {
34220
34445
  throw new Error(
34221
- `\u7CFB\u7EDF\u53EA\u80FD\u6709\u4E00\u4E2A\u534F\u8C03\u8005\u667A\u80FD\u4F53\uFF1A\u5DF2\u5B58\u5728 ${existingCoordinator.name}\uFF08${existingCoordinator.id}\uFF09\uFF0C\u4E0D\u80FD\u518D\u521B\u5EFA\u6216\u542F\u7528 ${record5.name}\uFF08${record5.id}\uFF09\u3002`
34446
+ `\u7CFB\u7EDF\u53EA\u80FD\u6709\u4E00\u4E2A\u534F\u8C03\u8005\u667A\u80FD\u4F53\uFF1A\u5DF2\u5B58\u5728 ${existingCoordinator.name}\uFF08${existingCoordinator.id}\uFF09\uFF0C\u4E0D\u80FD\u518D\u521B\u5EFA\u6216\u542F\u7528 ${record6.name}\uFF08${record6.id}\uFF09\u3002`
34222
34447
  );
34223
34448
  }
34224
34449
  }
34225
- await this.opts.store.upsertActor(record5);
34226
- this.opts.onRolesChanged?.(record5.id, record5.status === "active" ? roles : []);
34450
+ await this.opts.store.upsertActor(record6);
34451
+ this.opts.onRolesChanged?.(record6.id, record6.status === "active" ? roles : []);
34227
34452
  await this.audit({ kind: "actor_upsert", actorId: a.id, by, at: this.now(), detail: { status: a.status, roles } });
34228
- return record5;
34453
+ return record6;
34229
34454
  }
34230
34455
  /** 删除即停用(2.1 完成标准) */
34231
34456
  async disableActor(id, by) {
@@ -34237,21 +34462,21 @@ var init_service2 = __esm({
34237
34462
  return this.opts.store.getActor(id);
34238
34463
  }
34239
34464
  /** 设置/清除头像引用("blob:<哈希>";null = 回到默认生成的插画头像) */
34240
- async setActorAvatar(id, ref, by) {
34465
+ async setActorAvatar(id, ref2, by) {
34241
34466
  const a = await this.opts.store.getActor(id);
34242
34467
  if (!a) throw new Error(`actor not found: ${id}`);
34243
34468
  const { avatar: _drop, ...rest } = a;
34244
- const record5 = ref === null ? rest : { ...rest, avatar: ref };
34245
- await this.opts.store.upsertActor(record5);
34246
- await this.audit({ kind: "actor_upsert", actorId: id, by, at: this.now(), detail: { avatar: ref ?? "cleared" } });
34247
- return record5;
34469
+ const record6 = ref2 === null ? rest : { ...rest, avatar: ref2 };
34470
+ await this.opts.store.upsertActor(record6);
34471
+ await this.audit({ kind: "actor_upsert", actorId: id, by, at: this.now(), detail: { avatar: ref2 ?? "cleared" } });
34472
+ return record6;
34248
34473
  }
34249
34474
  /** 设置/清除团队图标引用(同头像约定;不接受 emoji,只接受 blob 引用) */
34250
- async setTeamIcon(teamId, ref) {
34475
+ async setTeamIcon(teamId, ref2) {
34251
34476
  const t = (await this.opts.store.listTeams()).find((x2) => x2.id === teamId);
34252
34477
  if (!t) throw new Error(`team not found: ${teamId}`);
34253
34478
  const { icon: _drop, ...rest } = t;
34254
- await this.opts.store.upsertTeam(ref === null ? rest : { ...rest, icon: ref });
34479
+ await this.opts.store.upsertTeam(ref2 === null ? rest : { ...rest, icon: ref2 });
34255
34480
  }
34256
34481
  listActors(filter) {
34257
34482
  return this.opts.store.listActors(filter);
@@ -34657,13 +34882,13 @@ ${input.description}
34657
34882
  this.opts.store.listConnectors()
34658
34883
  ]);
34659
34884
  const globalConnected = new Set(connectors.filter((c) => c.status === "connected").map((c) => c.id));
34660
- const record5 = new Map(connections.map((c) => [c.connectorId, c.enabled]));
34885
+ const record6 = new Map(connections.map((c) => [c.connectorId, c.enabled]));
34661
34886
  const legacy = new Set(config2?.connectorIds ?? []);
34662
- const ids = /* @__PURE__ */ new Set([...record5.keys(), ...legacy, ...globalConnected]);
34887
+ const ids = /* @__PURE__ */ new Set([...record6.keys(), ...legacy, ...globalConnected]);
34663
34888
  const effective = /* @__PURE__ */ new Set();
34664
34889
  for (const id of ids) {
34665
- if (record5.has(id)) {
34666
- if (record5.get(id)) effective.add(id);
34890
+ if (record6.has(id)) {
34891
+ if (record6.get(id)) effective.add(id);
34667
34892
  } else if (legacy.has(id)) effective.add(id);
34668
34893
  else if (globalConnected.has(id)) effective.add(id);
34669
34894
  }
@@ -35528,6 +35753,7 @@ function actorsDomain(opts) {
35528
35753
  status: "active",
35529
35754
  ...b2.email !== void 0 ? { email: b2.email } : {},
35530
35755
  ...b2.teamId !== void 0 ? { teamId: b2.teamId } : {},
35756
+ ...b2.reportsTo ? { reportsTo: b2.reportsTo } : {},
35531
35757
  ...avatar !== void 0 ? { avatar } : {},
35532
35758
  ...positions !== void 0 ? { roles: positions } : {}
35533
35759
  },
@@ -35542,10 +35768,12 @@ function actorsDomain(opts) {
35542
35768
  const b2 = req.body ?? {};
35543
35769
  const positions = positionsFromPayload(b2);
35544
35770
  const avatar = avatarRefFromPayload(b2);
35545
- const { positions: _positions, id: _id, kind: _kind, avatar: _avatar, ...patch } = b2;
35771
+ const { positions: _positions, id: _id, kind: _kind, avatar: _avatar, reportsTo: _reportsTo, ...patch } = b2;
35546
35772
  const actor = await service.upsertActor({
35547
35773
  ...existing,
35548
35774
  ...patch,
35775
+ // 汇报人:显式给(含清空)——传空串/null 归一为 undefined(写库落 null);未传则保留原值
35776
+ ...b2.reportsTo !== void 0 ? { reportsTo: b2.reportsTo || void 0 } : {},
35549
35777
  ...avatar !== void 0 ? { avatar } : {},
35550
35778
  ...positions !== void 0 ? { roles: positions } : {},
35551
35779
  id: existing.id,
@@ -35561,8 +35789,8 @@ function actorsDomain(opts) {
35561
35789
  router.post("/api/actors/:id/avatar", async (req) => {
35562
35790
  const { service, blobs } = await resolveCtx(req.auth.companyId);
35563
35791
  if (!blobs) throw new ApiError(501, "NOT_IMPLEMENTED", "blob \u5B58\u50A8\u672A\u63A5\u5165");
35564
- const ref = await storeImageBlob(blobs, req.body?.data);
35565
- const actor = await service.setActorAvatar(req.params.id, ref, req.auth.actor);
35792
+ const ref2 = await storeImageBlob(blobs, req.body?.data);
35793
+ const actor = await service.setActorAvatar(req.params.id, ref2, req.auth.actor);
35566
35794
  return { status: 201, body: { avatar: actor.avatar } };
35567
35795
  });
35568
35796
  router.delete("/api/actors/:id/avatar", async (req) => {
@@ -35573,9 +35801,9 @@ function actorsDomain(opts) {
35573
35801
  router.post("/api/teams/:id/icon", async (req) => {
35574
35802
  const { service, blobs } = await resolveCtx(req.auth.companyId);
35575
35803
  if (!blobs) throw new ApiError(501, "NOT_IMPLEMENTED", "blob \u5B58\u50A8\u672A\u63A5\u5165");
35576
- const ref = await storeImageBlob(blobs, req.body?.data);
35577
- await service.setTeamIcon(req.params.id, ref);
35578
- return { status: 201, body: { icon: ref } };
35804
+ const ref2 = await storeImageBlob(blobs, req.body?.data);
35805
+ await service.setTeamIcon(req.params.id, ref2);
35806
+ return { status: 201, body: { icon: ref2 } };
35579
35807
  });
35580
35808
  router.delete("/api/teams/:id/icon", async (req) => {
35581
35809
  const { service } = await resolveCtx(req.auth.companyId);
@@ -36380,12 +36608,12 @@ function parseAcceptanceCriteria(body) {
36380
36608
  }
36381
36609
  function parseReviewActor(req) {
36382
36610
  const actor = String(req.auth.actor);
36383
- const shortId = actor.split(":").slice(2).join(":") || actor;
36611
+ const shortId2 = actor.split(":").slice(2).join(":") || actor;
36384
36612
  const b2 = req.body ?? {};
36385
36613
  return {
36386
36614
  actorKind: actor.includes(":agent:") ? "agent" : "human",
36387
36615
  actorId: actor,
36388
- ...b2.actor_name !== void 0 || b2.actorName !== void 0 ? { actorName: b2.actor_name ?? b2.actorName } : { actorName: shortId }
36616
+ ...b2.actor_name !== void 0 || b2.actorName !== void 0 ? { actorName: b2.actor_name ?? b2.actorName } : { actorName: shortId2 }
36389
36617
  };
36390
36618
  }
36391
36619
  function parseAnnotationActor(req) {
@@ -36429,6 +36657,9 @@ function parseResolveArtifactAnnotation(req) {
36429
36657
  };
36430
36658
  }
36431
36659
  function mapProjectError(err) {
36660
+ if (err instanceof Error && err.message.startsWith("invalid qa_quality_metrics payload:")) {
36661
+ return new ApiError(400, "INVALID_QA_QUALITY_METRICS", err.message);
36662
+ }
36432
36663
  if (err instanceof Error && err.name === "ProjectDocumentSystemNotReady") {
36433
36664
  return new ApiError(409, "PROJECT_DOCUMENT_SYSTEM_NOT_READY", err.message);
36434
36665
  }
@@ -36873,10 +37104,10 @@ var init_service3 = __esm({
36873
37104
  return this.store.getCompany(id);
36874
37105
  }
36875
37106
  /** 设置/清除公司头像引用("blob:<哈希>";null = 回到默认首字母标识)。 */
36876
- async setAvatar(id, ref) {
37107
+ async setAvatar(id, ref2) {
36877
37108
  const company = await this.requireCompany(id);
36878
- await this.store.updateCompany(id, { avatar: ref });
36879
- const next = ref === null ? (({ avatar: _drop, ...rest }) => rest)(company) : { ...company, avatar: ref };
37109
+ await this.store.updateCompany(id, { avatar: ref2 });
37110
+ const next = ref2 === null ? (({ avatar: _drop, ...rest }) => rest)(company) : { ...company, avatar: ref2 };
36880
37111
  return next;
36881
37112
  }
36882
37113
  /**
@@ -37114,8 +37345,8 @@ function companiesDomain(opts) {
37114
37345
  });
37115
37346
  router.post("/api/companies/:id/avatar", async (req) => {
37116
37347
  if (!blobs) throw new ApiError(501, "NOT_IMPLEMENTED", "blob \u5B58\u50A8\u672A\u63A5\u5165");
37117
- const ref = await storeImageBlob(blobs, req.body?.data);
37118
- const company = await service.setAvatar(req.params.id, ref).catch(mapErr);
37348
+ const ref2 = await storeImageBlob(blobs, req.body?.data);
37349
+ const company = await service.setAvatar(req.params.id, ref2).catch(mapErr);
37119
37350
  return { status: 201, body: { avatar: company.avatar } };
37120
37351
  });
37121
37352
  router.delete("/api/companies/:id/avatar", async (req) => {
@@ -38912,13 +39143,13 @@ function scanCodexTelemetry(workdir, sinceMs, sessionsRoot = codexSessionsRoot()
38912
39143
  function createCodexNormalizeState() {
38913
39144
  return { toolUseIds: /* @__PURE__ */ new Set(), previousAgentMessage: false, lastAgentMessageEndedWithNewline: false };
38914
39145
  }
38915
- function record2(value) {
39146
+ function record3(value) {
38916
39147
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
38917
39148
  }
38918
39149
  function textOf3(value) {
38919
39150
  if (typeof value === "string") return value;
38920
39151
  if (Array.isArray(value)) return value.map(textOf3).filter(Boolean).join("\n");
38921
- const obj = record2(value);
39152
+ const obj = record3(value);
38922
39153
  if (!obj) return void 0;
38923
39154
  if (typeof obj.text === "string") return obj.text;
38924
39155
  if (typeof obj.content === "string") return obj.content;
@@ -38982,7 +39213,7 @@ ${text}` : text;
38982
39213
  resetAgentMessageBoundary(state);
38983
39214
  const id = item.id;
38984
39215
  const name = typeof item.name === "string" ? item.name : "mcp_tool_call";
38985
- const input = record2(item.arguments) ?? record2(item.input) ?? {};
39216
+ const input = record3(item.arguments) ?? record3(item.input) ?? {};
38986
39217
  const events = toolUseOnce(state, id, name, input);
38987
39218
  if (phase === "completed" || phase === "direct") {
38988
39219
  events.push({
@@ -39027,7 +39258,7 @@ function normalizeCodexStreamLine(line, state = createCodexNormalizeState()) {
39027
39258
  } catch {
39028
39259
  return line ? [{ kind: "message", payload: { text: line }, outputText: line }] : [];
39029
39260
  }
39030
- const item = record2(obj.item);
39261
+ const item = record3(obj.item);
39031
39262
  if (obj.type === "item.started" && item) return normalizeCodexItem(item, "started", state);
39032
39263
  if (obj.type === "item.completed" && item) return normalizeCodexItem(item, "completed", state);
39033
39264
  if (typeof obj.type === "string") return normalizeCodexItem(obj, "direct", state);
@@ -39286,7 +39517,7 @@ function addToken(acc, value, field) {
39286
39517
  acc[field] += value;
39287
39518
  }
39288
39519
  }
39289
- function record3(value) {
39520
+ function record4(value) {
39290
39521
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
39291
39522
  }
39292
39523
  function firstNumber(src, keys) {
@@ -39348,23 +39579,23 @@ function accumulateTokenFields(acc, src) {
39348
39579
  "cache_write_tokens",
39349
39580
  "cacheWriteTokens"
39350
39581
  ], "cacheCreationTokens") || changed;
39351
- const promptDetails = record3(src["prompt_tokens_details"]) ?? record3(src["promptTokensDetails"]);
39582
+ const promptDetails = record4(src["prompt_tokens_details"]) ?? record4(src["promptTokensDetails"]);
39352
39583
  if (promptDetails) {
39353
39584
  changed = addFirstToken(acc, promptDetails, ["cached_tokens", "cachedTokens"], "cacheReadTokens") || changed;
39354
39585
  }
39355
39586
  return changed;
39356
39587
  }
39357
39588
  function accumulateUsage(acc, value) {
39358
- const root = record3(value);
39589
+ const root = record4(value);
39359
39590
  if (!root) return false;
39360
39591
  const candidates = [
39361
39592
  root,
39362
- record3(root["usage"]),
39363
- record3(root["token_usage"]),
39364
- record3(root["tokenUsage"]),
39365
- record3(root["tokens"]),
39366
- record3(root["usageMetadata"]),
39367
- record3(root["usage_metadata"])
39593
+ record4(root["usage"]),
39594
+ record4(root["token_usage"]),
39595
+ record4(root["tokenUsage"]),
39596
+ record4(root["tokens"]),
39597
+ record4(root["usageMetadata"]),
39598
+ record4(root["usage_metadata"])
39368
39599
  ];
39369
39600
  const seen = /* @__PURE__ */ new Set();
39370
39601
  let changed = false;
@@ -39556,7 +39787,7 @@ var init_pricing = __esm({
39556
39787
  });
39557
39788
 
39558
39789
  // ../adapters/src/_core/acp.ts
39559
- function record4(value) {
39790
+ function record5(value) {
39560
39791
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
39561
39792
  }
39562
39793
  function firstString(...values) {
@@ -39571,11 +39802,11 @@ function textFromUnknown(value) {
39571
39802
  const parts = value.map(textFromUnknown).filter((x2) => Boolean(x2));
39572
39803
  return parts.length ? parts.join("") : void 0;
39573
39804
  }
39574
- const obj = record4(value);
39805
+ const obj = record5(value);
39575
39806
  if (!obj) return void 0;
39576
- const direct = firstString(obj["text"], obj["delta"], obj["message"], record4(obj["error"])?.["message"]);
39807
+ const direct = firstString(obj["text"], obj["delta"], obj["message"], record5(obj["error"])?.["message"]);
39577
39808
  if (direct) return direct;
39578
- return textFromUnknown(obj["content"]) ?? textFromUnknown(record4(obj["result"])?.["content"]) ?? textFromUnknown(obj["result"]);
39809
+ return textFromUnknown(obj["content"]) ?? textFromUnknown(record5(obj["result"])?.["content"]) ?? textFromUnknown(obj["result"]);
39579
39810
  }
39580
39811
  function jsonLike(value) {
39581
39812
  if (typeof value !== "string") return value;
@@ -39590,15 +39821,15 @@ function jsonLike(value) {
39590
39821
  return { text: value };
39591
39822
  }
39592
39823
  function toolPayload(data) {
39593
- const d = record4(data) ?? {};
39594
- const nested = record4(d["toolCall"]) ?? record4(d["tool_call"]) ?? record4(d["call"]) ?? record4(d["tool"]) ?? record4(d["data"]) ?? {};
39824
+ const d = record5(data) ?? {};
39825
+ const nested = record5(d["toolCall"]) ?? record5(d["tool_call"]) ?? record5(d["call"]) ?? record5(d["tool"]) ?? record5(d["data"]) ?? {};
39595
39826
  return { ...nested, ...d };
39596
39827
  }
39597
39828
  function toolCallIdOf(d) {
39598
39829
  return firstString(d["toolCallId"], d["tool_call_id"], d["callId"], d["call_id"], d["id"]);
39599
39830
  }
39600
39831
  function toolNameOf(d) {
39601
- return firstString(d["name"], d["toolName"], d["tool_name"], record4(d["tool"])?.["name"]) ?? toolNameFromTitle(firstString(d["title"]) ?? "", firstString(d["kind"]) ?? "");
39832
+ return firstString(d["name"], d["toolName"], d["tool_name"], record5(d["tool"])?.["name"]) ?? toolNameFromTitle(firstString(d["title"]) ?? "", firstString(d["kind"]) ?? "");
39602
39833
  }
39603
39834
  function inputFromTitle(title, toolName) {
39604
39835
  const colon = title.indexOf(":");
@@ -39640,7 +39871,7 @@ function argsTextOf(d) {
39640
39871
  return typeof value === "string" ? value : void 0;
39641
39872
  }
39642
39873
  function toolOutputOf(d) {
39643
- const error2 = record4(d["error"]);
39874
+ const error2 = record5(d["error"]);
39644
39875
  if (error2) return error2["message"] ?? error2;
39645
39876
  const raw = d["rawOutput"] ?? d["raw_output"] ?? d["output"] ?? d["result"] ?? d["results"] ?? d["response"] ?? d["data"] ?? d["content"];
39646
39877
  const stream = compactRecord({
@@ -39678,7 +39909,7 @@ function parseJsonString(value) {
39678
39909
  function locationsOf(d) {
39679
39910
  const locations = d["locations"];
39680
39911
  if (!Array.isArray(locations)) return void 0;
39681
- const out = locations.filter((item) => Boolean(record4(item)));
39912
+ const out = locations.filter((item) => Boolean(record5(item)));
39682
39913
  return out.length ? out : void 0;
39683
39914
  }
39684
39915
  function statusOf(d) {
@@ -39783,8 +40014,8 @@ function extractSessionID(result) {
39783
40014
  return result?.sessionId ?? "";
39784
40015
  }
39785
40016
  function extractCurrentModelID(result) {
39786
- const r = record4(result);
39787
- const models = record4(r?.["models"]);
40017
+ const r = record5(result);
40018
+ const models = record5(r?.["models"]);
39788
40019
  return firstString(
39789
40020
  models?.["currentModelId"],
39790
40021
  models?.["current_model_id"],
@@ -40242,11 +40473,11 @@ var init_acp = __esm({
40242
40473
  this.flushTextBuffer("message");
40243
40474
  }
40244
40475
  handleAgentMessage(data) {
40245
- const text = textFromUnknown(record4(data)?.["content"] ?? data);
40476
+ const text = textFromUnknown(record5(data)?.["content"] ?? data);
40246
40477
  if (text) this.appendText("message", text);
40247
40478
  }
40248
40479
  handleAgentThought(data) {
40249
- const text = textFromUnknown(record4(data)?.["content"] ?? data);
40480
+ const text = textFromUnknown(record5(data)?.["content"] ?? data);
40250
40481
  if (text) this.appendText("thought", text);
40251
40482
  }
40252
40483
  handleToolCallStart(data) {
@@ -41403,6 +41634,63 @@ var init_connect_script = __esm({
41403
41634
  }
41404
41635
  });
41405
41636
 
41637
+ // ../server/src/node-health.ts
41638
+ function deriveNodeHealth(input) {
41639
+ const staleMs = input.staleMs ?? 9e4;
41640
+ const maxFail = input.maxConsecutiveFailures ?? 3;
41641
+ const cf = input.snapshot?.consecutiveFailures ?? 0;
41642
+ const heartbeatFresh = input.lastHeartbeatMs !== void 0 && input.now - input.lastHeartbeatMs < staleMs;
41643
+ const healthy = input.online && heartbeatFresh && cf < maxFail;
41644
+ return {
41645
+ healthy,
41646
+ online: input.online,
41647
+ ...input.lastHeartbeatMs !== void 0 ? { lastHeartbeatAt: new Date(input.lastHeartbeatMs).toISOString() } : {},
41648
+ activeRunCount: input.activeRunCount,
41649
+ consecutiveFailures: cf,
41650
+ ...input.snapshot?.lastSuccessfulDispatchAt ? { lastSuccessfulDispatchAt: input.snapshot.lastSuccessfulDispatchAt } : {},
41651
+ ...input.snapshot?.lastFailureAt ? { lastFailureAt: input.snapshot.lastFailureAt } : {},
41652
+ ...input.snapshot?.lastFailureReason ? { lastFailureReason: input.snapshot.lastFailureReason } : {}
41653
+ };
41654
+ }
41655
+ var NodeHealthTracker;
41656
+ var init_node_health = __esm({
41657
+ "../server/src/node-health.ts"() {
41658
+ "use strict";
41659
+ NodeHealthTracker = class {
41660
+ byNode = /* @__PURE__ */ new Map();
41661
+ ensure(nodeId) {
41662
+ let s2 = this.byNode.get(nodeId);
41663
+ if (!s2) {
41664
+ s2 = { consecutiveFailures: 0 };
41665
+ this.byNode.set(nodeId, s2);
41666
+ }
41667
+ return s2;
41668
+ }
41669
+ /** 节点交付了一次会话(可达):清零连败、记成功时刻。 */
41670
+ recordReachable(nodeId, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
41671
+ const s2 = this.ensure(nodeId);
41672
+ s2.consecutiveFailures = 0;
41673
+ s2.lastSuccessfulDispatchAt = nowIso;
41674
+ }
41675
+ /** 节点级失败(server-unreachable/失联/派发丢失):连败 +1、记失败时刻与归因。 */
41676
+ recordUnreachable(nodeId, reason, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
41677
+ const s2 = this.ensure(nodeId);
41678
+ s2.consecutiveFailures += 1;
41679
+ s2.lastFailureAt = nowIso;
41680
+ if (reason !== void 0) s2.lastFailureReason = reason;
41681
+ }
41682
+ snapshot(nodeId) {
41683
+ const s2 = this.byNode.get(nodeId);
41684
+ return s2 ? { ...s2 } : void 0;
41685
+ }
41686
+ /** 节点被删除/换机时清账(可选调用)。 */
41687
+ forget(nodeId) {
41688
+ this.byNode.delete(nodeId);
41689
+ }
41690
+ };
41691
+ }
41692
+ });
41693
+
41406
41694
  // ../server/src/domains/nodes/routes.ts
41407
41695
  function nodesDomain(deps) {
41408
41696
  let hubWired = false;
@@ -41486,6 +41774,8 @@ function nodesDomain(deps) {
41486
41774
  }
41487
41775
  const daemonVersionById = new Map((hub?.connectedDaemons() ?? []).map((d) => [d.daemonId, d.meta.daemonVersion]));
41488
41776
  const connectionById = new Map((hub?.recentConnectionChanges() ?? []).map((d) => [d.daemonId, d]));
41777
+ const onlineIds = new Set((hub?.connectedDaemons() ?? []).map((d) => d.daemonId));
41778
+ const healthNow = Date.now();
41489
41779
  const nodes = await deps.nodeStore.listNodes();
41490
41780
  const items = await Promise.all(nodes.map(async (n) => {
41491
41781
  const rts = await deps.nodeStore.listRuntimes(n.id);
@@ -41514,6 +41804,15 @@ function nodesDomain(deps) {
41514
41804
  ...connection.lastDisconnectedAt ? { lastDisconnectedAt: new Date(connection.lastDisconnectedAt).toISOString() } : {},
41515
41805
  ...connection.lastReconnectedAt ? { lastReconnectedAt: new Date(connection.lastReconnectedAt).toISOString() } : {}
41516
41806
  }
41807
+ } : {},
41808
+ ...deps.nodeHealth ? {
41809
+ health: deriveNodeHealth({
41810
+ online: onlineIds.has(n.id),
41811
+ ...hub?.lastSeenAt(n.id) !== void 0 ? { lastHeartbeatMs: hub.lastSeenAt(n.id) } : {},
41812
+ activeRunCount: deps.activeRunCountOf?.(n.id) ?? 0,
41813
+ ...deps.nodeHealth.snapshot(n.id) ? { snapshot: deps.nodeHealth.snapshot(n.id) } : {},
41814
+ now: healthNow
41815
+ })
41517
41816
  } : {}
41518
41817
  };
41519
41818
  }));
@@ -41712,6 +42011,7 @@ var init_routes4 = __esm({
41712
42011
  import_node_path2 = require("node:path");
41713
42012
  init_src4();
41714
42013
  init_connect_script();
42014
+ init_node_health();
41715
42015
  }
41716
42016
  });
41717
42017
 
@@ -42220,6 +42520,207 @@ var init_dashboard = __esm({
42220
42520
  }
42221
42521
  });
42222
42522
 
42523
+ // ../server/src/domains/collab/workorder-metrics.ts
42524
+ function rate(passed, total, source) {
42525
+ return { passed, total, rate: total > 0 ? passed / total : null, source };
42526
+ }
42527
+ function ref(actorId, resolveActor) {
42528
+ const extra = resolveActor?.(actorId);
42529
+ return {
42530
+ id: actorId,
42531
+ ...extra?.name ? { name: extra.name } : {},
42532
+ ...extra?.role ? { role: extra.role } : {},
42533
+ ...extra?.avatar ? { avatar: extra.avatar } : {}
42534
+ };
42535
+ }
42536
+ function firstReviewPassRate(model, artifactIds) {
42537
+ let passed = 0;
42538
+ let total = 0;
42539
+ for (const artifactId of artifactIds) {
42540
+ const first = (model.concludeAttempts.get(artifactId) ?? []).find((attempt) => attempt.gate !== null);
42541
+ if (!first) continue;
42542
+ total += 1;
42543
+ if (attemptStatus(model, first) === "satisfied") passed += 1;
42544
+ }
42545
+ return rate(passed, total, "oplog");
42546
+ }
42547
+ function qualitySnapshot(outputs) {
42548
+ const completed = outputs.filter((output) => output.status === "completed").sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt) || b2.outputId.localeCompare(a.outputId));
42549
+ for (const output of completed) {
42550
+ try {
42551
+ const payload = parseWorkorderQualityMetricsPayload(output.payload);
42552
+ if (!payload) continue;
42553
+ return {
42554
+ source: "qa",
42555
+ schemaVersion: 1,
42556
+ suiteRunId: payload.suiteRunId,
42557
+ ...payload.checklistRef ? { checklistRef: payload.checklistRef } : {},
42558
+ reportedAt: output.updatedAt,
42559
+ functionalCoverage: rate(payload.functionalCoverage.passed, payload.functionalCoverage.total, "qa"),
42560
+ p0Tests: {
42561
+ ...rate(payload.p0Tests.passed, payload.p0Tests.total, "qa"),
42562
+ failed: payload.p0Tests.failed,
42563
+ blocked: payload.p0Tests.blocked
42564
+ },
42565
+ p1Tests: {
42566
+ ...rate(payload.p1Tests.passed, payload.p1Tests.total, "qa"),
42567
+ failed: payload.p1Tests.failed,
42568
+ blocked: payload.p1Tests.blocked
42569
+ }
42570
+ };
42571
+ } catch {
42572
+ }
42573
+ }
42574
+ return null;
42575
+ }
42576
+ function earliestIso(values) {
42577
+ let earliest = null;
42578
+ for (const raw of values) {
42579
+ if (!raw) continue;
42580
+ const time3 = Date.parse(raw);
42581
+ if (!Number.isFinite(time3)) continue;
42582
+ if (!earliest || time3 < earliest.time) earliest = { raw, time: time3 };
42583
+ }
42584
+ return earliest?.raw ?? null;
42585
+ }
42586
+ function latestIso(values) {
42587
+ let latest = null;
42588
+ for (const raw of values) {
42589
+ if (!raw) continue;
42590
+ const time3 = Date.parse(raw);
42591
+ if (!Number.isFinite(time3)) continue;
42592
+ if (!latest || time3 > latest.time) latest = { raw, time: time3 };
42593
+ }
42594
+ return latest?.raw ?? null;
42595
+ }
42596
+ function journalIdentity(entry) {
42597
+ return entry.sessionId ?? entry.dispatchId;
42598
+ }
42599
+ function executionMetrics(journal, artifactIds, resolveActor, nowMs) {
42600
+ const sessions = /* @__PURE__ */ new Map();
42601
+ const activeByJob = /* @__PURE__ */ new Map();
42602
+ let fallbackSeq = 0;
42603
+ for (const entry of journal) {
42604
+ if (!("artifactId" in entry) || !artifactIds.has(entry.artifactId)) continue;
42605
+ if (entry.kind !== "dispatched" && entry.kind !== "recovered" && entry.kind !== "exited") continue;
42606
+ const identity = journalIdentity(entry);
42607
+ const active = activeByJob.get(entry.jobKey);
42608
+ const key = (identity && sessions.has(identity) ? identity : active) ?? identity ?? `${entry.jobKey}#${fallbackSeq++}`;
42609
+ const previous = sessions.get(key);
42610
+ if (entry.kind === "dispatched") {
42611
+ sessions.set(key, {
42612
+ key,
42613
+ artifactId: entry.artifactId,
42614
+ actor: entry.actor,
42615
+ startedAt: entry.at,
42616
+ failed: false
42617
+ });
42618
+ activeByJob.set(entry.jobKey, key);
42619
+ } else if (entry.kind === "recovered") {
42620
+ sessions.set(key, {
42621
+ key,
42622
+ artifactId: entry.artifactId,
42623
+ actor: entry.actor,
42624
+ startedAt: previous?.startedAt ?? entry.at,
42625
+ durationMs: previous?.durationMs,
42626
+ failed: previous?.failed ?? false
42627
+ });
42628
+ activeByJob.set(entry.jobKey, key);
42629
+ } else {
42630
+ sessions.set(key, {
42631
+ key,
42632
+ artifactId: entry.artifactId,
42633
+ actor: entry.actor,
42634
+ startedAt: previous?.startedAt,
42635
+ durationMs: entry.durationMs,
42636
+ failed: !entry.progressed
42637
+ });
42638
+ activeByJob.delete(entry.jobKey);
42639
+ }
42640
+ }
42641
+ const actorTotals = /* @__PURE__ */ new Map();
42642
+ let executionDurationMs = 0;
42643
+ let executionFailures = 0;
42644
+ for (const session of sessions.values()) {
42645
+ const started = session.startedAt ? Date.parse(session.startedAt) : Number.NaN;
42646
+ const duration3 = session.durationMs ?? (Number.isFinite(started) ? Math.max(0, nowMs - started) : 0);
42647
+ executionDurationMs += duration3;
42648
+ if (session.failed) executionFailures += 1;
42649
+ const current = actorTotals.get(session.actor) ?? { sessionCount: 0, executionDurationMs: 0 };
42650
+ current.sessionCount += 1;
42651
+ current.executionDurationMs += duration3;
42652
+ actorTotals.set(session.actor, current);
42653
+ }
42654
+ const byActor = [...actorTotals.entries()].map(([actor, totals]) => ({ actor: ref(actor, resolveActor), ...totals })).sort((a, b2) => b2.executionDurationMs - a.executionDurationMs || a.actor.id.localeCompare(b2.actor.id));
42655
+ return { sessionCount: sessions.size, executionDurationMs, byActor, executionFailures };
42656
+ }
42657
+ function workorderDuration(ops, journal, artifactIds) {
42658
+ const createdAt = earliestIso(
42659
+ ops.filter((op) => op.kind === "spawn_artifact" && artifactIds.has(op.artifactId)).map((op) => op.timestamp)
42660
+ );
42661
+ const latestExecutionFinishedAt = latestIso(
42662
+ journal.filter((entry) => entry.kind === "exited" && artifactIds.has(entry.artifactId)).map((entry) => entry.at)
42663
+ );
42664
+ const createdMs = createdAt ? Date.parse(createdAt) : Number.NaN;
42665
+ const finishedMs = latestExecutionFinishedAt ? Date.parse(latestExecutionFinishedAt) : Number.NaN;
42666
+ return {
42667
+ createdAt,
42668
+ latestExecutionFinishedAt,
42669
+ durationMs: Number.isFinite(createdMs) && Number.isFinite(finishedMs) ? Math.max(0, finishedMs - createdMs) : null
42670
+ };
42671
+ }
42672
+ function downstreamAnnotationCount(model, artifactIds) {
42673
+ let total = 0;
42674
+ for (const annotation of model.annotations.values()) {
42675
+ if (!annotation.raisedFrom || !artifactIds.has(annotation.raisedFrom)) continue;
42676
+ const target = annotation.anchor.target;
42677
+ const targetArtifact = model.artifacts.has(target) ? target : model.revisions.get(target)?.artifactId;
42678
+ if (targetArtifact && artifactIds.has(targetArtifact)) total += 1;
42679
+ }
42680
+ return total;
42681
+ }
42682
+ function reportedGapCount(model, artifactIds) {
42683
+ let total = 0;
42684
+ for (const artifactId of artifactIds) total += (model.gaps.get(artifactId) ?? []).length;
42685
+ return total;
42686
+ }
42687
+ function buildWorkorderMetrics(input) {
42688
+ const nowMs = input.nowMs ?? Date.now();
42689
+ return input.workorders.map((workorder) => {
42690
+ const artifactIds = new Set(
42691
+ [...input.model.artifacts.values()].filter((artifact) => artifact.workspace === workorder.id).map((artifact) => artifact.id)
42692
+ );
42693
+ const execution = executionMetrics(input.dispatchJournal, artifactIds, input.resolveActor, nowMs);
42694
+ const reportedGaps = reportedGapCount(input.model, artifactIds);
42695
+ return {
42696
+ workorder,
42697
+ sessionCount: execution.sessionCount,
42698
+ executionDurationMs: execution.executionDurationMs,
42699
+ workorderDuration: workorderDuration(
42700
+ input.opsByWorkorder?.get(workorder.id) ?? [],
42701
+ input.dispatchJournal,
42702
+ artifactIds
42703
+ ),
42704
+ byActor: execution.byActor,
42705
+ firstReviewPassRate: firstReviewPassRate(input.model, artifactIds),
42706
+ qualityMetrics: qualitySnapshot(input.nodeOutputsByWorkorder?.get(workorder.id) ?? []),
42707
+ annotationCount: downstreamAnnotationCount(input.model, artifactIds),
42708
+ blockage: {
42709
+ total: execution.executionFailures + reportedGaps,
42710
+ executionFailures: execution.executionFailures,
42711
+ reportedGaps
42712
+ }
42713
+ };
42714
+ });
42715
+ }
42716
+ var init_workorder_metrics = __esm({
42717
+ "../server/src/domains/collab/workorder-metrics.ts"() {
42718
+ "use strict";
42719
+ init_src2();
42720
+ init_src();
42721
+ }
42722
+ });
42723
+
42223
42724
  // ../server/src/domains/collab/organization-usage.ts
42224
42725
  function emptyUsage() {
42225
42726
  return {
@@ -42465,6 +42966,43 @@ function collabDomain(opts) {
42465
42966
  const dispatchPausedOf = await buildDispatchPausedResolver(artifacts);
42466
42967
  return { status: 200, body: { items: buildWorkorderSummaries(kernel.model, resolve5, resolveProject, dispatchPausedOf) } };
42467
42968
  });
42969
+ router.get("/api/workorder-metrics", async (req) => {
42970
+ const { kernel, oplog, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
42971
+ const resolve5 = await buildResolver(registry2);
42972
+ const resolveProject = await buildProjectResolver(artifacts);
42973
+ const dispatchPausedOf = await buildDispatchPausedResolver(artifacts);
42974
+ const workorders = buildWorkorderSummaries(kernel.model, resolve5, resolveProject, dispatchPausedOf);
42975
+ const nodeOutputsByWorkorder = /* @__PURE__ */ new Map();
42976
+ const artifactIdsByWorkorder = /* @__PURE__ */ new Map();
42977
+ for (const artifact of kernel.model.artifacts.values()) {
42978
+ const ids = artifactIdsByWorkorder.get(artifact.workspace) ?? [];
42979
+ ids.push(artifact.id);
42980
+ artifactIdsByWorkorder.set(artifact.workspace, ids);
42981
+ }
42982
+ const opsByWorkorder = /* @__PURE__ */ new Map();
42983
+ await Promise.all(workorders.map(async (workorder) => {
42984
+ const artifactIds = artifactIdsByWorkorder.get(workorder.id) ?? [];
42985
+ const [nodeOutputs, opGroups] = await Promise.all([
42986
+ artifacts ? artifacts.listNodeOutputs({ workOrderId: workorder.id }) : Promise.resolve([]),
42987
+ Promise.all(artifactIds.map((artifactId) => oplog.read(artifactId)))
42988
+ ]);
42989
+ nodeOutputsByWorkorder.set(workorder.id, nodeOutputs);
42990
+ opsByWorkorder.set(workorder.id, opGroups.flat());
42991
+ }));
42992
+ return {
42993
+ status: 200,
42994
+ body: {
42995
+ items: buildWorkorderMetrics({
42996
+ model: kernel.model,
42997
+ workorders,
42998
+ dispatchJournal: opts.metricsDispatchJournal?.() ?? opts.dispatchJournal?.() ?? [],
42999
+ opsByWorkorder,
43000
+ nodeOutputsByWorkorder,
43001
+ ...resolve5 ? { resolveActor: resolve5 } : {}
43002
+ })
43003
+ }
43004
+ };
43005
+ });
42468
43006
  router.get("/api/workorders/:id", async (req) => {
42469
43007
  const { kernel, oplog, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
42470
43008
  const resolve5 = await buildResolver(registry2);
@@ -42592,10 +43130,12 @@ var init_collab = __esm({
42592
43130
  init_inbox();
42593
43131
  init_audit();
42594
43132
  init_dashboard();
43133
+ init_workorder_metrics();
42595
43134
  init_organization_usage();
42596
43135
  init_node_state();
42597
43136
  init_workorders();
42598
43137
  init_workorder_detail();
43138
+ init_workorder_metrics();
42599
43139
  init_inbox();
42600
43140
  init_audit();
42601
43141
  init_dashboard();
@@ -42632,8 +43172,8 @@ function makeSeededWorkorderCreator(deps) {
42632
43172
  await deps.kernel.applyIntervention(planned.plan, preview2.baseSeqs, input.actor);
42633
43173
  const briefId = planned.rootArtifactId;
42634
43174
  if (input.brief.trim()) {
42635
- const ref = await deps.blobs.put(enc2(input.brief));
42636
- await deps.kernel.proposeRevision({ artifactId: briefId, actor: input.actor, contentRef: ref, reason: "\u5267\u672C\u5EFA\u5355\uFF1A\u843D\u5B9A brief \u9996\u7248" });
43175
+ const ref2 = await deps.blobs.put(enc2(input.brief));
43176
+ await deps.kernel.proposeRevision({ artifactId: briefId, actor: input.actor, contentRef: ref2, reason: "\u5267\u672C\u5EFA\u5355\uFF1A\u843D\u5B9A brief \u9996\u7248" });
42637
43177
  await deps.kernel.advanceQueue(briefId, input.actor);
42638
43178
  await deps.kernel.conclude({ artifactId: briefId, actor: input.actor, note: "\u5267\u672C\u5EFA\u5355\u65F6\u5B9A\u7A3F" });
42639
43179
  }
@@ -42932,18 +43472,18 @@ var init_sink = __esm({
42932
43472
  end(sessionId, update) {
42933
43473
  const captured = this.sessions.get(sessionId);
42934
43474
  this.enqueue(sessionId, async (st) => {
42935
- for (const ref of update.opRefs) {
43475
+ for (const ref2 of update.opRefs) {
42936
43476
  await this.store.appendEvents(st.runId, [{
42937
43477
  seq: ++st.seq,
42938
43478
  eventType: "oplog.appended",
42939
43479
  stream: "oasis",
42940
- message: ref.kind,
42941
- payload: { opId: ref.id, kind: ref.kind, target: ref.target, oplogSeq: ref.seq }
43480
+ message: ref2.kind,
43481
+ payload: { opId: ref2.id, kind: ref2.kind, target: ref2.target, oplogSeq: ref2.seq }
42942
43482
  }]);
42943
43483
  await this.store.putRunLink({
42944
43484
  runId: st.runId,
42945
43485
  refType: "op",
42946
- refId: ref.id,
43486
+ refId: ref2.id,
42947
43487
  relation: "produced",
42948
43488
  createdAt: update.exit.at
42949
43489
  });
@@ -43123,7 +43663,7 @@ var init_service4 = __esm({
43123
43663
  /** 组织汇总专用瘦读:只取归属、时间和 usage,并统一补齐展示成本。 */
43124
43664
  async listUsageRuns(filter) {
43125
43665
  const rows = await this.store.listUsageRuns(filter);
43126
- return rows.map((row) => ({
43666
+ return rows.filter((row) => row.usage != null).map((row) => ({
43127
43667
  ...row,
43128
43668
  usage: {
43129
43669
  ...row.usage,
@@ -43240,8 +43780,8 @@ var init_service4 = __esm({
43240
43780
  totalRuns: items.length,
43241
43781
  runningRuns: byStatus.get("running") ?? 0,
43242
43782
  failedRuns: (byStatus.get("failed") ?? 0) + (byStatus.get("timeout") ?? 0) + (byStatus.get("killed") ?? 0) + (byStatus.get("orphaned") ?? 0),
43243
- byStatus: [...byStatus].map(([status, count]) => ({ status, count })),
43244
- byRuntimeKind: [...byKind].map(([runtimeKind, count]) => ({ runtimeKind, count }))
43783
+ byStatus: [...byStatus].map(([status, count2]) => ({ status, count: count2 })),
43784
+ byRuntimeKind: [...byKind].map(([runtimeKind, count2]) => ({ runtimeKind, count: count2 }))
43245
43785
  };
43246
43786
  }
43247
43787
  };
@@ -43836,18 +44376,18 @@ var init_chat_sessions = __esm({
43836
44376
  });
43837
44377
 
43838
44378
  // ../server/src/domains/automations/playbooks.ts
43839
- function isKnownPlaybook(ref) {
43840
- return ref in BUILTIN;
44379
+ function isKnownPlaybook(ref2) {
44380
+ return ref2 in BUILTIN;
43841
44381
  }
43842
- function resolvePlaybook(ref, ctx) {
43843
- const def = BUILTIN[ref];
44382
+ function resolvePlaybook(ref2, ctx) {
44383
+ const def = BUILTIN[ref2];
43844
44384
  if (def) {
43845
44385
  return { artifactType: def.artifactType, title: def.title(ctx), brief: def.brief(ctx) };
43846
44386
  }
43847
44387
  return {
43848
44388
  artifactType: "document",
43849
44389
  title: `\u81EA\u52A8\u5316\u7ACB\u9879\uFF1A${ctx.automation.name}\uFF08${isoDate(ctx.now)}\uFF09`,
43850
- brief: `\u7531\u81EA\u52A8\u5316\u300C${ctx.automation.name}\u300D\uFF08\u5267\u672C ${ref}\uFF09\u53D1\u8D77\u7684\u534F\u4F5C\u3002`
44390
+ brief: `\u7531\u81EA\u52A8\u5316\u300C${ctx.automation.name}\u300D\uFF08\u5267\u672C ${ref2}\uFF09\u53D1\u8D77\u7684\u534F\u4F5C\u3002`
43851
44391
  };
43852
44392
  }
43853
44393
  var isoDate, BUILTIN, BUILTIN_PLAYBOOK_REFS;
@@ -45036,8 +45576,8 @@ ${body || "\uFF08\u672A\u586B\u5199\uFF09"}`;
45036
45576
  plan
45037
45577
  };
45038
45578
  }
45039
- function isKnownPlaybook2(ref) {
45040
- return ref in BUILTIN2;
45579
+ function isKnownPlaybook2(ref2) {
45580
+ return ref2 in BUILTIN2;
45041
45581
  }
45042
45582
  function listPlaybooks() {
45043
45583
  return Object.values(BUILTIN2).map((d) => ({
@@ -45049,8 +45589,8 @@ function listPlaybooks() {
45049
45589
  ...d.graph ? { graph: d.graph } : {}
45050
45590
  }));
45051
45591
  }
45052
- function resolvePlaybook2(ref, ctx) {
45053
- const def = BUILTIN2[ref];
45592
+ function resolvePlaybook2(ref2, ctx) {
45593
+ const def = BUILTIN2[ref2];
45054
45594
  return def ? def.resolve(ctx) : null;
45055
45595
  }
45056
45596
  var BUG_FIX_INPUTS, BUG_FIX_STRUCTURE, BUILTIN2, PLAYBOOK_REFS;
@@ -45120,15 +45660,15 @@ function playbooksDomain(opts) {
45120
45660
  return { status: 200, body };
45121
45661
  });
45122
45662
  router.post("/api/playbooks/:ref/run", async (req) => {
45123
- const ref = req.params.ref;
45124
- if (!ref || !isKnownPlaybook2(ref)) {
45125
- throw new ApiError(400, "UNKNOWN_PLAYBOOK", `\u672A\u77E5\u5267\u672C ${ref}\uFF08\u5185\u7F6E\uFF1A${PLAYBOOK_REFS.join(", ")}\uFF09`);
45663
+ const ref2 = req.params.ref;
45664
+ if (!ref2 || !isKnownPlaybook2(ref2)) {
45665
+ throw new ApiError(400, "UNKNOWN_PLAYBOOK", `\u672A\u77E5\u5267\u672C ${ref2}\uFF08\u5185\u7F6E\uFF1A${PLAYBOOK_REFS.join(", ")}\uFF09`);
45126
45666
  }
45127
45667
  const payload = (req.body ?? null)?.payload ?? null;
45128
- const resolved = resolvePlaybook2(ref, { now: now(), ...payload !== null ? { payload } : {} });
45129
- if (!resolved) throw new ApiError(400, "UNKNOWN_PLAYBOOK", `\u672A\u77E5\u5267\u672C ${ref}`);
45668
+ const resolved = resolvePlaybook2(ref2, { now: now(), ...payload !== null ? { payload } : {} });
45669
+ if (!resolved) throw new ApiError(400, "UNKNOWN_PLAYBOOK", `\u672A\u77E5\u5267\u672C ${ref2}`);
45130
45670
  if (resolved.produces !== "workorder" || !resolved.plan) {
45131
- throw new ApiError(400, "PLAYBOOK_NOT_RUNNABLE", `\u5267\u672C ${ref} \u6682\u4E0D\u652F\u6301\u76F4\u63A5\u8FD0\u884C\u5EFA\u5355`);
45671
+ throw new ApiError(400, "PLAYBOOK_NOT_RUNNABLE", `\u5267\u672C ${ref2} \u6682\u4E0D\u652F\u6301\u76F4\u63A5\u8FD0\u884C\u5EFA\u5355`);
45132
45672
  }
45133
45673
  const projectId = readProjectId(payload);
45134
45674
  if (projectId && opts.projectExists && !await opts.projectExists(projectId)) {
@@ -45144,7 +45684,7 @@ function playbooksDomain(opts) {
45144
45684
  actor: req.auth.actor
45145
45685
  });
45146
45686
  const body = {
45147
- playbookRef: ref,
45687
+ playbookRef: ref2,
45148
45688
  produces: resolved.produces,
45149
45689
  workspaceId: result.workspace,
45150
45690
  rootArtifactId: result.rootArtifactId,
@@ -45205,6 +45745,7 @@ var init_daemon_adapter = __esm({
45205
45745
  this.nodeLostGraceMs = opts.nodeLostGraceMs ?? graceFromEnv(12e4);
45206
45746
  this.ackTimeoutMs = opts.ackTimeoutMs ?? ackMsFromEnv(15e3);
45207
45747
  this.log = opts.log ?? ((m2) => console.warn(m2));
45748
+ this.health = opts.health;
45208
45749
  hub.addMessageListener((_daemonId, msg) => this.onDaemonMessage(msg));
45209
45750
  hub.addDisconnectListener((daemonId) => this.onNodeDown(daemonId));
45210
45751
  hub.addConnectListener((daemon) => this.onNodeUp(daemon.daemonId));
@@ -45213,6 +45754,7 @@ var init_daemon_adapter = __esm({
45213
45754
  nodeLostGraceMs;
45214
45755
  ackTimeoutMs;
45215
45756
  log;
45757
+ health;
45216
45758
  /** 断联节点 → 收割定时器。重连取消;到期收割。 */
45217
45759
  reapTimers = /* @__PURE__ */ new Map();
45218
45760
  /** 统一结算一次会话退出:清 ack 定时器、置 exited、触发回调、从 pending 摘除。幂等(已退出即跳过)。 */
@@ -45224,9 +45766,17 @@ var init_daemon_adapter = __esm({
45224
45766
  entry.ackTimer = void 0;
45225
45767
  }
45226
45768
  entry.exited = info;
45769
+ if (info.reason === "server-unreachable") this.health?.recordUnreachable(entry.nodeId, info.reason);
45770
+ else this.health?.recordReachable(entry.nodeId);
45227
45771
  for (const cb of entry.exitCbs.splice(0)) cb(info);
45228
45772
  this.pending.delete(dispatchId);
45229
45773
  }
45774
+ /** 该节点当前在途(未退出)会话数——节点健康的 activeRunCount。 */
45775
+ activeRunCount(nodeId) {
45776
+ let n = 0;
45777
+ for (const e of this.pending.values()) if (e.nodeId === nodeId && !e.exited) n++;
45778
+ return n;
45779
+ }
45230
45780
  /** 收到任何回帧(started/event/output/exited)即视为派发已达,取消 ack 超时。 */
45231
45781
  cancelAck(dispatchId) {
45232
45782
  const entry = this.pending.get(dispatchId);
@@ -45269,6 +45819,8 @@ var init_daemon_adapter = __esm({
45269
45819
  onDaemonMessage(msg) {
45270
45820
  if (msg.type === "session_started") {
45271
45821
  this.cancelAck(msg.dispatchId);
45822
+ const entry = this.pending.get(msg.dispatchId);
45823
+ if (entry) this.health?.recordReachable(entry.nodeId);
45272
45824
  } else if (msg.type === "session_exited") {
45273
45825
  const info = msg.info.reason || msg.info.code !== null ? msg.info : { ...msg.info, reason: "error" };
45274
45826
  this.settle(msg.dispatchId, info);
@@ -45769,6 +46321,12 @@ var init_ci_gate = __esm({
45769
46321
  const statuses = [];
45770
46322
  const repoViews = [];
45771
46323
  for (const unit of units) {
46324
+ if (await ci.isMerged({ repo: unit.repo, sha: unit.sha })) {
46325
+ const status2 = { state: "success", summary: `${unit.repo.id}@${unit.sha.slice(0, 8)}: \u5185\u5BB9\u5DF2\u5728 ${unit.repo.develop}\uFF08\u4E0A\u8F6E\u5DF2\u5408\uFF09\uFF0CCI \u514D\u68C0` };
46326
+ statuses.push({ unit, status: status2 });
46327
+ repoViews.push({ repoId: unit.repo.id, sha: unit.sha.slice(0, 8), state: "success", summary: status2.summary });
46328
+ continue;
46329
+ }
45772
46330
  const pr = await ci.ensurePr(
45773
46331
  { repo: unit.repo, branch: unit.branch, sha: unit.sha },
45774
46332
  { title: `[oasis-ci] ${artifactId} \u2192 ${unit.repo.develop}`, body: `CI gate for artifact ${artifactId} @ ${unit.sha}` }
@@ -45947,7 +46505,7 @@ var init_resolve = __esm({
45947
46505
  });
45948
46506
 
45949
46507
  // ../server/src/coordinator/worker.ts
45950
- var import_node_crypto21, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_MANAGER_BODY, CoordinatorWorker;
46508
+ var import_node_crypto21, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_MANAGER_BODY, CoordinatorWorker;
45951
46509
  var init_worker = __esm({
45952
46510
  "../server/src/coordinator/worker.ts"() {
45953
46511
  "use strict";
@@ -46001,9 +46559,15 @@ var init_worker = __esm({
46001
46559
  COORDINATOR_SYSTEM_PROMPT = `${AUTONOMOUS_ETHOS}
46002
46560
 
46003
46561
  ${SHARED_GRAPH_BODY}`;
46562
+ CONVERSATIONAL_RECOVERY_TOOLS = `\u3010\u6062\u590D\u52A8\u4F5C\uFF08\u5BF9\u8BDD\u4E13\u5C5E\uFF0C\u4EBA\u5728\u573A\u65F6\u624D\u7528\uFF09\uFF1A\u6682\u505C / \u6062\u590D\u8282\u70B9\u6D3E\u53D1\u2014\u2014\u53EA\u6321 agent \u6D3E\u53D1\u3001\u4E0D\u9501\u5185\u5BB9\uFF08\u2260 freeze/cancel\uFF09\uFF0C\u5373\u65F6\u751F\u6548\u3001\u4E0D\u8FDB\u6539\u56FE\u7F13\u51B2\u3011\uFF1A
46563
+ - \`oasis hold <id> --reason "<\u4E3A\u4EC0\u4E48\u5148\u51BB>"\` \u2014\u2014 \u6682\u505C\u8BE5\u8282\u70B9\u7684 agent \u6D3E\u53D1\u3002\u53CD\u590D\u5931\u8D25\u3001\u6216\u4EBA\u8981\u5148\u67E5/\u5148\u624B\u52A8\u6539\u73AF\u5883\uFF08\u6362 runtime\u3001\u8865\u51ED\u8BC1\uFF09\u65F6\uFF0C\u5148\u628A\u5B83\u51BB\u4F4F\u522B\u518D\u7A7A\u8F6C\u91CD\u6D3E\uFF1Bowner \u4E0D\u53D8\u3001\u5185\u5BB9\u4ECD\u53EF\u6539\u3002
46564
+ - \`oasis release <id>\` \u2014\u2014 \u6062\u590D\u6D3E\u53D1\uFF1A\u89E3\u963B\u540E\u7CFB\u7EDF\u91CD\u63A8\u8BE5\u8282\u70B9**\u5F53\u4E0B\u771F\u6B63\u6B20\u7684\u6D3B**\uFF08\u5BF9\u88AB\u5347\u7EA7\u51BB\u7ED3\u7684\u8282\u70B9\uFF0C\u8FD9\u5C31\u662F"\u8BA9\u5B83\u91CD\u65B0\u8DD1"\uFF09\u3002**\u53EA\u5728\u786E\u8BA4\u6839\u56E0\u5DF2\u5904\u7406\u540E\u624D\u653E**\uFF08\u4EBA\u5DF2\u6362 runtime / \u8865\u4E86\u51ED\u8BC1 / \u6539\u4E86\u4EFB\u52A1\u4E66\uFF09\uFF0C\u5426\u5219\u4E00\u653E\u53C8\u683D\u540C\u4E00\u4E2A\u5751\u3002
46565
+ \u5224\u65AD\u8FB9\u754C\uFF1A\u8FD9\u4FE9\u662F"\u6709\u4EE3\u4EF7\u3001\u9700\u5224\u65AD"\u7684\u52A8\u4F5C\u2014\u2014**\u4EBA\u6279\u51C6\u4E86\u518D\u505A**\uFF0C\u522B\u81EA\u4F5C\u4E3B\u5F20\u628A\u4EBA\u6B63\u5728\u67E5\u7684\u8282\u70B9\u653E\u51FA\u53BB\u3002`;
46004
46566
  CONVERSATIONAL_MANAGER_BODY = `${CONVERSATIONAL_ETHOS}
46005
46567
 
46006
- ${SHARED_GRAPH_BODY}`;
46568
+ ${SHARED_GRAPH_BODY}
46569
+
46570
+ ${CONVERSATIONAL_RECOVERY_TOOLS}`;
46007
46571
  CoordinatorWorker = class {
46008
46572
  constructor(deps) {
46009
46573
  this.deps = deps;
@@ -46334,6 +46898,69 @@ ${SHARED_GRAPH_BODY}`;
46334
46898
  ].join("\n");
46335
46899
  await this.runCoordinator(p2.id, task, `\u6B7B\u9501\u8BC4\u4F30 ${p2.id}\uFF08\u963B\u585E ${mins}min\uFF09`);
46336
46900
  }
46901
+ /**
46902
+ * ADR-0048 自动分诊:结构性运行时故障(连不上 / 挂死超时 / 缺凭证 / 配额打满)熔断升级前,**先跑一次本工单管理者
46903
+ * 产出诊断**——人点开 inbox 卡时看到的是"管理者已诊断 + 给了处置建议",而非冷启动的系统通用占位。
46904
+ *
46905
+ * 由 SLA 扫描 **fire-and-forget** 调用(内部 await 管理者会话跑完,但不阻塞扫描线程)。调用前 SLA 扫描已把节点
46906
+ * **冻结**(hold source:sla),所以这里管理者用自主 prompt(COORDINATOR_SYSTEM_PROMPT,**不含 hold/release**)——
46907
+ * 它能做的只有"诊断 + escalate"(运行时故障它多半解不了),**不会**误把刚冻的节点放出去。
46908
+ *
46909
+ * 兜底(保证 inbox 一定有卡):无可用管理者(run=null)/ 管理者跑完仍没 escalate(空手)→ 系统落一条通用 escalate。
46910
+ */
46911
+ async triageFailure(artifactId, ctx) {
46912
+ if (unresolvedEscalationsOf(this.deps.kernel.model, artifactId).length > 0) return;
46913
+ const task = this.buildTriageTask(artifactId, ctx);
46914
+ const run = await this.runCoordinator(
46915
+ artifactId,
46916
+ task,
46917
+ `\u5931\u8D25\u5206\u8BCA ${artifactId}\uFF08\u8FDE\u8D25 ${ctx.consecutiveFailures}${ctx.lastReason ? `\uFF0C\u672B\u6B21 ${ctx.lastReason}` : ""}\uFF09`
46918
+ );
46919
+ if (unresolvedEscalationsOf(this.deps.kernel.model, artifactId).length > 0) {
46920
+ this.deps.log?.(`[coordinator] ${artifactId} \u81EA\u52A8\u5206\u8BCA\u5B8C\u6210\uFF1A\u7BA1\u7406\u8005\u5DF2\u4EA7\u51FA\u8BCA\u65AD\u5E76 escalate`);
46921
+ return;
46922
+ }
46923
+ const failLine = `agent \u8FDE\u8D25\u7194\u65AD\uFF08\u8FDE\u8D25 ${ctx.consecutiveFailures} \u6B21${ctx.lastReason ? `\uFF0C\u672B\u6B21\u5931\u8D25\uFF1A${ctx.lastReason}` : ""}\uFF09`;
46924
+ await this.deps.kernel.escalate({
46925
+ artifactId,
46926
+ actor: SYSTEM_ACTOR,
46927
+ reason: run ? `\u81EA\u52A8\u5206\u8BCA\u672A\u6536\u53E3\uFF08\u7BA1\u7406\u8005\u88AB\u5524\u8D77\u5374\u6CA1\u4E0A\u62A5\u8BCA\u65AD\uFF09\u2014\u2014\u7CFB\u7EDF\u515C\u5E95\u5347\u7EA7\uFF1A${failLine}\uFF0C\u8BF7\u4EBA\u5DE5\u6392\u67E5\u6839\u56E0\uFF08\u8FDE\u4E0D\u4E0A/\u6302\u6B7B/\u7F3A\u51ED\u8BC1/\u914D\u989D\uFF09\u3002` : `\u672C\u5DE5\u5355\u65E0\u53EF\u7528\u7BA1\u7406\u8005\u3001\u65E0\u6CD5\u81EA\u52A8\u5206\u8BCA\u2014\u2014\u7CFB\u7EDF\u5347\u7EA7\uFF1A${failLine}\uFF0C\u8BF7\u4EBA\u5DE5\u6392\u67E5\u6839\u56E0\uFF08\u8FDE\u4E0D\u4E0A/\u6302\u6B7B/\u7F3A\u51ED\u8BC1/\u914D\u989D\uFF09\u3002`
46928
+ }).catch((e) => this.deps.log?.(`[coordinator] ${artifactId} \u5206\u8BCA\u515C\u5E95 escalate \u5931\u8D25\uFF1A${String(e)}`));
46929
+ this.deps.log?.(`[coordinator] ${artifactId} \u81EA\u52A8\u5206\u8BCA\u515C\u5E95\uFF1A\u7CFB\u7EDF\u843D\u901A\u7528 escalate\uFF08\u7BA1\u7406\u8005${run ? "\u7A7A\u624B" : "\u4E0D\u53EF\u7528"}\uFF09`);
46930
+ }
46931
+ /** 失败分诊的 TASK.md:把已知失败事实喂给管理者,让它归因 + 给处置建议 + escalate(唯一收尾)。 */
46932
+ buildTriageTask(artifactId, ctx) {
46933
+ const m2 = this.deps.kernel.model;
46934
+ const node = m2.artifacts.get(artifactId);
46935
+ const brief = node?.title ?? node?.description ?? "";
46936
+ const downstreams = consumersOf(m2, artifactId).map((a) => a.id);
46937
+ return [
46938
+ `## \u672C\u6B21\u4E3A\u4EC0\u4E48\u5524\u8D77\u4F60`,
46939
+ `\u8282\u70B9 **${artifactId}**${brief ? `\uFF08\u5B83\u662F\uFF1A${brief}\uFF09` : ""} \u7684 agent **\u53CD\u590D\u5931\u8D25\u3001\u5DF2\u88AB\u7194\u65AD**\u2014\u2014\u8FDE\u8D25 **${ctx.consecutiveFailures}** \u6B21\u3002`,
46940
+ ctx.lastReason ? `> \u672B\u6B21\u5931\u8D25\u539F\u56E0\uFF08\u8FD0\u884C\u65F6\u7ED9\u7684\uFF09\uFF1A\`${ctx.lastReason}\`` : `> \u672B\u6B21\u5931\u8D25\u539F\u56E0\uFF1A\u672A\u77E5\uFF08\u8FD0\u884C\u65F6\u672A\u7ED9\u7ED3\u6784\u5316\u539F\u56E0\uFF09\u3002`,
46941
+ `\u7CFB\u7EDF\u5DF2\u628A\u5B83**\u51BB\u7ED3**\uFF08\u9632\u6B62\u5B83\u7A7A\u8F6C\u53CD\u590D\u91CD\u8DD1\uFF09\u3002**\u4F60\u7684\u4EFB\u52A1\u662F\u8BCA\u65AD + \u4E0A\u62A5\uFF0C\u4E0D\u662F\u8BA9\u5B83\u590D\u5DE5**\u2014\u2014\u6839\u56E0\u6CA1\u9664\u5C31\u653E\u51FA\u53BB\u53EA\u4F1A\u518D\u683D\u4E00\u6B21\u3002`,
46942
+ downstreams.length ? `\u8FD9\u6761\u7EBF\u5361\u7740\u3001\u4E0B\u6E38\u8DDF\u7740\u505C\uFF1A${downstreams.map((d) => `\`${d}\``).join("\u3001")}\u3002` : ``,
46943
+ ``,
46944
+ `## \u8FD9\u591A\u534A\u662F\u8FD0\u884C\u65F6 / \u8D44\u6E90\u7C7B\u6545\u969C\uFF08\u4E0D\u662F\u56FE\u75C5\uFF09`,
46945
+ `\u7194\u65AD = agent \u8FDE\u7740\u8DD1\u4E0D\u8D77\u6765\u6216\u8DD1\u4E0D\u5B8C\uFF0C\u5E38\u89C1\u51E0\u7C7B\uFF0C\u4F60\u636E\u672B\u6B21\u539F\u56E0\u5224\u65AD\u843D\u5728\u54EA\u7C7B\uFF1A`,
46946
+ `- **\u7ED1\u5B9A\u8D26\u53F7\u914D\u989D\u6253\u6EE1**\uFF08\u5982 429 / \u4F7F\u7528\u4E0A\u9650\uFF09\u2014\u2014\u8981\u4EBA\u52A0\u989D\u5EA6 / \u6362\u7ED1\u5B9A\u5230\u6709\u989D\u5EA6\u7684\u8D26\u53F7 / \u51CF\u5E76\u53D1\u3002`,
46947
+ `- **\u7F3A\u51ED\u8BC1 / \u5BC6\u94A5**\uFF08\u5982\u67D0\u4ED3\u5E93\u3001\u67D0 API \u6CA1\u6388\u6743\uFF09\u2014\u2014\u8981\u4EBA\u8865\u51ED\u8BC1\u3002`,
46948
+ `- **runtime \u8FDE\u4E0D\u4E0A / \u4F1A\u8BDD\u6302\u6B7B\u8D85\u65F6**\uFF08\u8282\u70B9\u6389\u7EBF\u3001\u8FDB\u7A0B\u5361\u6B7B\uFF09\u2014\u2014\u8981\u4EBA\u6362 runtime / \u91CD\u542F\u8282\u70B9\u3002`,
46949
+ `- **\u4EFB\u52A1\u672C\u8EAB\u8BA9 agent \u53CD\u590D\u4EA7\u4E0D\u51FA**\uFF08\u4EFB\u52A1\u4E66\u6709\u786C\u4F24 / \u7F3A\u5173\u952E\u8F93\u5165\uFF09\u2014\u2014\u8981\u4EBA\u6539\u4EFB\u52A1\u4E66\u6216\u8865\u8F93\u5165\u3002`,
46950
+ `\u8FD9\u4E9B\u4F60**\u591A\u534A\u72EC\u7ACB\u89E3\u4E0D\u4E86**\uFF08\u6362\u989D\u5EA6\u3001\u6362\u673A\u5668\u3001\u8865\u51ED\u8BC1\u90FD\u8981\u4EBA\uFF09\uFF0C\u6240\u4EE5\u6838\u5FC3\u4EA7\u51FA\u662F**\u4E00\u4EFD\u8BA9\u4EBA\u4E00\u773C\u770B\u61C2\u8BE5\u600E\u4E48\u4FEE\u7684\u8BCA\u65AD**\u3002`,
46951
+ ``,
46952
+ `## \u73B0\u6709\u4EA7\u7269\uFF08\u90BB\u57DF\u4E0A\u4E0B\u6587\uFF0C\u5E2E\u4F60\u5224\u65AD\u5F71\u54CD\u9762\uFF09`,
46953
+ this.structDigest(artifactId),
46954
+ ``,
46955
+ `## \u4F60\u7684\u52A8\u4F5C\u2014\u2014**\u53EA\u6709\u4E00\u4E2A\u6536\u5C3E\uFF1Aescalate**`,
46956
+ `\`oasis escalate ${artifactId} --reason "<\u4F60\u5224\u65AD\u7684\u6839\u56E0\u7C7B\u522B + \u5EFA\u8BAE\u4EBA\u5177\u4F53\u505A\u4EC0\u4E48>"\``,
46957
+ `- reason \u5199\u4EBA\u8BDD\uFF1A\u5148\u8BF4\u6839\u56E0\uFF08\u5982"\u7ED1\u5B9A\u8D26\u53F7\u914D\u989D\u6253\u6EE1\uFF0C\u672B\u6B21\u62A5 429 \u4F7F\u7528\u4E0A\u9650"\uFF09\uFF0C\u518D\u8BF4\u5EFA\u8BAE\uFF08\u5982"\u6362\u7ED1\u5B9A\u5230\u6709\u989D\u5EA6\u8D26\u53F7 / \u52A0\u989D\u5EA6 / \u51CF\u5E76\u53D1"\uFF09\u3002\u4EBA\u5728 inbox \u91CC\u76F4\u63A5\u770B\u8FD9\u6BB5\u3002`,
46958
+ `- **\u522B\u6539\u56FE**\uFF08link/spawn/cancel\uFF09\u2014\u2014\u8FD9\u4E0D\u662F\u7F3A\u4E0A\u6E38 / \u56FE\u75C5\uFF0C\u4E71\u6539\u56FE\u4F1A\u63A9\u76D6\u771F\u95EE\u9898\u3002`,
46959
+ `- **\u522B resolve-gap**\u2014\u2014\u8FD9\u91CC\u6CA1\u6709 gap\u3002`,
46960
+ `- **\u522B\u8BD5\u56FE\u8BA9\u5B83\u590D\u5DE5 / \u522B\u50AC\u91CD\u8DD1**\u2014\u2014\u5B83\u88AB\u51BB\u7740\u662F\u5BF9\u7684\uFF0C\u6839\u56E0\u6CA1\u9664\u653E\u51FA\u53BB\u5FC5\u518D\u683D\u3002`,
46961
+ `- **\u7EDD\u4E0D\u7A7A\u624B\u9000\u51FA**\uFF1A\u4E0D escalate = \u4EBA\u62FF\u5230\u7684\u8FD8\u662F\u7CFB\u7EDF\u7684\u901A\u7528\u5360\u4F4D\u3001\u8FD9\u6B21\u5206\u8BCA\u767D\u8DD1\u3002\u5224\u65AD\u4E0D\u4E86\u6839\u56E0\uFF0C\u4E5F\u628A"\u6211\u770B\u5230\u7684\u73B0\u8C61 + \u9700\u8981\u4EBA\u67E5\u4EC0\u4E48"escalate \u4E0A\u53BB\u3002`
46962
+ ].filter(Boolean).join("\n");
46963
+ }
46337
46964
  /**
46338
46965
  * 决策 0026:解析某节点所属**工单**的管理者——读该 workspace 根 brief 的 `fields.manager`(陪发起人聊出此单的
46339
46966
  * AI 实例),再经 `resolveManager` 拿它的 active record + binding。无 manager 字段 / 解析不到(非 active / 无绑定)
@@ -46558,6 +47185,7 @@ var init_src5 = __esm({
46558
47185
  init_playbooks2();
46559
47186
  init_scheduler();
46560
47187
  init_daemon_adapter();
47188
+ init_node_health();
46561
47189
  init_side_map();
46562
47190
  init_engine();
46563
47191
  init_fs_state();
@@ -47354,11 +47982,11 @@ var require_binaryParsers = __commonJS({
47354
47982
  var array2 = [];
47355
47983
  var i2;
47356
47984
  if (dimension.length > 1) {
47357
- var count = dimension.shift();
47358
- for (i2 = 0; i2 < count; i2++) {
47985
+ var count2 = dimension.shift();
47986
+ for (i2 = 0; i2 < count2; i2++) {
47359
47987
  array2[i2] = parse2(dimension, elementType2);
47360
47988
  }
47361
- dimension.unshift(count);
47989
+ dimension.unshift(count2);
47362
47990
  } else {
47363
47991
  for (i2 = 0; i2 < dimension[0]; i2++) {
47364
47992
  array2[i2] = parseElement(elementType2);
@@ -52699,8 +53327,8 @@ function renderIndexYaml2(index) {
52699
53327
  function renderProgressJsonl(progress) {
52700
53328
  return "```json\n" + progress.map((p2) => JSON.stringify(p2)).join("\n") + "\n```";
52701
53329
  }
52702
- function parseDocRef(ref) {
52703
- return ref.startsWith(DOC_REF_PREFIX) ? ref.slice(DOC_REF_PREFIX.length) : null;
53330
+ function parseDocRef(ref2) {
53331
+ return ref2.startsWith(DOC_REF_PREFIX) ? ref2.slice(DOC_REF_PREFIX.length) : null;
52704
53332
  }
52705
53333
  function runStateKey2(workOrderId, nodeId) {
52706
53334
  return `${workOrderId ?? ""}::${nodeId ?? ""}`;
@@ -52908,6 +53536,7 @@ var PostgresRegistryStore = class _PostgresRegistryStore {
52908
53536
  name text NOT NULL,
52909
53537
  email text,
52910
53538
  team_id text,
53539
+ reports_to text, -- \u6C47\u62A5\u4EBA\uFF08\u8D1F\u8D23\u4EBA\uFF09\uFF1A\u53E6\u4E00\u4E2A actor id\uFF0C\u53EF\u7A7A
52911
53540
  status text NOT NULL, -- active | disabled\uFF08\u5220\u9664\u5373\u505C\u7528\uFF09
52912
53541
  roles jsonb NOT NULL DEFAULT '[]'::jsonb, -- spec \xA76.2 \u540D\u518C\u89D2\u8272\uFF08\u4EFB\u52A1 2.0\uFF09
52913
53542
  avatar text, -- "blob:<hash>"\uFF0C\u7A7A=\u9ED8\u8BA4\u751F\u6210
@@ -52933,6 +53562,7 @@ var PostgresRegistryStore = class _PostgresRegistryStore {
52933
53562
  await pool.query(`ALTER TABLE "${s2}".actor_configs ADD COLUMN IF NOT EXISTS max_concurrent integer NOT NULL DEFAULT 4`);
52934
53563
  await pool.query(`ALTER TABLE "${s2}".actor_configs ADD COLUMN IF NOT EXISTS reviewer_ids jsonb NOT NULL DEFAULT '[]'::jsonb`);
52935
53564
  await pool.query(`ALTER TABLE "${s2}".actor_configs ADD COLUMN IF NOT EXISTS batched_continuation boolean NOT NULL DEFAULT false`);
53565
+ await pool.query(`ALTER TABLE "${s2}".actors ADD COLUMN IF NOT EXISTS reports_to text`);
52936
53566
  await pool.query(`
52937
53567
  CREATE TABLE IF NOT EXISTS "${s2}".teams (
52938
53568
  id text PRIMARY KEY,
@@ -53036,10 +53666,10 @@ var PostgresRegistryStore = class _PostgresRegistryStore {
53036
53666
  /* ---------- 员工 ---------- */
53037
53667
  async upsertActor(a) {
53038
53668
  await this.pool.query(
53039
- `INSERT INTO ${this.s}.actors (id, kind, name, email, team_id, status, roles, avatar, created_at)
53040
- VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9)
53041
- ON CONFLICT (id) DO UPDATE SET kind=$2, name=$3, email=$4, team_id=$5, status=$6, roles=$7::jsonb, avatar=$8`,
53042
- [a.id, a.kind, a.name, a.email ?? null, a.teamId ?? null, a.status, JSON.stringify(a.roles), a.avatar ?? null, a.createdAt]
53669
+ `INSERT INTO ${this.s}.actors (id, kind, name, email, team_id, status, roles, avatar, created_at, reports_to)
53670
+ VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10)
53671
+ ON CONFLICT (id) DO UPDATE SET kind=$2, name=$3, email=$4, team_id=$5, status=$6, roles=$7::jsonb, avatar=$8, reports_to=$10`,
53672
+ [a.id, a.kind, a.name, a.email ?? null, a.teamId ?? null, a.status, JSON.stringify(a.roles), a.avatar ?? null, a.createdAt, a.reportsTo ?? null]
53043
53673
  );
53044
53674
  }
53045
53675
  async getActor(id) {
@@ -53398,6 +54028,7 @@ var rowToActor = (row) => ({
53398
54028
  name: row.name,
53399
54029
  ...row.email !== null ? { email: row.email } : {},
53400
54030
  ...row.team_id !== null ? { teamId: row.team_id } : {},
54031
+ ...row.reports_to !== null && row.reports_to !== void 0 ? { reportsTo: row.reports_to } : {},
53401
54032
  status: row.status,
53402
54033
  roles: row.roles ?? [],
53403
54034
  ...row.avatar !== null && row.avatar !== void 0 ? { avatar: row.avatar } : {},
@@ -54884,7 +55515,7 @@ var PostgresTraceStore = class _PostgresTraceStore {
54884
55515
  const r = await this.pool.query(
54885
55516
  `SELECT actor_id, artifact_id, started_at, effective_model, usage
54886
55517
  FROM ${this.s}.agent_runs
54887
- WHERE usage IS NOT NULL AND started_at >= $1 AND started_at <= $2
55518
+ WHERE jsonb_typeof(usage) = 'object' AND started_at >= $1 AND started_at <= $2
54888
55519
  ORDER BY started_at`,
54889
55520
  [filter.from, filter.to]
54890
55521
  );
@@ -55690,6 +56321,7 @@ var rowToRun2 = (row) => ({
55690
56321
  });
55691
56322
 
55692
56323
  // ../storage/src/postgres-chat-sessions.ts
56324
+ init_chat_session();
55693
56325
  var ident7 = (s2) => {
55694
56326
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
55695
56327
  return s2;
@@ -55757,10 +56389,24 @@ var PostgresChatSessionStore = class _PostgresChatSessionStore {
55757
56389
  }
55758
56390
  async listSessions(humanActorId) {
55759
56391
  const r = await this.pool.query(
55760
- `SELECT * FROM ${this.s}.chat_sessions WHERE human_actor_id = $1 ORDER BY touched_at DESC`,
56392
+ `SELECT s.*, lm.status AS last_assistant_status, lm.content AS last_assistant_content
56393
+ FROM ${this.s}.chat_sessions s
56394
+ LEFT JOIN LATERAL (
56395
+ SELECT status, content FROM ${this.s}.chat_messages
56396
+ WHERE session_id = s.id AND role = 'assistant'
56397
+ ORDER BY seq DESC LIMIT 1
56398
+ ) lm ON TRUE
56399
+ WHERE s.human_actor_id = $1
56400
+ ORDER BY s.touched_at DESC`,
55761
56401
  [humanActorId]
55762
56402
  );
55763
- return r.rows.map(rowToSession);
56403
+ return r.rows.map((row) => {
56404
+ const quality = deriveLastTurnQuality({
56405
+ status: row.last_assistant_status ?? void 0,
56406
+ content: row.last_assistant_content ?? ""
56407
+ });
56408
+ return { ...rowToSession(row), ...quality ? { lastTurnQuality: quality } : {} };
56409
+ });
55764
56410
  }
55765
56411
  async getSession(id) {
55766
56412
  const r = await this.pool.query(`SELECT * FROM ${this.s}.chat_sessions WHERE id = $1`, [id]);
@@ -56401,10 +57047,10 @@ async function startServe(opts) {
56401
57047
  let nodeStore = await FileNodeStore.open(path16.join(opts.dir, "nodes.json"));
56402
57048
  const registryAuditFile = path16.join(opts.dir, "registry-audit.ndjson");
56403
57049
  const registryListeners = /* @__PURE__ */ new Set();
56404
- const registryAudit = (record5) => {
56405
- fs18.appendFile(registryAuditFile, JSON.stringify(record5) + "\n", () => {
57050
+ const registryAudit = (record6) => {
57051
+ fs18.appendFile(registryAuditFile, JSON.stringify(record6) + "\n", () => {
56406
57052
  });
56407
- for (const l of registryListeners) l({ kind: record5.kind, actor: record5.actor, target: record5.target, timestamp: record5.timestamp });
57053
+ for (const l of registryListeners) l({ kind: record6.kind, actor: record6.actor, target: record6.target, timestamp: record6.timestamp });
56408
57054
  };
56409
57055
  await syncRegistryRolesToKernel(registryStore, kernel);
56410
57056
  let builtinSkills = [];
@@ -56627,6 +57273,20 @@ async function startServe(opts) {
56627
57273
  fs18.appendFile(journalFile, JSON.stringify(entry) + "\n", () => {
56628
57274
  });
56629
57275
  };
57276
+ const readMetricJournal = () => {
57277
+ if (!fs18.existsSync(journalFile)) return [...journalRing];
57278
+ try {
57279
+ return fs18.readFileSync(journalFile, "utf8").split(/\r?\n/).filter(Boolean).flatMap((line) => {
57280
+ try {
57281
+ return [JSON.parse(line)];
57282
+ } catch {
57283
+ return [];
57284
+ }
57285
+ });
57286
+ } catch {
57287
+ return [...journalRing];
57288
+ }
57289
+ };
56630
57290
  const closeOrphanedDispatches = () => {
56631
57291
  const open2 = /* @__PURE__ */ new Map();
56632
57292
  for (const entry of journalRing) {
@@ -56668,6 +57328,8 @@ async function startServe(opts) {
56668
57328
  let hub = null;
56669
57329
  let chatRemoteAdapter = null;
56670
57330
  let dispatchRemoteAdapter = null;
57331
+ const nodeHealth = new NodeHealthTracker();
57332
+ const activeRunCountOf = (nodeId) => (chatRemoteAdapter?.activeRunCount(nodeId) ?? 0) + (dispatchRemoteAdapter?.activeRunCount(nodeId) ?? 0);
56671
57333
  let localUrl = "";
56672
57334
  const chatLiveSessions = /* @__PURE__ */ new Map();
56673
57335
  const remoteApiUrl = opts.publicUrl ? normalizeHttpBaseUrl(opts.publicUrl) : apiUrlFromGatewayUrl(opts.gatewayUrl);
@@ -56809,7 +57471,9 @@ async function startServe(opts) {
56809
57471
  nodeStore,
56810
57472
  registry: registryStore,
56811
57473
  gatewayUrl: opts.gatewayUrl ?? `ws://127.0.0.1:${opts.port ?? 7320}/node-gateway`,
56812
- repoRemote: opts.nodeRepoRemote ?? "git@github.com:open-friday/oasis-core.git"
57474
+ repoRemote: opts.nodeRepoRemote ?? "git@github.com:open-friday/oasis-core.git",
57475
+ nodeHealth,
57476
+ activeRunCountOf
56813
57477
  }),
56814
57478
  collabDomain({
56815
57479
  kernel,
@@ -56818,6 +57482,7 @@ async function startServe(opts) {
56818
57482
  registry: registryStore,
56819
57483
  artifacts: artifactStateStore,
56820
57484
  dispatchJournal: () => journalRing.slice(-200),
57485
+ metricsDispatchJournal: readMetricJournal,
56821
57486
  workorderDrafts,
56822
57487
  // 活工单查不到时回退查建单草案(status="draft")
56823
57488
  ...opts.sla ? { sla: opts.sla } : {},
@@ -57178,7 +57843,7 @@ ${detail}` : genericLine));
57178
57843
  }
57179
57844
  });
57180
57845
  hub = new DaemonHub(server.httpServer, "/node-gateway", (t) => nodeTokens.resolve(t));
57181
- const hubAdapterOpts = { log: (m2) => console.warn(m2) };
57846
+ const hubAdapterOpts = { log: (m2) => console.warn(m2), health: nodeHealth };
57182
57847
  chatRemoteAdapter = new DaemonHubAdapter(hub, hubAdapterOpts);
57183
57848
  dispatchRemoteAdapter = new DaemonHubAdapter(hub, hubAdapterOpts);
57184
57849
  const port = new URL(server.baseUrl).port;
@@ -57413,6 +58078,8 @@ ${detail}` : genericLine));
57413
58078
  }
57414
58079
  const humanMs = opts.sla?.humanMs ?? 24 * 36e5;
57415
58080
  const agentMs = opts.sla?.agentMs ?? 10 * 6e4;
58081
+ const quotaStarvationMs = opts.sla?.quotaStarvationMs ?? 6 * 36e5;
58082
+ let triageFailure;
57416
58083
  const initiatorCache = /* @__PURE__ */ new Map();
57417
58084
  const initiatorOf = async (workspace) => {
57418
58085
  if (initiatorCache.has(workspace)) return initiatorCache.get(workspace);
@@ -57485,23 +58152,44 @@ ${detail}` : genericLine));
57485
58152
  await escalate(a.id, a.owner, `human \u6301\u6709 ${Math.round(humanMs / 6e4)} \u5206\u949F\u672A\u52A8\u5DE5`);
57486
58153
  }
57487
58154
  }
58155
+ const escalateHold = async (artifactId, reason, kind) => {
58156
+ if (unresolvedEscalationsOf(kernel.model, artifactId).length > 0) return;
58157
+ if (isHeld(kernel.model, artifactId)) return;
58158
+ const owner = kernel.model.artifacts.get(artifactId)?.owner ?? "";
58159
+ try {
58160
+ await kernel.escalate({ artifactId, actor: SYSTEM_ACTOR, reason });
58161
+ await kernel.hold({ artifactId, actor: SYSTEM_ACTOR, source: "sla", reason });
58162
+ journal({ kind: "escalated", at: (/* @__PURE__ */ new Date()).toISOString(), artifactId, from: owner, to: owner, reason });
58163
+ console.log(`[sla] ${artifactId} ${kind}\uFF1A\u843D escalate + hold\uFF08owner \u4FDD\u6301 ${owner}\uFF0C\u4E0D\u593A\uFF09`);
58164
+ } catch (err) {
58165
+ console.error(`[sla] ${kind}\u5931\u8D25 ${artifactId}: ${String(err)}`);
58166
+ }
58167
+ };
57488
58168
  for (const f2 of allDispatchers().flatMap((d) => d.fusedJobs())) {
57489
58169
  if (f2.action !== "produce") continue;
57490
- const cur = kernel.model.artifacts.get(f2.artifactId);
57491
- if (!cur) continue;
58170
+ if (!kernel.model.artifacts.get(f2.artifactId)) continue;
57492
58171
  if (now - Date.parse(f2.fusedAt) <= agentMs) continue;
57493
58172
  if (unresolvedEscalationsOf(kernel.model, f2.artifactId).length > 0) continue;
57494
58173
  if (isHeld(kernel.model, f2.artifactId)) continue;
57495
- const reason = `agent \u8FDE\u8D25\u7194\u65AD\u540E ${Math.round(agentMs / 6e4)} \u5206\u949F\u65E0\u4EBA\u5904\u7406\uFF08\u8FDE\u8D25 ${f2.consecutiveFailures} \u6B21\uFF09`;
57496
- try {
57497
- await kernel.escalate({ artifactId: f2.artifactId, actor: SYSTEM_ACTOR, reason });
57498
- await kernel.hold({ artifactId: f2.artifactId, actor: SYSTEM_ACTOR, source: "sla", reason });
57499
- journal({ kind: "escalated", at: (/* @__PURE__ */ new Date()).toISOString(), artifactId: f2.artifactId, from: cur.owner, to: cur.owner, reason });
57500
- console.log(`[sla] ${f2.artifactId} \u7194\u65AD\u5347\u7EA7\uFF1A\u843D escalate + hold\uFF08owner \u4FDD\u6301 ${cur.owner}\uFF0C\u4E0D\u593A\uFF09`);
57501
- } catch (err) {
57502
- console.error(`[sla] \u7194\u65AD\u5347\u7EA7\u5931\u8D25 ${f2.artifactId}: ${String(err)}`);
58174
+ const failReason = `agent \u8FDE\u8D25\u7194\u65AD\u540E ${Math.round(agentMs / 6e4)} \u5206\u949F\u65E0\u4EBA\u5904\u7406\uFF08\u8FDE\u8D25 ${f2.consecutiveFailures} \u6B21\uFF09`;
58175
+ if (triageFailure) {
58176
+ const owner = kernel.model.artifacts.get(f2.artifactId)?.owner ?? "";
58177
+ try {
58178
+ await kernel.hold({ artifactId: f2.artifactId, actor: SYSTEM_ACTOR, source: "sla", reason: failReason });
58179
+ console.log(`[sla] ${f2.artifactId} \u7194\u65AD\u5347\u7EA7\uFF1A\u51BB\u7ED3 + \u89E6\u53D1\u81EA\u52A8\u5206\u8BCA\uFF08owner \u4FDD\u6301 ${owner}\uFF0C\u4E0D\u593A\uFF09`);
58180
+ void triageFailure(f2.artifactId, { consecutiveFailures: f2.consecutiveFailures, ...f2.lastReason ? { lastReason: f2.lastReason } : {} });
58181
+ } catch (err) {
58182
+ console.error(`[sla] \u7194\u65AD\u5347\u7EA7\u51BB\u7ED3\u5931\u8D25 ${f2.artifactId}: ${String(err)}`);
58183
+ }
58184
+ } else {
58185
+ await escalateHold(f2.artifactId, failReason, "\u7194\u65AD\u5347\u7EA7");
57503
58186
  }
57504
58187
  }
58188
+ for (const s2 of allDispatchers().flatMap((d) => d.starvingJobs(quotaStarvationMs, now))) {
58189
+ if (s2.action !== "produce") continue;
58190
+ if (!kernel.model.artifacts.get(s2.artifactId)) continue;
58191
+ await escalateHold(s2.artifactId, `\u914D\u989D\u957F\u671F\u4E0D\u8DB3\uFF1A\u7ED1\u5B9A\u8D26\u53F7\u5DF2\u9650\u6D41 ${Math.round(s2.sinceMs / 36e5)} \u5C0F\u65F6\u4ECD\u672A\u6062\u590D\uFF08\u7591\u4F3C\u7B97\u529B\u7F3A\u53E3\uFF0C\u9700\u52A0\u989D\u5EA6/\u6362\u7ED1\u5B9A/\u51CF\u5E76\u53D1\uFF09`, "\u914D\u989D\u9965\u997F\u5347\u7EA7");
58192
+ }
57505
58193
  })().catch((err) => console.error(`[sla] \u626B\u63CF\u5931\u8D25: ${String(err)}`));
57506
58194
  }, opts.sla?.scanMs ?? 6e4);
57507
58195
  let mirrorTimer;
@@ -57594,6 +58282,7 @@ ${detail}` : genericLine));
57594
58282
  // 0030 D7 落盘
57595
58283
  });
57596
58284
  coordinatorRef = coordinator;
58285
+ triageFailure = (artifactId, ctx) => coordinator.triageFailure(artifactId, ctx);
57597
58286
  coordTimer = setInterval(() => {
57598
58287
  void coordinator.tick().catch((err) => console.error(`[coordinator] tick \u5931\u8D25: ${String(err)}`));
57599
58288
  }, 15e3);
@@ -59756,7 +60445,7 @@ ${res.warning}`);
59756
60445
  }
59757
60446
 
59758
60447
  // src/index.ts
59759
- var PKG_VERSION = true ? "0.1.54" : "dev";
60448
+ var PKG_VERSION = true ? "0.1.57" : "dev";
59760
60449
  var OASIS_DIR = path18.join(os9.homedir(), ".oasis");
59761
60450
  var CONFIG_FILE = path18.join(OASIS_DIR, "node-config.json");
59762
60451
  var PID_FILE = path18.join(OASIS_DIR, "node.pid");