oasis_test 0.1.53 → 0.1.54

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 +485 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -46,6 +46,7 @@ function emptyReadModel() {
46
46
  annotations: /* @__PURE__ */ new Map(),
47
47
  annotationSeq: /* @__PURE__ */ new Map(),
48
48
  seals: /* @__PURE__ */ new Map(),
49
+ holds: /* @__PURE__ */ new Map(),
49
50
  lastOpAt: /* @__PURE__ */ new Map(),
50
51
  pinMeta: /* @__PURE__ */ new Map(),
51
52
  gaps: /* @__PURE__ */ new Map(),
@@ -316,13 +317,28 @@ function fold(model, op) {
316
317
  gap.resolved = true;
317
318
  gap.resolvedAtSeq = op.seq;
318
319
  if (p2.note !== void 0) gap.note = p2.note;
319
- for (const e of model.escalations.get(op.artifactId) ?? []) e.resolved = true;
320
+ for (const e of model.escalations.get(op.artifactId) ?? []) {
321
+ if (e.resolved) continue;
322
+ if (e.gapId !== void 0) {
323
+ if (e.gapId === p2.gapId) e.resolved = true;
324
+ } else if (e.part === gap.part) {
325
+ e.resolved = true;
326
+ }
327
+ }
320
328
  break;
321
329
  }
322
330
  case "escalate": {
323
331
  const p2 = op.payload;
324
332
  const list = model.escalations.get(op.artifactId) ?? [];
325
- list.push({ part: p2.part ?? null, reason: p2.reason, by: op.actor, ...op.hand !== void 0 ? { hand: op.hand } : {}, at: op.timestamp, resolved: false });
333
+ list.push({
334
+ part: p2.part ?? null,
335
+ reason: p2.reason,
336
+ by: op.actor,
337
+ ...op.hand !== void 0 ? { hand: op.hand } : {},
338
+ at: op.timestamp,
339
+ resolved: false,
340
+ ...p2.gapId !== void 0 ? { gapId: p2.gapId } : {}
341
+ });
326
342
  model.escalations.set(op.artifactId, list);
327
343
  break;
328
344
  }
@@ -418,6 +434,21 @@ function fold(model, op) {
418
434
  model.seals.delete(op.artifactId);
419
435
  break;
420
436
  }
437
+ case "hold": {
438
+ const p2 = op.payload;
439
+ if (!model.artifacts.has(op.artifactId)) throw new CorruptLogError(op, "hold on missing artifact");
440
+ model.holds.set(op.artifactId, {
441
+ source: p2.source,
442
+ by: op.actor,
443
+ at: op.timestamp,
444
+ ...p2.reason !== void 0 ? { reason: p2.reason } : {}
445
+ });
446
+ break;
447
+ }
448
+ case "release": {
449
+ model.holds.delete(op.artifactId);
450
+ break;
451
+ }
421
452
  case "unlink_input": {
422
453
  const p2 = op.payload;
423
454
  const artifact = model.artifacts.get(op.artifactId);
@@ -499,6 +530,12 @@ function lifecycleOf(model, id) {
499
530
  const seal = model.seals.get(id);
500
531
  return seal ? `sealed:${seal.reason}` : "active";
501
532
  }
533
+ function isHeld(model, id) {
534
+ return model.holds.has(id);
535
+ }
536
+ function holdOf(model, id) {
537
+ return model.holds.get(id) ?? null;
538
+ }
502
539
  function annotationsOf(model, id) {
503
540
  const revisionIds = new Set(revisionsOf(model, id).map((r) => r.id));
504
541
  return [...model.annotations.values()].filter(
@@ -565,7 +602,14 @@ function findGap(model, gapId) {
565
602
  return null;
566
603
  }
567
604
  function unresolvedEscalationsOf(model, id) {
568
- return (model.escalations.get(id) ?? []).filter((e) => !e.resolved).map((e) => ({ part: e.part, reason: e.reason, by: e.by, ...e.hand !== void 0 ? { hand: e.hand } : {}, at: e.at }));
605
+ return (model.escalations.get(id) ?? []).filter((e) => !e.resolved).map((e) => ({
606
+ part: e.part,
607
+ reason: e.reason,
608
+ by: e.by,
609
+ ...e.hand !== void 0 ? { hand: e.hand } : {},
610
+ at: e.at,
611
+ ...e.gapId !== void 0 ? { gapId: e.gapId } : {}
612
+ }));
569
613
  }
570
614
  function listPendingReviews(model, workspace) {
571
615
  const all = [...model.pendingReviews.values()];
@@ -606,6 +650,16 @@ function stuckDiagnosis(model, id, opts) {
606
650
  if (!artifact || lifecycle !== "active") {
607
651
  return { id, lifecycle, staleMs, causes, needsCoordinator: false };
608
652
  }
653
+ if (isHeld(model, id)) {
654
+ const h = holdOf(model, id);
655
+ causes.push({
656
+ category: "held",
657
+ source: "oplog",
658
+ detail: `\u8282\u70B9\u5DF2\u6682\u505C\u6D3E\u53D1\uFF08${h?.source ?? "?"}${h?.reason ? `\uFF1A${h.reason}` : ""}\uFF09\u2014\u2014\u5F85\u89E3\u963B`,
659
+ handler: "human"
660
+ });
661
+ return { id, lifecycle, staleMs, causes, needsCoordinator: false };
662
+ }
609
663
  if (artifact.owner.startsWith("pending:role:")) {
610
664
  causes.push({
611
665
  category: "owner-pending",
@@ -2454,19 +2508,28 @@ var init_kernel = __esm({
2454
2508
  */
2455
2509
  async escalate(args) {
2456
2510
  this.artifactOrThrow(args.artifactId);
2457
- if (!isHumanActor(args.actor)) {
2511
+ if (!isHumanActor(args.actor) && args.actor !== SYSTEM_ACTOR) {
2458
2512
  const ws = this.model.artifacts.get(args.artifactId)?.workspace;
2459
2513
  const brief = [...this.model.artifacts.values()].find((a) => a.type === "brief" && a.workspace === ws);
2460
2514
  const manager = brief?.fields?.["manager"];
2461
2515
  if (manager !== args.actor) {
2462
2516
  throw new KernelError(
2463
- `escalate \u4EC5\u9650\u672C\u5DE5\u5355\u7BA1\u7406\u8005\uFF08brief.fields.manager\uFF09\u6216\u4EBA\u7C7B\u8C03\u7528\u2014\u2014${args.actor} \u4E0D\u662F\u3002\u4F60\u82E5\u662F producer/owner \u5361\u4F4F\u4E86\uFF1A\u7528 \`oasis gap\` \u4E0A\u62A5\u9700\u8981\uFF0C\u7531\u7BA1\u7406\u8005\u5206\u8BCA\uFF08\u51B3\u7B56 0030 D4\uFF09\u3002`
2517
+ `escalate \u4EC5\u9650\u672C\u5DE5\u5355\u7BA1\u7406\u8005\uFF08brief.fields.manager\uFF09\u3001\u4EBA\u7C7B\u6216\u7CFB\u7EDF\u8C03\u7528\u2014\u2014${args.actor} \u4E0D\u662F\u3002\u4F60\u82E5\u662F producer/owner \u5361\u4F4F\u4E86\uFF1A\u7528 \`oasis gap\` \u4E0A\u62A5\u9700\u8981\uFF0C\u7531\u7BA1\u7406\u8005\u5206\u8BCA\uFF08\u51B3\u7B56 0030 D4\uFF09\u3002`
2464
2518
  );
2465
2519
  }
2466
2520
  }
2521
+ let part = args.part;
2522
+ let gapId = args.gapId;
2523
+ if (gapId) {
2524
+ const found = findGap(this.model, gapId);
2525
+ if (found && found.artifactId === args.artifactId) {
2526
+ if (part === void 0 && found.gap.part) part = found.gap.part;
2527
+ }
2528
+ }
2467
2529
  await this.commit(args.artifactId, args.actor, "escalate", args.artifactId, {
2468
2530
  reason: args.reason,
2469
- ...args.part !== void 0 ? { part: args.part } : {}
2531
+ ...part !== void 0 ? { part } : {},
2532
+ ...gapId !== void 0 ? { gapId } : {}
2470
2533
  }, void 0, args.hand);
2471
2534
  }
2472
2535
  /** 决策 0027 D1:人显式关闭本 artifact 上的 escalate(off-graph 解决兜底)。 */
@@ -2619,6 +2682,43 @@ var init_kernel = __esm({
2619
2682
  const payload = { persistence: "persistent" };
2620
2683
  await this.commit(args.artifactId, args.actor, "promote", args.artifactId, payload);
2621
2684
  }
2685
+ /**
2686
+ * 节点级派发暂停(node-hold,本批):落一条 hold op——**只挡 agent 派发、不锁内容**(≠ seal/freeze,
2687
+ * 见 docs/notes/artifact-seal-lifecycle.md)。用于自动升级冻结(source:"sla")与人/管理者显式暂停。
2688
+ * 不校验 lifecycle:sealed 节点本就不派、held 于它无意义但无害;主用途是冻 active 节点。幂等——重复 hold 覆盖来源。
2689
+ */
2690
+ async hold(args) {
2691
+ this.artifactOrThrow(args.artifactId);
2692
+ this.assertHoldAdmission(args.artifactId, args.actor);
2693
+ const payload = {
2694
+ source: args.source,
2695
+ ...args.reason !== void 0 ? { reason: args.reason } : {}
2696
+ };
2697
+ await this.commit(args.artifactId, args.actor, "hold", args.artifactId, payload);
2698
+ }
2699
+ /** 解阻(node-hold):撤销 hold,节点恢复可派(投影下一拍重推当下欠的活)。空解(未 held)拦下,避免日志噪声。 */
2700
+ async release(args) {
2701
+ this.artifactOrThrow(args.artifactId);
2702
+ this.assertHoldAdmission(args.artifactId, args.actor);
2703
+ if (!isHeld(this.model, args.artifactId)) throw new KernelError(`not held: ${args.artifactId}`);
2704
+ const payload = { ...args.reason !== void 0 ? { reason: args.reason } : {} };
2705
+ await this.commit(args.artifactId, args.actor, "release", args.artifactId, payload);
2706
+ }
2707
+ /**
2708
+ * hold/release 准入闸(红线 6:管控在服务端收紧):人类 / 本工单管理者 / `actor:system`(自动升级)可调;
2709
+ * 普通 producer/owner agent 不可——防 agent 把自己节点暂停了逃避派发(同 escalate 的机制闸精神,kernel.escalate)。
2710
+ */
2711
+ assertHoldAdmission(artifactId, actor) {
2712
+ if (isHumanActor(actor)) return;
2713
+ if (actor === SYSTEM_ACTOR) return;
2714
+ const ws = this.model.artifacts.get(artifactId)?.workspace;
2715
+ const brief = [...this.model.artifacts.values()].find((a) => a.type === "brief" && a.workspace === ws);
2716
+ const manager = brief?.fields?.["manager"];
2717
+ if (manager === actor) return;
2718
+ throw new KernelError(
2719
+ `hold/release \u4EC5\u9650\u4EBA\u7C7B\u3001\u672C\u5DE5\u5355\u7BA1\u7406\u8005\uFF08brief.fields.manager\uFF09\u6216\u7CFB\u7EDF\u8C03\u7528\u2014\u2014${actor} \u4E0D\u662F\u3002`
2720
+ );
2721
+ }
2622
2722
  // ── 治理 op(§11.3):force/override 单点豁免可直发;结构 op 以 Intervention 为载体 ──────
2623
2723
  /** 治理解封:sealed → active(lifecycle = 最后一条 seal/reopen,§4.4)。frozen 解冻、cancelled 复活都走它 */
2624
2724
  async reopen(args) {
@@ -4269,6 +4369,7 @@ var init_dispatcher = __esm({
4269
4369
  const { kernel } = this.opts;
4270
4370
  const jobWorkspace = kernel.model.artifacts.get(spec.artifactId)?.workspace;
4271
4371
  if (jobWorkspace && await this.opts.resolveDispatchHold?.(jobWorkspace)) return 0;
4372
+ if (isHeld(kernel.model, spec.artifactId)) return 0;
4272
4373
  this.assertSpawnAttemptCurrent(jobKey, attempt);
4273
4374
  const seqNow = kernel.model.lastSeq.get(spec.artifactId) ?? 0;
4274
4375
  const strike = this.strikes.get(jobKey);
@@ -24554,7 +24655,8 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
24554
24655
  artifactId: str(args, "artifactId"),
24555
24656
  actor,
24556
24657
  reason: str(args, "reason"),
24557
- ...optStr(args, "part") !== void 0 ? { part: optStr(args, "part") } : {}
24658
+ ...optStr(args, "part") !== void 0 ? { part: optStr(args, "part") } : {},
24659
+ ...optStr(args, "gapId") !== void 0 ? { gapId: optStr(args, "gapId") } : {}
24558
24660
  });
24559
24661
  return { message: '\u5DF2\u4E0A\u62A5\uFF08escalate\uFF09\uFF1A\u7CFB\u7EDF\u5C06\u5347\u7EA7\u5230\u8D1F\u8D23\u4EBA\u6536\u4EF6\u7BB1\u3002\u4F60\u53EF\u4EE5\u6536\u5DE5\u4E86\uFF1B\u4E4B\u540E\u53EF\u80FD\u5728\u5BF9\u8BDD\u91CC\u88AB ta \u95EE\u5230\u3002\u26A0\uFE0F \u82E5\u8FD9\u6B21\u4E0A\u62A5\u5173\u8054\u67D0\u6761 gap\uFF0C**\u4E0D\u8981\u5BF9\u5B83\u8C03 resolve-gap**\u2014\u2014\u90A3\u8868\u793A"\u9700\u6C42\u5DF2\u6EE1\u8DB3\u3001\u8BA9 owner \u590D\u5DE5"\uFF0C\u73B0\u5728\u8FD8\u6CA1\u6EE1\u8DB3\uFF1Bgap \u4FDD\u6301 open \u8282\u70B9\u624D\u4F1A\u7EE7\u7EED\u7B49\u5F85\u3002' };
24560
24662
  }
@@ -24610,6 +24712,23 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
24610
24712
  await kernel.reopen({ artifactId: str(args, "artifactId"), actor, note: optStr(args, "note") });
24611
24713
  return { message: `reopened ${str(args, "artifactId")}\uFF08lifecycle \u2192 active\uFF09` };
24612
24714
  }
24715
+ case "hold": {
24716
+ await kernel.hold({
24717
+ artifactId: str(args, "artifactId"),
24718
+ actor,
24719
+ source: optStr(args, "source") ?? "human",
24720
+ ...optStr(args, "reason") !== void 0 ? { reason: optStr(args, "reason") } : {}
24721
+ });
24722
+ return { message: `\u8282\u70B9\u5DF2\u6682\u505C\u6D3E\u53D1\uFF08hold\uFF09\uFF1A${str(args, "artifactId")}\u3002\u6062\u590D\u7528 release\u3002` };
24723
+ }
24724
+ case "release": {
24725
+ await kernel.release({
24726
+ artifactId: str(args, "artifactId"),
24727
+ actor,
24728
+ ...optStr(args, "reason") !== void 0 ? { reason: optStr(args, "reason") } : {}
24729
+ });
24730
+ return { message: `\u8282\u70B9\u5DF2\u89E3\u963B\uFF08release\uFF09\uFF1A${str(args, "artifactId")}\u2014\u2014\u6295\u5F71\u4E0B\u4E00\u62CD\u4F1A\u91CD\u63A8\u8BE5\u8282\u70B9\u5F53\u4E0B\u6B20\u7684\u6D3B\u3002` };
24731
+ }
24613
24732
  case "forceConclude": {
24614
24733
  await kernel.forceConclude({
24615
24734
  artifactId: str(args, "artifactId"),
@@ -26675,6 +26794,15 @@ var init_memory_trace_store = __esm({
26675
26794
  const nextCursor = items.length > limit ? page[page.length - 1]?.id ?? null : null;
26676
26795
  return { items: page.map((r) => this.decorate(structuredClone(r))), nextCursor };
26677
26796
  }
26797
+ async listUsageRuns(filter) {
26798
+ return [...this.runs.values()].filter((run) => run.usage !== null && run.startedAt >= filter.from && run.startedAt <= filter.to).map((run) => ({
26799
+ actorId: run.actorId,
26800
+ artifactId: run.artifactId,
26801
+ startedAt: run.startedAt,
26802
+ effectiveModel: run.effectiveModel,
26803
+ usage: structuredClone(run.usage)
26804
+ }));
26805
+ }
26678
26806
  async appendEvent(runId, event) {
26679
26807
  return (await this.appendEvents(runId, [event]))[0];
26680
26808
  }
@@ -39704,6 +39832,8 @@ async function runACPSession(job, cfg) {
39704
39832
  ...job.env,
39705
39833
  ...cfg.yoloEnv ?? {},
39706
39834
  ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path11.dirname(p2)).join(path11.delimiter)}${path11.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
39835
+ // 每会话技能目录绝对路径 → env(供不扫 cwd 的 runtime 经 config external_dirs 发现,见 injectSkillsDirEnv)。
39836
+ ...cfg.injectSkillsDirEnv ? { [cfg.injectSkillsDirEnv]: path11.join(dir, sessionSkillsSubdir(cfg.runtimeKind ?? "")) } : {},
39707
39837
  OASIS_SERVER: job.server.url,
39708
39838
  OASIS_TOKEN: job.actorToken
39709
39839
  },
@@ -39946,6 +40076,7 @@ var init_acp = __esm({
39946
40076
  path11 = __toESM(require("node:path"), 1);
39947
40077
  readline3 = __toESM(require("node:readline"), 1);
39948
40078
  init_sync_skills();
40079
+ init_skill_cache();
39949
40080
  init_claude_code();
39950
40081
  init_usage();
39951
40082
  init_pricing();
@@ -40218,6 +40349,11 @@ var init_hermes = __esm({
40218
40349
  },
40219
40350
  extraArgs: this.opts.extraArgs,
40220
40351
  runtimeKind: "hermes",
40352
+ // hermes 不扫 cwd 技能目录(只认 $HERMES_HOME/skills + config external_dirs)。把本会话
40353
+ // 技能目录绝对路径喂到 OASIS_HERMES_SKILLS_DIR;node enroll 已把 config.yaml 的
40354
+ // skills.external_dirs 配成该 env → hermes 每 session 发现各自技能。见
40355
+ // packages/adapters/docs/modules/07-skills-delivery.md 与 connect-script.ts。
40356
+ injectSkillsDirEnv: "OASIS_HERMES_SKILLS_DIR",
40221
40357
  ...this.opts.syncSkills ? { syncSkills: this.opts.syncSkills } : {}
40222
40358
  });
40223
40359
  }
@@ -41036,6 +41172,45 @@ if command -v hermes &>/dev/null; then
41036
41172
  "$HERMES_PIP" install 'agent-client-protocol>=0.9.0,<1.0'
41037
41173
  fi
41038
41174
  fi
41175
+ # Oasis \u6BCF\u4F1A\u8BDD\u6280\u80FD\uFF1Ahermes \u4E0D\u626B cwd\uFF0C\u53EA\u8BA4 $HERMES_HOME/skills + config skills.external_dirs\u3002
41176
+ # \u5E42\u7B49\u5730\u628A config.yaml \u7684 external_dirs \u914D\u6210\u5B57\u9762\u91CF $OASIS_HERMES_SKILLS_DIR\uFF08\u7531 hermes \u8FD0\u884C\u65F6
41177
+ # \u6309 env \u5C55\u5F00\uFF1Badapter \u6BCF\u4F1A\u8BDD\u628A\u8BE5 env \u6307\u5411\u672C workdir \u7684\u6280\u80FD\u76EE\u5F55 \u2192 \u6BCF session \u5404\u81EA\u53D1\u73B0\uFF0C\u8BFB\u4FA7\u9694\u79BB\uFF09\u3002
41178
+ HERMES_HOME_DIR="$HERMES_HOME"
41179
+ if [ -z "$HERMES_HOME_DIR" ]; then HERMES_HOME_DIR="$HOME/.hermes"; fi
41180
+ "$HERMES_PY" - "$HERMES_HOME_DIR/config.yaml" <<'PYEOF' || true
41181
+ import sys, os
41182
+ try:
41183
+ import yaml
41184
+ except Exception:
41185
+ sys.exit(0)
41186
+ p = sys.argv[1]
41187
+ data = {}
41188
+ if os.path.exists(p):
41189
+ try:
41190
+ with open(p) as f:
41191
+ data = yaml.safe_load(f) or {}
41192
+ except Exception:
41193
+ sys.exit(0)
41194
+ if not isinstance(data, dict):
41195
+ sys.exit(0)
41196
+ skills = data.get("skills")
41197
+ if not isinstance(skills, dict):
41198
+ skills = {}
41199
+ data["skills"] = skills
41200
+ ext = skills.get("external_dirs")
41201
+ if not isinstance(ext, list):
41202
+ ext = [] if not ext else [ext]
41203
+ skills["external_dirs"] = ext
41204
+ token = "$OASIS_HERMES_SKILLS_DIR"
41205
+ if token not in ext:
41206
+ ext.append(token)
41207
+ os.makedirs(os.path.dirname(p), exist_ok=True)
41208
+ tmp = p + ".oasis.tmp"
41209
+ with open(tmp, "w") as f:
41210
+ yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True)
41211
+ os.replace(tmp, p)
41212
+ print("-> hermes config.yaml: skills.external_dirs += $OASIS_HERMES_SKILLS_DIR")
41213
+ PYEOF
41039
41214
  fi
41040
41215
 
41041
41216
  OASIS_DIR="$(pwd)"
@@ -41743,8 +41918,18 @@ var init_node_store = __esm({
41743
41918
  });
41744
41919
 
41745
41920
  // ../server/src/domains/collab/inbox.ts
41746
- function buildInbox(model, me, leadOf) {
41921
+ function buildInbox(model, me, leadOf, resolveActor) {
41747
41922
  const out = [];
41923
+ const gapItems = [];
41924
+ const actorRef = (id) => {
41925
+ const extra = resolveActor?.(id);
41926
+ return {
41927
+ id,
41928
+ ...extra?.name ? { name: extra.name } : {},
41929
+ ...extra?.role ? { role: extra.role } : {},
41930
+ ...extra?.avatar ? { avatar: extra.avatar } : {}
41931
+ };
41932
+ };
41748
41933
  const creatorOf = /* @__PURE__ */ new Map();
41749
41934
  for (const a of model.artifacts.values()) {
41750
41935
  if (a.type === "brief") creatorOf.set(a.workspace, a.owner);
@@ -41765,44 +41950,53 @@ function buildInbox(model, me, leadOf) {
41765
41950
  summary: `\u5DE5\u5355\u7F3A\u4EBA\u624B\uFF1A${pendingRole[1]} \u89D2\u8272\u65E0\u5458\u5DE5\uFF0C\u300C${nodeLabel(a)}\u300D\u5F85\u6D3E\u2014\u2014\u8BF7\u8865\u4EBA\u6216 assign`
41766
41951
  });
41767
41952
  }
41768
- const gapEscalationSeen = /* @__PURE__ */ new Set();
41769
41953
  for (const a of model.artifacts.values()) {
41770
- const escs = unresolvedEscalationsOf(model, a.id);
41771
- if (escs.length === 0) continue;
41772
41954
  const isOwnerRecipient = creatorOf.get(a.workspace) === me;
41773
41955
  const isLeadRecipient = !!leadOf && leadOf(a.owner) === me;
41774
41956
  if (!isOwnerRecipient && !isLeadRecipient) continue;
41775
- const key = `${a.workspace}:${a.id}`;
41776
- if (gapEscalationSeen.has(key)) continue;
41777
- gapEscalationSeen.add(key);
41778
- const who = isOwnerRecipient ? "\uFF08\u4F60\u662F\u5DE5\u5355\u5F52\u5C5E\u4EBA\uFF09" : "\uFF08\u4F60\u662F\u5361\u4F4F\u8282\u70B9 owner \u7684\u4E0A\u7EA7\uFF09";
41779
- out.push({
41780
- kind: "gap-escalation",
41781
- artifactId: a.id,
41782
- workspace: a.workspace,
41783
- since: escs.at(-1).at,
41784
- actionRequired: true,
41785
- summary: `\u300C${nodeLabel(a)}\u300D\u88AB\u4E0A\u62A5\u9700\u4F60\u4ECB\u5165${who}\uFF1A${escs.at(-1).reason.slice(0, 100)}${escs.length > 1 ? `\uFF08\u5171 ${escs.length} \u6761\uFF09` : ""}`
41786
- });
41787
- }
41788
- for (const a of model.artifacts.values()) {
41789
41957
  const gaps = unresolvedGapsOf(model, a.id);
41790
- if (gaps.length === 0) continue;
41791
- const isOwnerRecipient = creatorOf.get(a.workspace) === me;
41792
- const isLeadRecipient = !!leadOf && leadOf(a.owner) === me;
41793
- if (!isOwnerRecipient && !isLeadRecipient) continue;
41794
- const key = `${a.workspace}:${a.id}`;
41795
- if (gapEscalationSeen.has(key)) continue;
41796
- gapEscalationSeen.add(key);
41958
+ const escalations = unresolvedEscalationsOf(model, a.id);
41797
41959
  const who = isOwnerRecipient ? "\uFF08\u4F60\u662F\u5DE5\u5355\u5F52\u5C5E\u4EBA\uFF09" : "\uFF08\u4F60\u662F\u5361\u4F4F\u8282\u70B9 owner \u7684\u4E0A\u7EA7\uFF09";
41798
- out.push({
41799
- kind: "gap-escalation",
41800
- artifactId: a.id,
41801
- workspace: a.workspace,
41802
- since: model.lastOpAt.get(a.id) ?? "",
41803
- actionRequired: true,
41804
- summary: `\u300C${nodeLabel(a)}\u300D\u5361\u5728\u672A\u89E3\u51B3\u7684\u7F3A\u53E3\u4E0A\u3001\u9700\u4F60\u4ECB\u5165${who}\uFF1A${gaps[0].description.slice(0, 80)}${gaps.length > 1 ? `\uFF08\u5171 ${gaps.length} \u6761\uFF09` : ""}`
41805
- });
41960
+ for (const gap of gaps) {
41961
+ const byGapId = escalations.filter((entry) => entry.gapId === gap.gapId);
41962
+ const samePartGaps = gaps.filter((candidate) => candidate.part === gap.part);
41963
+ const samePartEscalations = escalations.filter(
41964
+ (entry) => entry.gapId === void 0 && entry.part === gap.part
41965
+ );
41966
+ const partlessEscalations = escalations.filter(
41967
+ (entry) => entry.gapId === void 0 && entry.part === null
41968
+ );
41969
+ const exactEscalation = byGapId.at(-1) ?? (samePartGaps.length === 1 ? samePartEscalations.at(-1) : void 0) ?? (gaps.length === 1 ? partlessEscalations.at(-1) : void 0);
41970
+ gapItems.push({
41971
+ kind: "gap-escalation",
41972
+ artifactId: a.id,
41973
+ workspace: a.workspace,
41974
+ since: exactEscalation?.at ?? gap.lastReportedAt ?? model.lastOpAt.get(a.id) ?? "",
41975
+ actionRequired: true,
41976
+ summary: exactEscalation?.reason ?? gap.description,
41977
+ gap: {
41978
+ gapId: gap.gapId,
41979
+ // 契约:reporter 永远是 GapView.by(原始上报人);诊断者另见 diagnosis 文案,不覆盖署名。
41980
+ reporter: actorRef(gap.by),
41981
+ artifactLabel: nodeLabel(a),
41982
+ problem: gap.description,
41983
+ ...exactEscalation ? { diagnosis: exactEscalation.reason } : {},
41984
+ priority: exactEscalation ? "decision_required" : "blocked",
41985
+ reportCount: gap.reportCount ?? 1
41986
+ }
41987
+ });
41988
+ }
41989
+ if (gaps.length === 0 && escalations.length > 0) {
41990
+ const last = escalations.at(-1);
41991
+ gapItems.push({
41992
+ kind: "gap-escalation",
41993
+ artifactId: a.id,
41994
+ workspace: a.workspace,
41995
+ since: last.at,
41996
+ actionRequired: true,
41997
+ summary: `\u300C${nodeLabel(a)}\u300D\u88AB\u4E0A\u62A5\u9700\u4F60\u4ECB\u5165${who}\uFF1A${last.reason.slice(0, 100)}${escalations.length > 1 ? `\uFF08\u5171 ${escalations.length} \u6761\uFF09` : ""}`
41998
+ });
41999
+ }
41806
42000
  }
41807
42001
  for (const r of listPendingReviews(model)) {
41808
42002
  if (r.origin) continue;
@@ -41910,7 +42104,10 @@ function buildInbox(model, me, leadOf) {
41910
42104
  if (gate) continue;
41911
42105
  out.push({ kind: "assigned", artifactId: a.id, workspace: ws, since: model.lastOpAt.get(a.id) ?? "", actionRequired: true, summary: `\u5728\u9014\u5F85\u529E\uFF1A${label}` });
41912
42106
  }
41913
- return out.sort((x2, y) => x2.since < y.since ? 1 : x2.since > y.since ? -1 : 0);
42107
+ const rank = (item) => item.gap?.priority === "decision_required" ? 0 : 1;
42108
+ gapItems.sort((a, b2) => rank(a) - rank(b2) || a.since.localeCompare(b2.since));
42109
+ out.sort((x2, y) => x2.since < y.since ? 1 : x2.since > y.since ? -1 : 0);
42110
+ return [...gapItems, ...out];
41914
42111
  }
41915
42112
  var init_inbox = __esm({
41916
42113
  "../server/src/domains/collab/inbox.ts"() {
@@ -41970,7 +42167,9 @@ var init_audit = __esm({
41970
42167
  reopen: "\u91CD\u5F00",
41971
42168
  seal: "\u5C01\u5B58",
41972
42169
  link_input: "\u63A5\u4F9D\u8D56",
41973
- report_gap: "\u7559\u7F3A\u53E3"
42170
+ report_gap: "\u7559\u7F3A\u53E3",
42171
+ hold: "\u6682\u505C\u6D3E\u53D1",
42172
+ release: "\u89E3\u963B"
41974
42173
  };
41975
42174
  }
41976
42175
  });
@@ -42021,6 +42220,139 @@ var init_dashboard = __esm({
42021
42220
  }
42022
42221
  });
42023
42222
 
42223
+ // ../server/src/domains/collab/organization-usage.ts
42224
+ function emptyUsage() {
42225
+ return {
42226
+ input: 0,
42227
+ cache: 0,
42228
+ output: 0,
42229
+ tokens: 0,
42230
+ costUsdMicros: 0,
42231
+ costParts: { input: 0, cache: 0, output: 0 }
42232
+ };
42233
+ }
42234
+ function usageOf(run) {
42235
+ if (!run.effectiveModel?.trim()) return null;
42236
+ const input = Math.max(run.usage.inputTokens ?? 0, 0);
42237
+ const cache = Math.max(run.usage.cacheReadTokens ?? 0, 0) + Math.max(run.usage.cacheCreationTokens ?? 0, 0);
42238
+ const output = Math.max(run.usage.outputTokens ?? 0, 0);
42239
+ const tokens = input + cache + output;
42240
+ const costUsdMicros = Math.max(run.usage.costUsdMicros ?? 0, 0);
42241
+ const denominator = tokens || 1;
42242
+ const inputCost = Math.round(costUsdMicros * input / denominator);
42243
+ const cacheCost = Math.round(costUsdMicros * cache / denominator);
42244
+ return {
42245
+ input,
42246
+ cache,
42247
+ output,
42248
+ tokens,
42249
+ costUsdMicros,
42250
+ costParts: {
42251
+ input: inputCost,
42252
+ cache: cacheCost,
42253
+ output: costUsdMicros - inputCost - cacheCost
42254
+ }
42255
+ };
42256
+ }
42257
+ function addUsage(target, addition) {
42258
+ target.input += addition.input;
42259
+ target.cache += addition.cache;
42260
+ target.output += addition.output;
42261
+ target.tokens += addition.tokens;
42262
+ target.costUsdMicros += addition.costUsdMicros;
42263
+ target.costParts.input += addition.costParts.input;
42264
+ target.costParts.cache += addition.costParts.cache;
42265
+ target.costParts.output += addition.costParts.output;
42266
+ }
42267
+ function bucketFormatter(timeZone, kind) {
42268
+ return new Intl.DateTimeFormat("en-CA", {
42269
+ timeZone,
42270
+ year: "numeric",
42271
+ month: "2-digit",
42272
+ day: "2-digit",
42273
+ ...kind === "hour" ? { hour: "2-digit", hourCycle: "h23" } : {}
42274
+ });
42275
+ }
42276
+ function bucketKey(formatter, value, kind) {
42277
+ const date3 = new Date(value);
42278
+ if (!Number.isFinite(date3.getTime())) return null;
42279
+ const parts = new Map(formatter.formatToParts(date3).map((part) => [part.type, part.value]));
42280
+ const day = `${parts.get("year")}-${parts.get("month")}-${parts.get("day")}`;
42281
+ return kind === "hour" ? `${day}T${parts.get("hour")}` : day;
42282
+ }
42283
+ function buildOrganizationUsageSummary(input) {
42284
+ const formatter = bucketFormatter(input.timeZone, input.bucketKind);
42285
+ const workorders = buildWorkorderSummaries(input.model);
42286
+ const workorderById = new Map(workorders.map((workorder) => [workorder.id, workorder]));
42287
+ const workspaceByArtifact = new Map([...input.model.artifacts.values()].map((artifact) => [artifact.id, artifact.workspace]));
42288
+ const buckets = /* @__PURE__ */ new Map();
42289
+ const rows = /* @__PURE__ */ new Map();
42290
+ const total = emptyUsage();
42291
+ let unpricedRunCount = 0;
42292
+ for (const run of input.runs) {
42293
+ const usage = usageOf(run);
42294
+ if (!usage) {
42295
+ unpricedRunCount += 1;
42296
+ continue;
42297
+ }
42298
+ addUsage(total, usage);
42299
+ const key = bucketKey(formatter, run.startedAt, input.bucketKind);
42300
+ if (key) {
42301
+ const bucket = buckets.get(key) ?? { key, ...emptyUsage() };
42302
+ addUsage(bucket, usage);
42303
+ buckets.set(key, bucket);
42304
+ }
42305
+ const workspace = run.artifactId ? workspaceByArtifact.get(run.artifactId) : void 0;
42306
+ const workorder = workspace ? workorderById.get(workspace) : void 0;
42307
+ const rowId = workorder?.id ?? UNASSIGNED_ID;
42308
+ let row = rows.get(rowId);
42309
+ if (!row) {
42310
+ row = {
42311
+ id: rowId,
42312
+ title: workorder?.title ?? "\u672A\u5173\u8054\u5DE5\u5355",
42313
+ stage: workorder?.stage ?? "unassigned",
42314
+ usage: emptyUsage(),
42315
+ employees: [],
42316
+ share: 0,
42317
+ ...!workorder ? { unassigned: true } : {}
42318
+ };
42319
+ rows.set(rowId, row);
42320
+ }
42321
+ addUsage(row.usage, usage);
42322
+ let employee = row.employees.find((item) => item.actorId === run.actorId);
42323
+ if (!employee) {
42324
+ employee = { actorId: run.actorId, usage: emptyUsage(), share: 0 };
42325
+ row.employees.push(employee);
42326
+ }
42327
+ addUsage(employee.usage, usage);
42328
+ }
42329
+ const workorderRows = [...rows.values()].map((row) => {
42330
+ row.share = total.tokens > 0 ? row.usage.tokens / total.tokens : 0;
42331
+ row.employees = row.employees.map((employee) => ({
42332
+ ...employee,
42333
+ share: row.usage.tokens > 0 ? employee.usage.tokens / row.usage.tokens : 0
42334
+ })).sort((a, b2) => b2.usage.tokens - a.usage.tokens);
42335
+ return row;
42336
+ }).sort((a, b2) => {
42337
+ if (a.unassigned !== b2.unassigned) return a.unassigned ? 1 : -1;
42338
+ return b2.usage.tokens - a.usage.tokens;
42339
+ });
42340
+ return {
42341
+ total,
42342
+ buckets: [...buckets.values()].sort((a, b2) => a.key.localeCompare(b2.key)),
42343
+ workorders: workorderRows,
42344
+ unpricedRunCount
42345
+ };
42346
+ }
42347
+ var UNASSIGNED_ID;
42348
+ var init_organization_usage = __esm({
42349
+ "../server/src/domains/collab/organization-usage.ts"() {
42350
+ "use strict";
42351
+ init_workorders();
42352
+ UNASSIGNED_ID = "__unassigned__";
42353
+ }
42354
+ });
42355
+
42024
42356
  // ../server/src/domains/collab/index.ts
42025
42357
  async function resolveLead(registry2, me) {
42026
42358
  const rec = await registry2.getActor(me);
@@ -42098,6 +42430,34 @@ function collabDomain(opts) {
42098
42430
  return ctx;
42099
42431
  }
42100
42432
  return (router) => {
42433
+ router.get("/api/organization/usage-summary", async (req) => {
42434
+ if (!opts.trace) {
42435
+ return { status: 503, body: { error: { code: "no_trace_store", message: "\u8FD0\u884C\u7528\u91CF\u6570\u636E\u6E90\u672A\u63A5\u5165" } } };
42436
+ }
42437
+ const from = req.query.get("from");
42438
+ const to = req.query.get("to");
42439
+ const bucket = req.query.get("bucket");
42440
+ const timeZone = req.query.get("timeZone") ?? "UTC";
42441
+ if (!from || !to || bucket !== "hour" && bucket !== "day") {
42442
+ return { status: 400, body: { error: { code: "bad_request", message: "from / to / bucket(hour|day) \u5FC5\u586B" } } };
42443
+ }
42444
+ const fromMs = Date.parse(from);
42445
+ const toMs = Date.parse(to);
42446
+ if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || fromMs > toMs) {
42447
+ return { status: 400, body: { error: { code: "bad_request", message: "from / to \u65F6\u95F4\u8303\u56F4\u65E0\u6548" } } };
42448
+ }
42449
+ try {
42450
+ new Intl.DateTimeFormat("en", { timeZone }).format();
42451
+ } catch {
42452
+ return { status: 400, body: { error: { code: "bad_request", message: "timeZone \u65E0\u6548" } } };
42453
+ }
42454
+ const { kernel } = await resolveCtx(req.auth.companyId);
42455
+ const runs = await opts.trace.listUsageRuns({ from, to });
42456
+ return {
42457
+ status: 200,
42458
+ body: buildOrganizationUsageSummary({ model: kernel.model, runs, bucketKind: bucket, timeZone })
42459
+ };
42460
+ });
42101
42461
  router.get("/api/workorders", async (req) => {
42102
42462
  const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
42103
42463
  const resolve5 = await buildResolver(registry2);
@@ -42165,8 +42525,13 @@ function collabDomain(opts) {
42165
42525
  });
42166
42526
  router.get("/api/inbox", async (req) => {
42167
42527
  const { kernel, registry: registry2 } = await resolveCtx(req.auth.companyId);
42168
- const leadOf = await buildLeadResolver(registry2);
42169
- return { status: 200, body: { items: buildInbox(kernel.model, req.auth.actor, leadOf) } };
42528
+ const [leadOf, resolveActor] = await Promise.all([
42529
+ buildLeadResolver(registry2),
42530
+ // 决策 0026 (c):gap 升级也发卡住节点 owner 的人类上级
42531
+ buildResolver(registry2)
42532
+ // 决策 0042:gap reporter 名册 join
42533
+ ]);
42534
+ return { status: 200, body: { items: buildInbox(kernel.model, req.auth.actor, leadOf, resolveActor) } };
42170
42535
  });
42171
42536
  router.get("/api/audit", async (req) => {
42172
42537
  const { oplog } = await resolveCtx(req.auth.companyId);
@@ -42227,12 +42592,14 @@ var init_collab = __esm({
42227
42592
  init_inbox();
42228
42593
  init_audit();
42229
42594
  init_dashboard();
42595
+ init_organization_usage();
42230
42596
  init_node_state();
42231
42597
  init_workorders();
42232
42598
  init_workorder_detail();
42233
42599
  init_inbox();
42234
42600
  init_audit();
42235
42601
  init_dashboard();
42602
+ init_organization_usage();
42236
42603
  init_planner();
42237
42604
  }
42238
42605
  });
@@ -42753,6 +43120,17 @@ var init_service4 = __esm({
42753
43120
  const page = await this.store.listRuns(filter);
42754
43121
  return { ...page, items: page.items.map((run) => this.withDisplayCost(run)) };
42755
43122
  }
43123
+ /** 组织汇总专用瘦读:只取归属、时间和 usage,并统一补齐展示成本。 */
43124
+ async listUsageRuns(filter) {
43125
+ const rows = await this.store.listUsageRuns(filter);
43126
+ return rows.map((row) => ({
43127
+ ...row,
43128
+ usage: {
43129
+ ...row.usage,
43130
+ costUsdMicros: displayCostUsdMicros(row.effectiveModel, row.usage)
43131
+ }
43132
+ }));
43133
+ }
42756
43134
  /** 按维度查聚合用量(per-session/per-agent plan §4)。store 未实现聚合时返回空。
42757
43135
  * 读侧补全展示成本:adapter 未上报 cost 时按价格快照估算(与员工周统计同口径)。 */
42758
43136
  async queryUsageStats(filter) {
@@ -45586,7 +45964,7 @@ var init_worker = __esm({
45586
45964
  \u3010\u600E\u4E48\u5E72\u3011\u963B\u585E\u662F**\u75C7\u72B6**\u4E0D\u662F\u75C5\u3002\u7CFB\u7EDF\u5DF2\u5148\u673A\u68B0\u5206\u8BCA\u3001\u628A**\u80FD\u5B9A\u4F4D\u7684**\uFF08\u8D44\u6E90\u6392\u961F / \u8FD0\u884C\u65F6\u7194\u65AD / \u4EBA\u6CA1\u63A5 / \u7B49\u4E00\u4E2A\u5065\u5EB7\u5728\u4EA7\u7684\u4E0A\u6E38\uFF09\u8DEF\u7531\u7ED9\u4E86\u5BF9\u53E3\u5904\u7F6E\u8005\uFF1B\u8D70\u5230\u4F60\u8FD9\u513F\u7684\u662F\u9700\u8981\u4F60\u5224\u65AD\u7684\u90A3\u7C7B\u3002
45587
45965
  \u2460 \u5148\u8BFB\u4E0B\u65B9"\u75C5\u5386"\u6BB5 + \u5FC5\u8981\u65F6 \`oasis status/content <id>\` \u6CBF\u4F9D\u8D56\u94FE\u6478\u6E05\u8BED\u4E49\u6839\uFF1B
45588
45966
  \u2461 \u51FA**\u6700\u7701\u4E8B\u3001\u6700\u6CBB\u672C**\u7684\u4E00\u624B\uFF1A\u80FD\u8865\u4E0A\u4E0B\u6587\u5C31\u522B\u6539\u56FE\u3001\u80FD\u8FDE\u65E2\u6709\u5C31\u522B\u65B0\u5EFA\u3001\u80FD\u81EA\u5DF1\u89E3\u51B3\u5C31\u522B\u60CA\u52A8\u4EBA\uFF1B\u9500\u6BC1\u6027\u6539\u56FE\uFF08\u65AD\u8FB9/\u5E9F\u8282\u70B9/\u6539\u6D3E\uFF09apply \u65F6\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u8F6C\u6210\u5F85\u4EBA\u786E\u8BA4\u7684\u63D0\u6848\uFF0C\u4F60 stage \u597D\u8BB2\u6E05\u5F71\u54CD\u5373\u53EF\u3002
45589
- \u2462 **\u6536\u5C3E\u5FC5\u987B\u663E\u5F0F**\u2014\u2014\u628A\u8FD9\u6761\u7EBF\u63A8\u52A8\u4E86\u3001\u6216\u5224\u65AD\u8BE5\u7531 owner \u7EE7\u7EED \u2192 \`oasis resolve-gap --gap <id> --note "<\u4F60\u505A\u4E86\u4EC0\u4E48 / owner \u8BE5\u600E\u4E48\u63A5>"\`\uFF08**\u4E0D\u53D1\u8FD9\u53E5 gap \u4E0D\u89E3\u3001owner \u4E0D\u9192**\uFF09\uFF1B\u591F\u4E0D\u7740 / \u8981\u4EBA\u5B9A \u2192 \`oasis escalate <\u8282\u70B9> --reason "<\u6839\u56E0 + \u8981\u4EBA\u505A\u4EC0\u4E48>"\` \u4E0A\u62A5\u3002**\u7EDD\u4E0D\u7A7A\u624B\u9000\u51FA**\uFF1A\u4EC0\u4E48\u90FD\u4E0D\u505A\u5730\u6536\u5DE5 = \u628A\u8282\u70B9\u6C38\u4E45\u667E\u6B7B\uFF0C\u6700\u7CDF\u3002`;
45967
+ \u2462 **\u6536\u5C3E\u5FC5\u987B\u663E\u5F0F**\u2014\u2014\u628A\u8FD9\u6761\u7EBF\u63A8\u52A8\u4E86\u3001\u6216\u5224\u65AD\u8BE5\u7531 owner \u7EE7\u7EED \u2192 \`oasis resolve-gap --gap <id> --note "<\u4F60\u505A\u4E86\u4EC0\u4E48 / owner \u8BE5\u600E\u4E48\u63A5>"\`\uFF08**\u4E0D\u53D1\u8FD9\u53E5 gap \u4E0D\u89E3\u3001owner \u4E0D\u9192**\uFF09\uFF1B\u591F\u4E0D\u7740 / \u8981\u4EBA\u5B9A \u2192 \`oasis escalate <\u8282\u70B9> --reason "<\u6839\u56E0 + \u8981\u4EBA\u505A\u4EC0\u4E48>" --gap <gapId> [--part <part>]\` \u4E0A\u62A5\uFF08**\u5FC5\u987B\u5E26 --gap**\uFF0C\u6709 part \u65F6\u4E00\u5E76\u5E26\u4E0A\uFF09\u3002**\u7EDD\u4E0D\u7A7A\u624B\u9000\u51FA**\uFF1A\u4EC0\u4E48\u90FD\u4E0D\u505A\u5730\u6536\u5DE5 = \u628A\u8282\u70B9\u6C38\u4E45\u667E\u6B7B\uFF0C\u6700\u7CDF\u3002`;
45590
45968
  CONVERSATIONAL_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\uFF0C\u6B63\u5728\u548C\u5DE5\u5355\u53D1\u8D77\u4EBA\u5BF9\u8BDD\u3001\u4E00\u8D77\u628A\u5B83\u5F80\u524D\u63A8\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A\u5E2E\u5404\u8282\u70B9 owner \u626B\u6E05\u969C\u788D\u3001\u7B54\u7591\u3001\u8865\u4E0A\u4E0B\u6587\u3001**\u81EA\u5DF1\u8DD1\u5206\u6790 / \u62C9\u6570\u636E / \u5199\u8BF4\u660E**\u3001\u534F\u8C03\u8DE8\u8282\u70B9\u3001\u6309\u9700\u6539\u56FE\uFF08\u8FDE\u8FB9 / \u65B0\u5EFA / \u53BB\u91CD / \u6539\u6D3E / \u6539\u6807\u9898\u4E0E brief\uFF09\u3001\u4E3A\u53D1\u8D77\u4EBA\u89E3\u7B54\u6216\u4EE3\u529E\u5DE5\u5355\u8303\u56F4\u5185\u7684\u4E8B\u52A1\u3002**\u4F60\u6709\u4F1A\u8BDD\u5185\u7684\u5168\u90E8\u80FD\u529B\uFF08Bash / \u6587\u4EF6 / \u4EFB\u610F CLI\uFF09\uFF0C\u653E\u5F00\u7528\u3002\u552F\u4E00\u786C\u8FB9\u754C\uFF1A\u4E0D\u66FF owner \u5199\u8282\u70B9\u7684\u4EA7\u7269\u6B63\u6587**\uFF08\u8865\u4E0A\u4E0B\u6587\u3001\u7ED9\u53C2\u8003\u90FD\u884C\uFF0C\u522B\u4EE3\u7B14\uFF09\u3002
45591
45969
  - **\u5148\u8BCA\u65AD\u3001\u7ED9\u65B9\u6848\u3001\u95EE ta**\uFF1A\u770B\u6E05\u5361\u5728\u54EA\uFF0C\u7ED9 1\u20132 \u4E2A\u53EF\u884C\u505A\u6CD5\u3001\u8BB2\u6E05\u5404\u81EA\u5F71\u54CD\uFF0C\u95EE ta \u91C7\u7528\u54EA\u4E2A\u2014\u2014\u522B\u81EA\u4F5C\u4E3B\u5F20\u95F7\u5934\u6539\u3002\u660E\u663E\u4E14\u4F4E\u98CE\u9669\u7684\u53EF\u4EE5\u5148\u505A\u4E00\u7248\u518D\u8BF4\u3002
45592
45970
  - **\u6539\u52A8\u5206\u4E24\u7C7B**\uFF08\u6388\u6743\u5355\u4F4D\u662F diff\uFF09\uFF1A\u52A0\u8FB9 / \u65B0\u589E\u8282\u70B9 / \u6539\u6807\u9898 brief \u8FD9\u7C7B**\u53EF\u9006\u7684**\uFF08B\uFF09\uFF0C\`stage\`\u2192\`apply\` \u76F4\u63A5\u843D\u3001\u505A\u5B8C\u628A\u52A8\u4E86\u4EC0\u4E48\u544A\u8BC9 ta\uFF1B\u65AD\u8FB9 / \u5E9F\u8282\u70B9 / \u6539\u6D3E / \u64A4\u90E8\u4EF6\u8FD9\u7C7B**\u9500\u6BC1\u6027\u7684**\uFF08C\uFF09\uFF0C\u4F60 \`apply\` \u65F6\u7CFB\u7EDF\u4F1A**\u81EA\u52A8\u8F6C\u6210\u4E00\u4EFD\u5F85\u786E\u8BA4\u63D0\u6848**\u653E\u5230 ta \u9762\u524D\u7684\u300C\u5F85\u786E\u8BA4\u53D8\u66F4\u300D\u5361\u2014\u2014\u4F60 **stage \u597D\u3001\u628A\u5F71\u54CD\u8BB2\u6E05\u695A\u5C31\u884C\uFF0C\u522B\u81EA\u5DF1\u786C\u843D**\uFF0C\u7531 ta \u5728\u5361\u4E0A\u786E\u8BA4\u6216\u9A73\u56DE\u3002
@@ -45613,7 +45991,7 @@ var init_worker = __esm({
45613
45991
 
45614
45992
  \u3010\u6536\u5C3E gap / \u4E0A\u62A5\u5361\u70B9\u3011\uFF08\u4E0D\u8D70\u6682\u5B58\u7F13\u51B2\uFF0C\u76F4\u63A5\u751F\u6548\uFF09
45615
45993
  - \`oasis resolve-gap --gap <gapId> --note "<\u4F60\u505A\u4E86\u4EC0\u4E48 / owner \u8BE5\u600E\u4E48\u7EE7\u7EED>"\` \u2014\u2014 **\u4FEE\u5B8C\u4E00\u6761 gap \u5FC5\u987B\u663E\u5F0F\u6536\u5C3E**\uFF1A\u8FDE\u8FB9 / \u5EFA\u8282\u70B9**\u4E0D\u4F1A\u81EA\u52A8\u89E3 gap**\uFF0C\u4E0D\u53D1\u8FD9\u53E5\u62A5 gap \u7684 owner \u9192\u4E0D\u8FC7\u6765\u3002\`--gap\` = \u90A3\u6761 gap \u7684 id\uFF08reconcile \u4EFB\u52A1\u91CC\u4F1A\u7ED9\u4F60\uFF09\uFF1B\`--note\` \u539F\u6837\u7ED9 owner \u770B\uFF0C\u5199\u4EBA\u8BDD\u3002
45616
- - \`oasis escalate <id> --reason "<\u7F3A\u4EC0\u4E48\u80FD\u529B / \u8981\u4EBA\u66FF\u4F60\u5B9A\u6216\u505A\u4EC0\u4E48>"\` \u2014\u2014 **\u641E\u4E0D\u5B9A / \u62FF\u4E0D\u51C6 / \u6839\u56E0\u4E0D\u5728\u56FE\u4E0A\u5C31\u4E0A\u62A5\u53D1\u8D77\u4EBA**\uFF08\u522B\u6C89\u9ED8\u9000\u51FA\uFF09\u3002\`<id>\` = \u5361\u4F4F\u7684\u8282\u70B9\uFF1B\`--reason\` \u8BF4\u6E05\u4F60\u5224\u65AD\u7684\u6839\u56E0\u548C\u9700\u8981\u4EBA\u505A\u4EC0\u4E48\u3002\u7CFB\u7EDF\u4F1A\u767B\u8BB0\u672C\u6B21\u4F1A\u8BDD\uFF0C\u4EBA\u56DE\u8BDD\u65F6\u4F60\u88AB**\u63A5\u7740\u5524\u8D77\u3001\u5E26\u4E0A\u4E0B\u6587\u7EE7\u7EED\u89E3**\u3002
45994
+ - \`oasis escalate <id> --reason "<\u7F3A\u4EC0\u4E48\u80FD\u529B / \u8981\u4EBA\u66FF\u4F60\u5B9A\u6216\u505A\u4EC0\u4E48>" --gap <gapId> [--part <part>]\` \u2014\u2014 **\u641E\u4E0D\u5B9A / \u62FF\u4E0D\u51C6 / \u6839\u56E0\u4E0D\u5728\u56FE\u4E0A\u5C31\u4E0A\u62A5\u53D1\u8D77\u4EBA**\uFF08\u522B\u6C89\u9ED8\u9000\u51FA\uFF09\u3002\`<id>\` = \u5361\u4F4F\u7684\u8282\u70B9\uFF1B\`--reason\` \u8BF4\u6E05\u4F60\u5224\u65AD\u7684\u6839\u56E0\u548C\u9700\u8981\u4EBA\u505A\u4EC0\u4E48\u3002\u7CFB\u7EDF\u4F1A\u767B\u8BB0\u672C\u6B21\u4F1A\u8BDD\uFF0C\u4EBA\u56DE\u8BDD\u65F6\u4F60\u88AB**\u63A5\u7740\u5524\u8D77\u3001\u5E26\u4E0A\u4E0B\u6587\u7EE7\u7EED\u89E3**\u3002
45617
45995
 
45618
45996
  \u3010\u8BFB\u56FE\u3011\uFF08\u90FD\u8BFB**\u771F\u56FE**\uFF0C\u4E0E\u522B\u7684 agent \u540C\u8BED\u4E49\uFF0C\u4E0D\u53D7\u4F60\u6682\u5B58\u5F71\u54CD\uFF09
45619
45997
  - \`oasis graph\` \u2014\u2014 \u5168\u56FE\u4F9D\u8D56\u8FB9\uFF08\u8C01\u8FDE\u8C01\uFF09\uFF1B\`oasis ls\` \u2014\u2014 \u5168\u4EA7\u7269\u5217\u8868\uFF08id / type / owner / head / \u961F\u5217 / \u72B6\u6001\u6807\uFF09\u3002\u5148\u7528\u5B83\u4EEC\u5EFA\u7ACB\u5168\u5C40\u56FE\u666F\u3002
@@ -45779,7 +46157,7 @@ ${SHARED_GRAPH_BODY}`;
45779
46157
  const out = [];
45780
46158
  for (const artifact of this.deps.kernel.model.artifacts.values()) {
45781
46159
  for (const g2 of unresolvedGapsOf(this.deps.kernel.model, artifact.id)) {
45782
- out.push({ artifactId: artifact.id, gapId: g2.gapId, description: g2.description });
46160
+ out.push({ artifactId: artifact.id, gapId: g2.gapId, description: g2.description, part: g2.part });
45783
46161
  }
45784
46162
  }
45785
46163
  return out;
@@ -45814,6 +46192,10 @@ ${SHARED_GRAPH_BODY}`;
45814
46192
  }
45815
46193
  return lines.length > 0 ? lines.join("\n") : "\uFF08\u56FE\u91CC\u6682\u65E0\u5176\u5B83\u4EA7\u7269\uFF09";
45816
46194
  }
46195
+ escalateCmd(gap, reasonPlaceholder) {
46196
+ const partFlag = gap.part ? ` --part ${gap.part}` : "";
46197
+ return `oasis escalate ${gap.artifactId} --reason "${reasonPlaceholder}" --gap ${gap.gapId}${partFlag}`;
46198
+ }
45817
46199
  async reconcile(gap) {
45818
46200
  const gapper = this.deps.kernel.model.artifacts.get(gap.artifactId);
45819
46201
  const brief = gapper?.title ?? gapper?.description ?? "";
@@ -45826,7 +46208,7 @@ ${SHARED_GRAPH_BODY}`;
45826
46208
  `## \u8981\u4F60\u505A\u4EC0\u4E48\uFF08\u5148\u5206\u8BCA\uFF09`,
45827
46209
  `\u8FD9\u4E2A\u9700\u6C42**\u53EF\u80FD\u662F\u4EFB\u4F55\u963B\u585E**\u2014\u2014\u7F3A\u4E0A\u6E38\u8F93\u5165 / \u7F3A\u4FE1\u606F / \u8981\u4EBA\u51B3\u7B56 / \u7F3A\u80FD\u529B\uFF08\u5982\u67D0\u4E2A\u5DE5\u5177\u88C5\u4E0D\u4E0A\uFF09\u3002\u4F60\u5206\u8BCA\uFF1A`,
45828
46210
  `- **\u56FE\u4E0A\u80FD\u4FEE\u7684**\uFF08\u7F3A\u4E0A\u6E38/\u8BE5\u8FDE\u6CA1\u8FDE\uFF09\u2192 \u5224\u65AD\u8FDE\u65E2\u6709\uFF08\u53BB\u91CD\uFF0C\u4F18\u5148\uFF09\u8FD8\u662F\u65B0\u5EFA\u2014\u2014\u6BD4\u5BF9\u4E0B\u9762\u73B0\u6709\u4EA7\u7269\u7684 brief\uFF1B\u62FF\u4E0D\u51C6\u5C31 \`oasis content <\u5019\u9009id>\` \u8BFB\u6B63\u6587\u3002`,
45829
- `- **\u56FE\u4E0A\u4FEE\u4E0D\u4E86\u7684**\uFF08\u8981\u4EBA\u62CD\u677F / \u7F3A\u73AF\u5883\u80FD\u529B\uFF09\u2192 \`oasis escalate ${gap.artifactId} --reason "<\u8981\u4EBA\u5B9A/\u505A\u4EC0\u4E48>"\`\uFF0C\u7CFB\u7EDF\u4F1A\u5347\u7EA7\u7ED9\u53D1\u8D77\u4EBA\u3001\u4F60\u4E4B\u540E\u53EF\u80FD\u5728\u5BF9\u8BDD\u91CC\u88AB ta \u95EE\u5230\uFF0C\u7136\u540E\u6536\u5DE5\u3002`,
46211
+ `- **\u56FE\u4E0A\u4FEE\u4E0D\u4E86\u7684**\uFF08\u8981\u4EBA\u62CD\u677F / \u7F3A\u73AF\u5883\u80FD\u529B\uFF09\u2192 \`${this.escalateCmd(gap, "<\u8981\u4EBA\u5B9A/\u505A\u4EC0\u4E48>")}\`\uFF0C\u7CFB\u7EDF\u4F1A\u5347\u7EA7\u7ED9\u53D1\u8D77\u4EBA\u3001\u4F60\u4E4B\u540E\u53EF\u80FD\u5728\u5BF9\u8BDD\u91CC\u88AB ta \u95EE\u5230\uFF0C\u7136\u540E\u6536\u5DE5\u3002`,
45830
46212
  ``,
45831
46213
  `## \u73B0\u6709\u4EA7\u7269\uFF08id / type / \u662F\u5426\u5DF2\u4EA4\u4ED8 / brief / \u4F9D\u8D56\uFF09`,
45832
46214
  this.structDigest(gap.artifactId),
@@ -45840,11 +46222,11 @@ ${SHARED_GRAPH_BODY}`;
45840
46222
  `- \u786E\u65E0\u5339\u914D\u3001\u65B0\u9700\u6C42 \u2192 \`oasis spawn --type <\u7C7B\u578B> --title "<\u77ED\u540D>" --brief "${gap.description}"\`\uFF08\u4E0D\u7528\u7ED9 id\uFF09+ \`oasis link ${gap.artifactId} --to <\u7C7B\u578B>:<\u77ED\u540D>\`\uFF08\u6309 type:title \u5F15\u521A\u5EFA\u7684\uFF09\u2192 apply\u3002`,
45841
46223
  `- **\u4FEE\u5B8C\u624D\u6536\u5C3E**\uFF1A\`oasis resolve-gap --gap ${gap.gapId} --note "<\u4F60\u505A\u4E86\u4EC0\u4E48 / owner \u8BE5\u600E\u4E48\u7EE7\u7EED>"\`\u2014\u2014**\u8FDE\u8FB9\u4E0D\u4F1A\u81EA\u52A8\u89E3 gap**\uFF0C\u4E0D\u53D1\u8FD9\u53E5 owner \u9192\u4E0D\u8FC7\u6765\uFF1Bnote \u4F1A\u539F\u6837\u7ED9 owner \u770B\uFF0C\u5199\u4EBA\u8BDD\u3002`,
45842
46224
  `**B. \u8981\u4EBA**\uFF08\u51B3\u7B56/\u80FD\u529B\u95EE\u9898\uFF0C\u56FE\u4E0A\u4FEE\u4E0D\u4E86\uFF09\uFF1A`,
45843
- `- \`oasis escalate ${gap.artifactId} --reason "<\u8981\u4EBA\u5B9A/\u505A\u4EC0\u4E48>"\` \u540E**\u76F4\u63A5\u6536\u5DE5**\u3002`,
46225
+ `- \`${this.escalateCmd(gap, "<\u8981\u4EBA\u5B9A/\u505A\u4EC0\u4E48>")}\` \u540E**\u76F4\u63A5\u6536\u5DE5**\u3002`,
45844
46226
  `- \u26A0\uFE0F **\u7EDD\u4E0D\u8981\u5BF9\u8FD9\u6761 gap \u8C03 resolve-gap**\u2014\u2014resolve-gap \u7684\u542B\u4E49\u662F"**\u9700\u6C42\u5DF2\u6EE1\u8DB3\u3001\u8BA9 owner \u590D\u5DE5**"\uFF0C\u4E0D\u662F"\u6211\u5206\u8BCA\u5B8C\u4E86"\u7684\u767B\u8BB0\u3002\u73B0\u5728\u9700\u6C42\u8FD8\u6CA1\u6EE1\u8DB3\uFF1Agap \u4FDD\u6301 open \u624D\u662F"\u8282\u70B9\u7EE7\u7EED\u7B49\u5F85"\u7684\u6B63\u786E\u72B6\u6001\uFF1B\u63D0\u524D resolve \u4F1A\u8BA9 owner \u5728\u5565\u90FD\u6CA1\u53D8\u7684\u60C5\u51B5\u4E0B\u88AB\u53EB\u9192\u767D\u8DD1\u3001\u8FD8\u4F1A\u628A\u4F60\u7684\u4E0A\u62A5\u4ECE\u4EBA\u7684\u6536\u4EF6\u7BB1\u91CC\u62B9\u6389\u3002\u4EBA\u7B54\u590D\u540E\uFF08\u591A\u534A\u5728\u5BF9\u8BDD\u91CC\u627E\u4F60\uFF09\u4F60\u518D\u4FEE\u56FE\u3001\u90A3\u65F6\u624D resolve-gap\u3002`,
45845
46227
  `**\u5176\u5B83**\uFF1A`,
45846
46228
  `- \u987A\u624B\u53D1\u73B0\u4E24\u4E2A\u4EA7\u7269\u91CD\u590D \u2192 **\u5148\u628A\u89E3 gap \u90A3\u7B14\uFF08\u542B resolve-gap\uFF09\u5355\u72EC\u6536\u6389**\uFF0C\u518D\u53E6\u8D77\u4E00\u7B14\uFF1A\`oasis edit\` \u6269\u4FDD\u7559\u4EF6 brief + \`oasis link\` \u6539\u8FDE\u6D88\u8D39\u8005 + \`oasis cancel\` \u5E9F\u5F03\u4EF6\uFF0C\u4E00\u8D77\u6682\u5B58\u518D apply\u3002`,
45847
- `- **\u62FF\u4E0D\u51C6\u4FEE\u6CD5 \u2192 \u4E00\u5F8B\u627E\u4EBA\uFF0C\u7EDD\u4E0D\u6C89\u9ED8\u9000\u51FA**\uFF1A\`oasis escalate ${gap.artifactId} --reason "<\u4F60\u5361\u5728\u54EA\u3001\u9700\u8981\u4EBA\u66FF\u4F60\u5B9A/\u505A\u4EC0\u4E48>"\` \u628A\u7591\u8651\u4E0A\u62A5\u7ED9\u53D1\u8D77\u4EBA\u2014\u2014\u7CFB\u7EDF\u4F1A\u767B\u8BB0\u672C\u6B21\u4F1A\u8BDD\uFF0C\u4EBA\u56DE\u8BDD\u65F6\u4F60\u88AB\u63A5\u7740\u5524\u8D77\u3001\u5E26\u7740\u4E0A\u4E0B\u6587\u7EE7\u7EED\u89E3\u3002**\u6CA1\u6709"\u8FD8\u4E0D\u5230\u8981\u4EBA\u7684\u7A0B\u5EA6"\u8FD9\u4E00\u6863\uFF1A\u6539\u4E0D\u52A8\u53C8\u62FF\u4E0D\u51C6\uFF0C\u5C31\u662F\u8981\u4EBA\u3002\u4EC0\u4E48\u90FD\u4E0D\u505A\u5730\u6536\u5DE5 = \u628A\u8282\u70B9\u6C38\u4E45\u667E\u6B7B\uFF0C\u6700\u7CDF\u3002**`
46229
+ `- **\u62FF\u4E0D\u51C6\u4FEE\u6CD5 \u2192 \u4E00\u5F8B\u627E\u4EBA\uFF0C\u7EDD\u4E0D\u6C89\u9ED8\u9000\u51FA**\uFF1A\`${this.escalateCmd(gap, "<\u4F60\u5361\u5728\u54EA\u3001\u9700\u8981\u4EBA\u66FF\u4F60\u5B9A/\u505A\u4EC0\u4E48>")}\` \u628A\u7591\u8651\u4E0A\u62A5\u7ED9\u53D1\u8D77\u4EBA\u2014\u2014\u7CFB\u7EDF\u4F1A\u767B\u8BB0\u672C\u6B21\u4F1A\u8BDD\uFF0C\u4EBA\u56DE\u8BDD\u65F6\u4F60\u88AB\u63A5\u7740\u5524\u8D77\u3001\u5E26\u7740\u4E0A\u4E0B\u6587\u7EE7\u7EED\u89E3\u3002**\u6CA1\u6709"\u8FD8\u4E0D\u5230\u8981\u4EBA\u7684\u7A0B\u5EA6"\u8FD9\u4E00\u6863\uFF1A\u6539\u4E0D\u52A8\u53C8\u62FF\u4E0D\u51C6\uFF0C\u5C31\u662F\u8981\u4EBA\u3002\u4EC0\u4E48\u90FD\u4E0D\u505A\u5730\u6536\u5DE5 = \u628A\u8282\u70B9\u6C38\u4E45\u667E\u6B7B\uFF0C\u6700\u7CDF\u3002**`
45848
46230
  ].join("\n");
45849
46231
  const subjectKey = `gap:${gap.gapId}`;
45850
46232
  const emptyCount = this.emptyHandedGap.get(subjectKey) ?? 0;
@@ -45854,7 +46236,7 @@ ${SHARED_GRAPH_BODY}`;
45854
46236
  `## \u26A0\uFE0F \u63A5\u4E0A\u4E00\u8F6E\u2014\u2014\u4F60\u4E0A\u6B21\u88AB\u5524\u8D77\u5374**\u7A7A\u624B\u9000\u51FA\u4E86**\uFF08\u65E2\u6CA1 resolve-gap \u4E5F\u6CA1 escalate\uFF09`,
45855
46237
  `\u8FD9\u6761\u7EBF\u8FD8\u5361\u7740\u3002\u8FD9\u4E00\u8F6E\u4F60**\u5FC5\u987B**\u4E8C\u9009\u4E00\u6536\u53E3\uFF0C\u4E0D\u8BB8\u518D\u7A7A\u624B\u9000\u51FA\uFF1A`,
45856
46238
  `- \u5DF2\u628A\u8FD9\u6761\u7EBF\u63A8\u52A8 / \u5224\u65AD\u8BE5 owner \u63A5\u624B \u2192 \`oasis resolve-gap --gap ${gap.gapId} --note "<\u4F60\u505A\u4E86\u4EC0\u4E48 / owner \u600E\u4E48\u63A5>"\``,
45857
- `- \u591F\u4E0D\u7740 / \u8981\u4EBA\u62CD\u677F \u2192 \`oasis escalate ${gap.artifactId} --reason "<\u6839\u56E0 + \u8981\u4EBA\u505A\u4EC0\u4E48>"\``,
46239
+ `- \u591F\u4E0D\u7740 / \u8981\u4EBA\u62CD\u677F \u2192 \`${this.escalateCmd(gap, "<\u6839\u56E0 + \u8981\u4EBA\u505A\u4EC0\u4E48>")}\``,
45858
46240
  `\u4E0D\u53D1\u8FD9\u4E24\u53E5\u4E4B\u4E00\uFF0Cgap \u6C38\u8FDC\u89E3\u4E0D\u5F00\u3001owner \u6C38\u8FDC\u9192\u4E0D\u8FC7\u6765\u3002**\u8FD9\u662F\u6700\u540E\u4E00\u6B21\u673A\u4F1A\u2014\u2014\u518D\u7A7A\u624B\uFF0C\u7CFB\u7EDF\u4F1A\u66FF\u4F60\u4E0A\u62A5\u7ED9\u4EBA\u3002**`
45859
46241
  ].join("\n");
45860
46242
  const resumeId = emptyCount > 0 ? this.emptyHandedSession.get(subjectKey) : void 0;
@@ -45877,7 +46259,9 @@ ${SHARED_GRAPH_BODY}`;
45877
46259
  await this.deps.kernel.escalate({
45878
46260
  artifactId: gap.artifactId,
45879
46261
  actor: run.managerActorId,
45880
- reason: `\u7BA1\u7406\u8005\u88AB\u7CFB\u7EDF\u5524\u8D77 ${n} \u8F6E\u4ECD\u672A\u6536\u53E3\uFF08\u65E2\u672A resolve-gap \u4E5F\u672A escalate\uFF09\u2014\u2014\u7CFB\u7EDF\u4EE3\u4E3A\u4E0A\u62A5\uFF0C\u8BF7\u4F60\u5904\u7406\u8FD9\u6761\u5361\u70B9\uFF1A${gap.description}`
46262
+ reason: `\u7BA1\u7406\u8005\u88AB\u7CFB\u7EDF\u5524\u8D77 ${n} \u8F6E\u4ECD\u672A\u6536\u53E3\uFF08\u65E2\u672A resolve-gap \u4E5F\u672A escalate\uFF09\u2014\u2014\u7CFB\u7EDF\u4EE3\u4E3A\u4E0A\u62A5\uFF0C\u8BF7\u4F60\u5904\u7406\u8FD9\u6761\u5361\u70B9\uFF1A${gap.description}`,
46263
+ gapId: gap.gapId,
46264
+ ...gap.part ? { part: gap.part } : {}
45881
46265
  }).catch((e) => this.deps.log?.(`[coordinator] ${gap.artifactId} \u81EA\u52A8 escalate \u5931\u8D25\uFF1A${String(e)}`));
45882
46266
  this.deps.log?.(`[coordinator] ${gap.artifactId} \u7BA1\u7406\u8005\u8FDE\u7EED ${n} \u8F6E\u7A7A\u624B \u2192 \u7CFB\u7EDF\u81EA\u52A8 escalate \u4EA4\u53D1\u8D77\u4EBA`);
45883
46267
  this.emptyHandedGap.delete(subjectKey);
@@ -54496,6 +54880,22 @@ var PostgresTraceStore = class _PostgresTraceStore {
54496
54880
  const nextCursor = hasMore ? page[page.length - 1]?.id ?? null : null;
54497
54881
  return { items: page, nextCursor };
54498
54882
  }
54883
+ async listUsageRuns(filter) {
54884
+ const r = await this.pool.query(
54885
+ `SELECT actor_id, artifact_id, started_at, effective_model, usage
54886
+ FROM ${this.s}.agent_runs
54887
+ WHERE usage IS NOT NULL AND started_at >= $1 AND started_at <= $2
54888
+ ORDER BY started_at`,
54889
+ [filter.from, filter.to]
54890
+ );
54891
+ return r.rows.map((row) => ({
54892
+ actorId: row.actor_id,
54893
+ artifactId: row.artifact_id ?? null,
54894
+ startedAt: row.started_at,
54895
+ effectiveModel: row.effective_model ?? null,
54896
+ usage: row.usage
54897
+ }));
54898
+ }
54499
54899
  /* ---------- 事件流(单写者 seq + 幂等吸收)---------- */
54500
54900
  async appendEvent(runId, event) {
54501
54901
  return (await this.appendEvents(runId, [event]))[0];
@@ -56414,6 +56814,7 @@ async function startServe(opts) {
56414
56814
  collabDomain({
56415
56815
  kernel,
56416
56816
  oplog,
56817
+ trace: trace.service,
56417
56818
  registry: registryStore,
56418
56819
  artifacts: artifactStateStore,
56419
56820
  dispatchJournal: () => journalRing.slice(-200),
@@ -57088,8 +57489,17 @@ ${detail}` : genericLine));
57088
57489
  if (f2.action !== "produce") continue;
57089
57490
  const cur = kernel.model.artifacts.get(f2.artifactId);
57090
57491
  if (!cur) continue;
57091
- if (now - Date.parse(f2.fusedAt) > agentMs) {
57092
- await escalate(f2.artifactId, cur.owner, `agent \u8FDE\u8D25\u7194\u65AD\u540E ${Math.round(agentMs / 6e4)} \u5206\u949F\u65E0\u4EBA\u5904\u7406`);
57492
+ if (now - Date.parse(f2.fusedAt) <= agentMs) continue;
57493
+ if (unresolvedEscalationsOf(kernel.model, f2.artifactId).length > 0) continue;
57494
+ 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)}`);
57093
57503
  }
57094
57504
  }
57095
57505
  })().catch((err) => console.error(`[sla] \u626B\u63CF\u5931\u8D25: ${String(err)}`));
@@ -58861,9 +59271,10 @@ ${res.warning}`);
58861
59271
  }
58862
59272
  case "escalate": {
58863
59273
  const { message } = await api.cmd("escalate", {
58864
- artifactId: needPos(positional, 0, "oasis escalate <artifactId> --reason t [--part p]"),
59274
+ artifactId: needPos(positional, 0, "oasis escalate <artifactId> --reason t [--gap id] [--part p]"),
58865
59275
  reason: need(flags, "reason"),
58866
- ...flags.get("part") !== void 0 ? { part: flags.get("part") } : {}
59276
+ ...flags.get("part") !== void 0 ? { part: flags.get("part") } : {},
59277
+ ...flags.get("gap") !== void 0 ? { gapId: flags.get("gap") } : {}
58867
59278
  });
58868
59279
  println(message);
58869
59280
  break;
@@ -58876,6 +59287,23 @@ ${res.warning}`);
58876
59287
  println(message);
58877
59288
  break;
58878
59289
  }
59290
+ case "hold": {
59291
+ const { message } = await api.cmd("hold", {
59292
+ artifactId: needPos(positional, 0, "oasis hold <artifactId> [--reason t]"),
59293
+ ...flags.get("reason") !== void 0 ? { reason: flags.get("reason") } : {},
59294
+ ...flags.get("source") !== void 0 ? { source: flags.get("source") } : {}
59295
+ });
59296
+ println(message);
59297
+ break;
59298
+ }
59299
+ case "release": {
59300
+ const { message } = await api.cmd("release", {
59301
+ artifactId: needPos(positional, 0, "oasis release <artifactId> [--reason t]"),
59302
+ ...flags.get("reason") !== void 0 ? { reason: flags.get("reason") } : {}
59303
+ });
59304
+ println(message);
59305
+ break;
59306
+ }
58879
59307
  case "add-revision": {
58880
59308
  const { message } = await api.cmd("addRevision", {
58881
59309
  artifactId: needPos(positional, 0, "oasis add-revision <artifactId> --desc <\u8981\u5B83\u5B8C\u6210\u4EC0\u4E48> [--role <\u8C01\u6765\u505A>]"),
@@ -59328,7 +59756,7 @@ ${res.warning}`);
59328
59756
  }
59329
59757
 
59330
59758
  // src/index.ts
59331
- var PKG_VERSION = true ? "0.1.53" : "dev";
59759
+ var PKG_VERSION = true ? "0.1.54" : "dev";
59332
59760
  var OASIS_DIR = path18.join(os9.homedir(), ".oasis");
59333
59761
  var CONFIG_FILE = path18.join(OASIS_DIR, "node-config.json");
59334
59762
  var PID_FILE = path18.join(OASIS_DIR, "node.pid");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"