oasis_test 0.1.52 → 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 +1389 -890
  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
  }
@@ -339,7 +355,7 @@ function fold(model, op) {
339
355
  nonMonotonicOps: p2.nonMonotonicOps,
340
356
  proposedBy: p2.proposedBy,
341
357
  origin: p2.origin ?? null,
342
- workspace: p2.plan.workspace ?? null,
358
+ workspace: p2.plan.workspace ?? model.artifacts.get(op.artifactId)?.workspace ?? null,
343
359
  advisory: p2.advisory ?? [],
344
360
  downstreamClosure: p2.downstreamClosure ?? [],
345
361
  at: op.timestamp,
@@ -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",
@@ -2004,9 +2058,9 @@ var init_kernel = __esm({
2004
2058
  throw new KernelError(`revision not in queue: ${args.revisionId}\uFF08state=${target?.state ?? "missing"}\uFF09`);
2005
2059
  }
2006
2060
  if (isStale(this.model, head)) {
2007
- const path20 = this.pathForStaleHead(artifact, queue);
2061
+ const path19 = this.pathForStaleHead(artifact, queue);
2008
2062
  throw new KernelError(
2009
- `stale base: ${head.id} based on ${head.base ?? "\u2205"}, head is ${artifact.currentRev ?? "\u2205"} \u2014 ` + (path20 === "rebase" ? `\u9700\u8981 rebase\uFF1A\u4F5C\u8005\u5728\u65B0 head \u4E0A\u91CD\u5199\u540E propose --rebase-of ${head.id}\uFF08\xA75.5\uFF09` : `\u591A\u6761\u7ADE\u4E89\u4E14\u68C0\u67E5\u8D35 \u2192 group-commit\uFF1Aowner \u4E00\u6B21\u96C6\u6210\u6574\u6279\uFF08\xA75.5\uFF09`)
2063
+ `stale base: ${head.id} based on ${head.base ?? "\u2205"}, head is ${artifact.currentRev ?? "\u2205"} \u2014 ` + (path19 === "rebase" ? `\u9700\u8981 rebase\uFF1A\u4F5C\u8005\u5728\u65B0 head \u4E0A\u91CD\u5199\u540E propose --rebase-of ${head.id}\uFF08\xA75.5\uFF09` : `\u591A\u6761\u7ADE\u4E89\u4E14\u68C0\u67E5\u8D35 \u2192 group-commit\uFF1Aowner \u4E00\u6B21\u96C6\u6210\u6574\u6279\uFF08\xA75.5\uFF09`)
2010
2064
  );
2011
2065
  }
2012
2066
  await this.landHead(args.artifactId, args.actor, head, args.hand);
@@ -2023,13 +2077,13 @@ var init_kernel = __esm({
2023
2077
  const head = queue[0];
2024
2078
  if (!head) return { landed };
2025
2079
  if (isStale(this.model, head)) {
2026
- const path20 = this.pathForStaleHead(artifact, queue);
2080
+ const path19 = this.pathForStaleHead(artifact, queue);
2027
2081
  return {
2028
2082
  landed,
2029
2083
  blockedHead: {
2030
2084
  revision: head,
2031
- path: path20,
2032
- hint: path20 === "rebase" ? `\u4F5C\u8005\u91CD\u5199\u540E propose --rebase-of ${head.id}` : `owner \u96C6\u6210\u6574\u6279\uFF08integrate\uFF09\uFF0CR*.parents = [head, \u2026batch]`
2085
+ path: path19,
2086
+ hint: path19 === "rebase" ? `\u4F5C\u8005\u91CD\u5199\u540E propose --rebase-of ${head.id}` : `owner \u96C6\u6210\u6574\u6279\uFF08integrate\uFF09\uFF0CR*.parents = [head, \u2026batch]`
2033
2087
  }
2034
2088
  };
2035
2089
  }
@@ -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);
@@ -4808,6 +4909,172 @@ var init_src2 = __esm({
4808
4909
  }
4809
4910
  });
4810
4911
 
4912
+ // ../server/src/git/ci-provider.ts
4913
+ function normalizeRemote(remote) {
4914
+ let s2 = remote.trim();
4915
+ s2 = s2.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
4916
+ s2 = s2.replace(/^git@/, "").replace(/^([^/:]+):/, "$1/");
4917
+ s2 = s2.replace(/\/+$/, "").replace(/\.git$/i, "");
4918
+ const slash = s2.indexOf("/");
4919
+ if (slash > 0) s2 = s2.slice(0, slash).toLowerCase() + s2.slice(slash);
4920
+ return s2;
4921
+ }
4922
+ function isGitHubRemote(remote) {
4923
+ return !!remote && /(?:^|\/\/|@)github\.com[/:]/i.test(remote);
4924
+ }
4925
+ function ciBranchFor(artifactId, sha2) {
4926
+ const s2 = slug3(artifactId.replace(/^artifact:/, "")).slice(0, 48);
4927
+ return `oasis-ci/${s2}/${sha2.slice(0, 8)}`;
4928
+ }
4929
+ function mirrorDirFor(mirrorRoot, repo) {
4930
+ if (repo.repoDir) return repo.repoDir;
4931
+ const key = repo.remote ? `${slug3(repo.id)}-${crypto.createHash("sha1").update(normalizeRemote(repo.remote)).digest("hex").slice(0, 8)}` : slug3(repo.id);
4932
+ return `${mirrorRoot}/${key}`;
4933
+ }
4934
+ var import_node_child_process, crypto, fs, path, import_node_util, execFile, slug3, GhCiProvider;
4935
+ var init_ci_provider = __esm({
4936
+ "../server/src/git/ci-provider.ts"() {
4937
+ "use strict";
4938
+ import_node_child_process = require("node:child_process");
4939
+ crypto = __toESM(require("node:crypto"), 1);
4940
+ fs = __toESM(require("node:fs"), 1);
4941
+ path = __toESM(require("node:path"), 1);
4942
+ import_node_util = require("node:util");
4943
+ execFile = (0, import_node_util.promisify)(import_node_child_process.execFile);
4944
+ slug3 = (id) => id.replace(/[^a-zA-Z0-9_-]+/g, "_");
4945
+ GhCiProvider = class {
4946
+ constructor(opts) {
4947
+ this.opts = opts;
4948
+ }
4949
+ repoDir(repo) {
4950
+ return mirrorDirFor(this.opts.mirrorRoot, repo);
4951
+ }
4952
+ /** 缺则 clone(首次使用/进程重启后本地镜像不存在)——否则 git fetch 会"not a git repo"报错。同 GitRemoteIntegrator。 */
4953
+ ensureClone(repo) {
4954
+ const dir = this.repoDir(repo);
4955
+ if (fs.existsSync(path.join(dir, ".git"))) {
4956
+ if (repo.remote) {
4957
+ let origin = "";
4958
+ try {
4959
+ origin = (0, import_node_child_process.execFileSync)("git", ["remote", "get-url", "origin"], { cwd: dir, env: process.env }).toString().trim();
4960
+ } catch {
4961
+ }
4962
+ if (origin !== repo.remote) {
4963
+ (0, import_node_child_process.execFileSync)("git", ["remote", "set-url", "origin", repo.remote], { cwd: dir, env: process.env });
4964
+ this.opts.log?.(`[ci] ${repo.id} \u955C\u50CF origin \u7EA0\u504F\uFF1A${origin || "(\u65E0)"} \u2192 ${repo.remote}`);
4965
+ }
4966
+ }
4967
+ return;
4968
+ }
4969
+ if (!repo.remote) throw new Error(`repo ${repo.id} \u65E0 remote\uFF0C\u65E0\u6CD5 clone`);
4970
+ fs.mkdirSync(path.dirname(dir), { recursive: true });
4971
+ (0, import_node_child_process.execFileSync)("git", ["clone", "--quiet", repo.remote, dir], { env: process.env });
4972
+ this.opts.log?.(`[ci] cloned ${repo.id} \u2192 ${dir}`);
4973
+ }
4974
+ async gh(repo, args) {
4975
+ this.ensureClone(repo);
4976
+ const { stdout } = await execFile("gh", args, { cwd: this.repoDir(repo), env: process.env });
4977
+ return stdout.trim();
4978
+ }
4979
+ async git(repo, args) {
4980
+ this.ensureClone(repo);
4981
+ const { stdout } = await execFile("git", args, { cwd: this.repoDir(repo), env: process.env });
4982
+ return stdout.trim();
4983
+ }
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"]);
4989
+ const list = JSON.parse(existing || "[]");
4990
+ 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})`);
4994
+ return created.url ? created : { number: created.number, url };
4995
+ }
4996
+ async status(ref) {
4997
+ let raw;
4998
+ try {
4999
+ raw = await this.gh(ref.repo, ["pr", "view", ref.branch, "--json", "statusCheckRollup,createdAt"]);
5000
+ } catch (e) {
5001
+ return { state: "pending", summary: `\u8BFB PR \u72B6\u6001\u5931\u8D25\uFF08\u4E0B\u8F6E\u91CD\u8BD5\uFF09\uFF1A${String(e)}` };
5002
+ }
5003
+ const parsed = JSON.parse(raw || "{}");
5004
+ const rollup = parsed.statusCheckRollup ?? [];
5005
+ if (rollup.length === 0) {
5006
+ const grace = this.opts.noCheckGraceMs ?? 12e4;
5007
+ const ageMs = parsed.createdAt ? Date.now() - Date.parse(parsed.createdAt) : Infinity;
5008
+ if (ageMs < grace) return { state: "pending" };
5009
+ 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`);
5011
+ return { state: "success", summary: "\u65E0 CI check\uFF0C\u95F8\u7A7A\u8FC7" };
5012
+ }
5013
+ const failed = [];
5014
+ let anyPending = false;
5015
+ for (const c of rollup) {
5016
+ const concl = (c.conclusion ?? "").toUpperCase();
5017
+ const st = (c.status ?? "").toUpperCase();
5018
+ if (st && st !== "COMPLETED") {
5019
+ anyPending = true;
5020
+ continue;
5021
+ }
5022
+ if (concl && !["SUCCESS", "NEUTRAL", "SKIPPED"].includes(concl)) failed.push(c);
5023
+ }
5024
+ if (failed.length > 0) {
5025
+ const blocks = [];
5026
+ for (const c of failed) {
5027
+ const name = c.name ?? c.context ?? "check";
5028
+ const concl = (c.conclusion ?? "").toUpperCase();
5029
+ const url = c.detailsUrl ?? c.targetUrl ?? "";
5030
+ let block = ` - ${name} \u2192 ${concl}${url ? `
5031
+ ${url}` : ""}`;
5032
+ const tail = await this.failedLogTail(ref.repo, url);
5033
+ if (tail) block += `
5034
+ \u5931\u8D25\u65E5\u5FD7\uFF08\u672B\u5C3E\uFF09\uFF1A
5035
+ ${tail.split("\n").map((l) => " " + l).join("\n")}`;
5036
+ blocks.push(block);
5037
+ }
5038
+ return { state: "failure", summary: `CI \u672A\u8FC7\uFF08${failed.length} \u4E2A check\uFF09\uFF1A
5039
+ ${blocks.join("\n")}` };
5040
+ }
5041
+ if (anyPending) return { state: "pending" };
5042
+ return { state: "success", summary: `CI \u901A\u8FC7\uFF08${rollup.length} \u4E2A check\uFF09` };
5043
+ }
5044
+ /** best-effort:从 check 的 detailsUrl 提 Actions run id,取失败步骤日志末尾(截断)。取不到返回空。 */
5045
+ async failedLogTail(repo, detailsUrl) {
5046
+ const m2 = /\/runs\/(\d+)/.exec(detailsUrl);
5047
+ if (!m2) return "";
5048
+ try {
5049
+ const log3 = await this.gh(repo, ["run", "view", m2[1], "--log-failed"]);
5050
+ return log3.split("\n").filter(Boolean).slice(-40).join("\n").slice(-2e3);
5051
+ } catch {
5052
+ return "";
5053
+ }
5054
+ }
5055
+ async merge(ref) {
5056
+ 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}`);
5059
+ return { merged: true };
5060
+ } catch (e) {
5061
+ return { merged: false, reason: String(e) };
5062
+ }
5063
+ }
5064
+ async isMerged(ref) {
5065
+ if (!ref.repo.remote) return false;
5066
+ 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}`]);
5069
+ return true;
5070
+ } catch {
5071
+ return false;
5072
+ }
5073
+ }
5074
+ };
5075
+ }
5076
+ });
5077
+
4811
5078
  // ../server/src/git/collect.ts
4812
5079
  function collectUmbrellaCommits(repos, featBranch, env) {
4813
5080
  const out = {};
@@ -4815,7 +5082,7 @@ function collectUmbrellaCommits(repos, featBranch, env) {
4815
5082
  if (!r.remote) continue;
4816
5083
  let ls;
4817
5084
  try {
4818
- ls = (0, import_node_child_process.execFileSync)("git", ["ls-remote", r.remote, `refs/heads/${featBranch}`, `refs/heads/${r.develop}`], {
5085
+ ls = (0, import_node_child_process2.execFileSync)("git", ["ls-remote", r.remote, `refs/heads/${featBranch}`, `refs/heads/${r.develop}`], {
4819
5086
  encoding: "utf8",
4820
5087
  ...env ? { env } : {}
4821
5088
  });
@@ -4830,12 +5097,16 @@ function collectUmbrellaCommits(repos, featBranch, env) {
4830
5097
  }
4831
5098
  const featSha = refs.get(`refs/heads/${featBranch}`);
4832
5099
  const devSha = refs.get(`refs/heads/${r.develop}`);
4833
- if (featSha && featSha !== devSha) out[r.id] = featSha;
5100
+ if (featSha && featSha !== devSha) out[r.remote ? normalizeRemote(r.remote) : r.id] = featSha;
4834
5101
  }
4835
5102
  return out;
4836
5103
  }
4837
5104
  function parseUmbrellaCommits(contentRef, repos) {
4838
- const byId = new Map(repos.map((r) => [r.id, r]));
5105
+ const byKey = /* @__PURE__ */ new Map();
5106
+ for (const r of repos) {
5107
+ byKey.set(r.id, r);
5108
+ if (r.remote) byKey.set(normalizeRemote(r.remote), r);
5109
+ }
4839
5110
  if (contentRef.trim().startsWith("{")) {
4840
5111
  let manifest = null;
4841
5112
  try {
@@ -4845,8 +5116,8 @@ function parseUmbrellaCommits(contentRef, repos) {
4845
5116
  }
4846
5117
  if (manifest) {
4847
5118
  const out = [];
4848
- for (const [id, sha2] of Object.entries(manifest)) {
4849
- const repo = byId.get(id);
5119
+ for (const [key, sha2] of Object.entries(manifest)) {
5120
+ const repo = byKey.get(key);
4850
5121
  if (repo && typeof sha2 === "string") out.push({ repo, sha: sha2 });
4851
5122
  }
4852
5123
  return out;
@@ -4854,11 +5125,12 @@ function parseUmbrellaCommits(contentRef, repos) {
4854
5125
  }
4855
5126
  return repos[0] ? [{ repo: repos[0], sha: contentRef }] : [];
4856
5127
  }
4857
- var import_node_child_process;
5128
+ var import_node_child_process2;
4858
5129
  var init_collect = __esm({
4859
5130
  "../server/src/git/collect.ts"() {
4860
5131
  "use strict";
4861
- import_node_child_process = require("node:child_process");
5132
+ import_node_child_process2 = require("node:child_process");
5133
+ init_ci_provider();
4862
5134
  }
4863
5135
  });
4864
5136
 
@@ -4869,17 +5141,16 @@ function autoMergeWorkIntoFeat(args) {
4869
5141
  for (const repo of args.repos) {
4870
5142
  if (!repo.remote) continue;
4871
5143
  const remote = repo.remote;
4872
- const key = `${repo.id.replace(/[^a-zA-Z0-9_-]+/g, "_")}-${(0, import_node_crypto3.createHash)("sha1").update(remote).digest("hex").slice(0, 12)}`;
4873
- const dir = path.join(args.mirrorRoot, key);
4874
- const git = (a) => (0, import_node_child_process2.execFileSync)("git", ["-C", dir, ...a], { encoding: "utf8", env, maxBuffer: 256 * 1024 * 1024 }).trim();
5144
+ const dir = mirrorDirFor(args.mirrorRoot, repo);
5145
+ const git = (a) => (0, import_node_child_process3.execFileSync)("git", ["-C", dir, ...a], { encoding: "utf8", env, maxBuffer: 256 * 1024 * 1024 }).trim();
4875
5146
  const clone2 = () => {
4876
- fs.rmSync(dir, { recursive: true, force: true });
4877
- fs.mkdirSync(path.dirname(dir), { recursive: true });
4878
- (0, import_node_child_process2.execFileSync)("git", ["clone", "--quiet", remote, dir], { env, maxBuffer: 256 * 1024 * 1024 });
5147
+ fs2.rmSync(dir, { recursive: true, force: true });
5148
+ fs2.mkdirSync(path2.dirname(dir), { recursive: true });
5149
+ (0, import_node_child_process3.execFileSync)("git", ["clone", "--quiet", remote, dir], { env, maxBuffer: 256 * 1024 * 1024 });
4879
5150
  git(["config", "user.email", "oasis@local"]);
4880
5151
  git(["config", "user.name", "oasis"]);
4881
5152
  };
4882
- if (!fs.existsSync(path.join(dir, ".git"))) {
5153
+ if (!fs2.existsSync(path2.join(dir, ".git"))) {
4883
5154
  clone2();
4884
5155
  } else {
4885
5156
  let originOk = false;
@@ -4934,18 +5205,18 @@ function autoMergeWorkIntoFeat(args) {
4934
5205
  staged.push(dir);
4935
5206
  }
4936
5207
  for (const dir of staged) {
4937
- (0, import_node_child_process2.execFileSync)("git", ["-C", dir, "push", "--quiet", "origin", args.featBranch], { env, maxBuffer: 256 * 1024 * 1024 });
5208
+ (0, import_node_child_process3.execFileSync)("git", ["-C", dir, "push", "--quiet", "origin", args.featBranch], { env, maxBuffer: 256 * 1024 * 1024 });
4938
5209
  }
4939
5210
  return { ok: true };
4940
5211
  }
4941
- var import_node_child_process2, import_node_crypto3, fs, path;
5212
+ var import_node_child_process3, fs2, path2;
4942
5213
  var init_auto_merge = __esm({
4943
5214
  "../server/src/git/auto-merge.ts"() {
4944
5215
  "use strict";
4945
- import_node_child_process2 = require("node:child_process");
4946
- import_node_crypto3 = require("node:crypto");
4947
- fs = __toESM(require("node:fs"), 1);
4948
- path = __toESM(require("node:path"), 1);
5216
+ import_node_child_process3 = require("node:child_process");
5217
+ fs2 = __toESM(require("node:fs"), 1);
5218
+ path2 = __toESM(require("node:path"), 1);
5219
+ init_ci_provider();
4949
5220
  }
4950
5221
  });
4951
5222
 
@@ -5361,8 +5632,8 @@ var init_parseUtil = __esm({
5361
5632
  init_errors2();
5362
5633
  init_en();
5363
5634
  makeIssue = (params) => {
5364
- const { data, path: path20, errorMaps, issueData } = params;
5365
- const fullPath = [...path20, ...issueData.path || []];
5635
+ const { data, path: path19, errorMaps, issueData } = params;
5636
+ const fullPath = [...path19, ...issueData.path || []];
5366
5637
  const fullIssue = {
5367
5638
  ...issueData,
5368
5639
  path: fullPath
@@ -5670,11 +5941,11 @@ var init_types = __esm({
5670
5941
  init_parseUtil();
5671
5942
  init_util();
5672
5943
  ParseInputLazyPath = class {
5673
- constructor(parent, value, path20, key) {
5944
+ constructor(parent, value, path19, key) {
5674
5945
  this._cachedPath = [];
5675
5946
  this.parent = parent;
5676
5947
  this.data = value;
5677
- this._path = path20;
5948
+ this._path = path19;
5678
5949
  this._key = key;
5679
5950
  }
5680
5951
  get path() {
@@ -9248,10 +9519,10 @@ function assignProp(target, prop, value) {
9248
9519
  configurable: true
9249
9520
  });
9250
9521
  }
9251
- function getElementAtPath(obj, path20) {
9252
- if (!path20)
9522
+ function getElementAtPath(obj, path19) {
9523
+ if (!path19)
9253
9524
  return obj;
9254
- return path20.reduce((acc, key) => acc?.[key], obj);
9525
+ return path19.reduce((acc, key) => acc?.[key], obj);
9255
9526
  }
9256
9527
  function promiseAllObject(promisesObj) {
9257
9528
  const keys = Object.keys(promisesObj);
@@ -9500,11 +9771,11 @@ function aborted(x2, startIndex = 0) {
9500
9771
  }
9501
9772
  return false;
9502
9773
  }
9503
- function prefixIssues(path20, issues) {
9774
+ function prefixIssues(path19, issues) {
9504
9775
  return issues.map((iss) => {
9505
9776
  var _a;
9506
9777
  (_a = iss).path ?? (_a.path = []);
9507
- iss.path.unshift(path20);
9778
+ iss.path.unshift(path19);
9508
9779
  return iss;
9509
9780
  });
9510
9781
  }
@@ -18044,8 +18315,8 @@ var require_utils = __commonJS({
18044
18315
  }
18045
18316
  return ind;
18046
18317
  }
18047
- function removeDotSegments(path20) {
18048
- let input = path20;
18318
+ function removeDotSegments(path19) {
18319
+ let input = path19;
18049
18320
  const output = [];
18050
18321
  let nextSlash = -1;
18051
18322
  let len = 0;
@@ -18297,8 +18568,8 @@ var require_schemes = __commonJS({
18297
18568
  wsComponent.secure = void 0;
18298
18569
  }
18299
18570
  if (wsComponent.resourceName) {
18300
- const [path20, query] = wsComponent.resourceName.split("?");
18301
- wsComponent.path = path20 && path20 !== "/" ? path20 : void 0;
18571
+ const [path19, query] = wsComponent.resourceName.split("?");
18572
+ wsComponent.path = path19 && path19 !== "/" ? path19 : void 0;
18302
18573
  wsComponent.query = query;
18303
18574
  wsComponent.resourceName = void 0;
18304
18575
  }
@@ -21811,23 +22082,23 @@ var init_router = __esm({
21811
22082
  };
21812
22083
  Router = class {
21813
22084
  routes = [];
21814
- on(method, path20, handler) {
21815
- this.routes.push({ method, segments: path20.split("/").filter(Boolean), handler });
22085
+ on(method, path19, handler) {
22086
+ this.routes.push({ method, segments: path19.split("/").filter(Boolean), handler });
21816
22087
  }
21817
- get(path20, h) {
21818
- this.on("GET", path20, h);
22088
+ get(path19, h) {
22089
+ this.on("GET", path19, h);
21819
22090
  }
21820
- post(path20, h) {
21821
- this.on("POST", path20, h);
22091
+ post(path19, h) {
22092
+ this.on("POST", path19, h);
21822
22093
  }
21823
- patch(path20, h) {
21824
- this.on("PATCH", path20, h);
22094
+ patch(path19, h) {
22095
+ this.on("PATCH", path19, h);
21825
22096
  }
21826
- put(path20, h) {
21827
- this.on("PUT", path20, h);
22097
+ put(path19, h) {
22098
+ this.on("PUT", path19, h);
21828
22099
  }
21829
- delete(path20, h) {
21830
- this.on("DELETE", path20, h);
22100
+ delete(path19, h) {
22101
+ this.on("DELETE", path19, h);
21831
22102
  }
21832
22103
  match(method, pathname) {
21833
22104
  const parts = pathname.split("/").filter(Boolean);
@@ -22296,10 +22567,10 @@ async function buildDynamicWorkorderPlan(input) {
22296
22567
  const schema = input.schema;
22297
22568
  const schemaByName = new Map(schema.map((def) => [def.name, def]));
22298
22569
  const displayTitle = input.title?.trim() || input.description;
22299
- const slug6 = artifactSlug(input.requestedArtifactId ?? input.workspace, displayTitle);
22300
- const briefId = `artifact:brief:${slug6}`;
22570
+ const slug4 = artifactSlug(input.requestedArtifactId ?? input.workspace, displayTitle);
22571
+ const briefId = `artifact:brief:${slug4}`;
22301
22572
  const primaryType = schemaByName.has(input.requestedType) && input.requestedType !== "brief" ? input.requestedType : "prd";
22302
- const primaryId = input.requestedArtifactId && input.requestedType === primaryType ? input.requestedArtifactId : `artifact:${primaryType}:${slug6}`;
22573
+ const primaryId = input.requestedArtifactId && input.requestedType === primaryType ? input.requestedArtifactId : `artifact:${primaryType}:${slug4}`;
22303
22574
  const issues = [...input.agentIssues ?? []];
22304
22575
  const enforceStaffing = Boolean(input.registry);
22305
22576
  const staffableTypes = new Set((await plannerArtifactTypes(schema, input.registry)).map((def) => def.name));
@@ -22378,7 +22649,7 @@ async function buildDynamicWorkorderPlan(input) {
22378
22649
  const owner = await resolveRole(def.ownerRole, type);
22379
22650
  const ownerActor = owner.actor ?? (type === primaryType ? input.actor : `pending:role:${def.ownerRole}`);
22380
22651
  if (enforceStaffing && !owner.actor && type !== primaryType) continue;
22381
- const id = type === primaryType ? primaryId : `artifact:${type}:${slug6}`;
22652
+ const id = type === primaryType ? primaryId : `artifact:${type}:${slug4}`;
22382
22653
  availableNodes.push({ type, id, role: def.ownerRole, owner: ownerActor });
22383
22654
  }
22384
22655
  }
@@ -23859,11 +24130,11 @@ async function resolveProposeContent(kernel, blobs, args, artifactId, ctx) {
23859
24130
  const ws = art?.workspace;
23860
24131
  const repos = ws ? (await ctx.resolveCodeRepo(ws))?.repos : void 0;
23861
24132
  if (repos?.length) {
23862
- const slug6 = (id) => id.replace(/[^a-zA-Z0-9_-]+/g, "_");
23863
- const feat = `feat/${slug6(artifactId)}`;
24133
+ const slug4 = (id) => id.replace(/[^a-zA-Z0-9_-]+/g, "_");
24134
+ const feat = `feat/${slug4(artifactId)}`;
23864
24135
  const proposingPart = optStr(args, "part");
23865
24136
  if (proposingPart !== void 0 && ctx.gitMirrorRoot) {
23866
- const work = `feat/${slug6(artifactId)}__${slug6(proposingPart)}`;
24137
+ const work = `feat/${slug4(artifactId)}__${slug4(proposingPart)}`;
23867
24138
  const r = autoMergeWorkIntoFeat({
23868
24139
  repos,
23869
24140
  workBranch: work,
@@ -24384,7 +24655,8 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
24384
24655
  artifactId: str(args, "artifactId"),
24385
24656
  actor,
24386
24657
  reason: str(args, "reason"),
24387
- ...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") } : {}
24388
24660
  });
24389
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' };
24390
24662
  }
@@ -24440,6 +24712,23 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
24440
24712
  await kernel.reopen({ artifactId: str(args, "artifactId"), actor, note: optStr(args, "note") });
24441
24713
  return { message: `reopened ${str(args, "artifactId")}\uFF08lifecycle \u2192 active\uFF09` };
24442
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
+ }
24443
24732
  case "forceConclude": {
24444
24733
  await kernel.forceConclude({
24445
24734
  artifactId: str(args, "artifactId"),
@@ -24582,7 +24871,7 @@ async function runView(kernel, oplog, blobs, name, artifactId, params, ciGateSta
24582
24871
  }
24583
24872
  case "content": {
24584
24873
  const id = need2();
24585
- const path20 = params?.get("path") ?? null;
24874
+ const path19 = params?.get("path") ?? null;
24586
24875
  const artifact = model.artifacts.get(id);
24587
24876
  if (!artifact) throw new Error(`artifact \u4E0D\u5B58\u5728: ${id}`);
24588
24877
  const head = artifact.currentRev;
@@ -24594,11 +24883,11 @@ async function runView(kernel, oplog, blobs, name, artifactId, params, ciGateSta
24594
24883
  const dec2 = new TextDecoder();
24595
24884
  if (headRev.contentKind === "manifest") {
24596
24885
  const entries = await readManifest(blobs, headRev.contentRef);
24597
- if (path20 !== null) {
24598
- const e = entries.find((x2) => x2.path === path20);
24599
- if (!e) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${path20}\uFF08\u672C\u4EA7\u7269\u6709\uFF1A${entries.map((x2) => x2.path).join("\u3001")}\uFF09`);
24886
+ if (path19 !== null) {
24887
+ const e = entries.find((x2) => x2.path === path19);
24888
+ if (!e) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${path19}\uFF08\u672C\u4EA7\u7269\u6709\uFF1A${entries.map((x2) => x2.path).join("\u3001")}\uFF09`);
24600
24889
  const isText = !e.bytes.includes(0);
24601
- return { kind: "file", path: path20, bytes: e.bytes.length, text: isText ? dec2.decode(e.bytes) : null };
24890
+ return { kind: "file", path: path19, bytes: e.bytes.length, text: isText ? dec2.decode(e.bytes) : null };
24602
24891
  }
24603
24892
  return {
24604
24893
  kind: "manifest",
@@ -24870,7 +25159,9 @@ async function startOasisServer(opts) {
24870
25159
  if (url.pathname === "/api/intervention/draft" && req.method === "POST") {
24871
25160
  try {
24872
25161
  const plan = JSON.parse(rawBody);
24873
- const { draftId, changeClass, nonMonotonicOps } = await engine.kernel.proposeIntervention(plan, actor);
25162
+ const chatSessionId = req.headers["x-chat-session-id"];
25163
+ const origin = typeof chatSessionId === "string" && chatSessionId ? chatSessionId : void 0;
25164
+ const { draftId, changeClass, nonMonotonicOps } = await engine.kernel.proposeIntervention(plan, actor, origin ? { origin } : void 0);
24874
25165
  res.writeHead(200, { "content-type": "application/json" }).end(
24875
25166
  JSON.stringify({ draftId, changeClass, nonMonotonicOps })
24876
25167
  );
@@ -25236,7 +25527,10 @@ async function startOasisServer(opts) {
25236
25527
  try {
25237
25528
  const viewName = url.searchParams.get("name") ?? "";
25238
25529
  if (viewName === "intervention-drafts") {
25239
- const data2 = listPendingReviews(engine.kernel.model).map((r) => ({
25530
+ const origin = url.searchParams.get("origin");
25531
+ const workspace = url.searchParams.get("workspace") ?? void 0;
25532
+ const reviews = listPendingReviews(engine.kernel.model, origin === null ? workspace : void 0).filter((r) => origin === null ? true : r.origin === origin);
25533
+ const data2 = reviews.map((r) => ({
25240
25534
  id: r.draftId,
25241
25535
  plan: r.plan,
25242
25536
  draftedBy: r.proposedBy,
@@ -25247,6 +25541,7 @@ async function startOasisServer(opts) {
25247
25541
  downstreamClosure: r.downstreamClosure,
25248
25542
  at: r.at,
25249
25543
  origin: r.origin,
25544
+ workspace: r.workspace,
25250
25545
  anchor: r.anchor
25251
25546
  }));
25252
25547
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ data: data2 }));
@@ -25559,12 +25854,12 @@ async function startOasisServer(opts) {
25559
25854
  const actorCtx = await opts.resolveActorContext(body.actorId).catch(() => null);
25560
25855
  if (actorCtx) {
25561
25856
  const { mkdtempSync: mkdtempSync9, mkdirSync: mkdirSync17, writeFileSync: writeFileSync15 } = await import("node:fs");
25562
- const { join: join24, dirname: dirname19 } = await import("node:path");
25563
- const { tmpdir: tmpdir13 } = await import("node:os");
25564
- const dir = mkdtempSync9(join24(tmpdir13(), "oasis-chat-"));
25857
+ const { join: join23, dirname: dirname19 } = await import("node:path");
25858
+ const { tmpdir: tmpdir12 } = await import("node:os");
25859
+ const dir = mkdtempSync9(join23(tmpdir12(), "oasis-chat-"));
25565
25860
  if (actorCtx.config?.prompt) {
25566
25861
  for (const [rel, content] of Object.entries(splitIdentityFiles2(actorCtx.config.prompt))) {
25567
- const file = join24(dir, rel);
25862
+ const file = join23(dir, rel);
25568
25863
  mkdirSync17(dirname19(file), { recursive: true });
25569
25864
  writeFileSync15(file, content);
25570
25865
  }
@@ -25576,12 +25871,12 @@ async function startOasisServer(opts) {
25576
25871
  const s2 = byId.get(id);
25577
25872
  return s2 ? `- **${s2.name}** (\`${s2.id}\`): ${s2.description}` : `- \`${id}\`\uFF08\u672A\u5728\u6280\u80FD\u5E93\u4E2D\uFF0C\u53EF\u80FD\u5DF2\u5378\u8F7D\uFF09`;
25578
25873
  });
25579
- writeFileSync15(join24(dir, "SKILLS.md"), ["# \u53EF\u7528\u6280\u80FD", "", "\u4EE5\u4E0B\u6280\u80FD\u5DF2\u4E3A\u4F60\u542F\u7528\uFF0C\u53EF\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u4F7F\u7528\uFF1A", "", ...lines].join("\n"));
25874
+ writeFileSync15(join23(dir, "SKILLS.md"), ["# \u53EF\u7528\u6280\u80FD", "", "\u4EE5\u4E0B\u6280\u80FD\u5DF2\u4E3A\u4F60\u542F\u7528\uFF0C\u53EF\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u4F7F\u7528\uFF1A", "", ...lines].join("\n"));
25580
25875
  }
25581
25876
  if (opts.materializeSkills) {
25582
25877
  const skillFiles = await opts.materializeSkills(body.actorId, "claude").catch(() => ({}));
25583
25878
  for (const [rel, content] of Object.entries(skillFiles)) {
25584
- const file = join24(dir, rel);
25879
+ const file = join23(dir, rel);
25585
25880
  mkdirSync17(dirname19(file), { recursive: true });
25586
25881
  writeFileSync15(file, content);
25587
25882
  }
@@ -25595,7 +25890,7 @@ async function startOasisServer(opts) {
25595
25890
  const modeNote = c.mode === "oauth" ? "OAuth \xB7 \u51ED\u8BC1\u7531\u5E73\u53F0\u7BA1\u7406\uFF0C\u901A\u8FC7\u5BF9\u5E94 CLI wrapper \u8C03\u7528" : "\u76F4\u63A5\u5199\u5165 \xB7 \u51ED\u8BC1\u5DF2\u6CE8\u5165\u73AF\u5883\u53D8\u91CF";
25596
25891
  return `- **${c.name}** (\`${c.id}\`): ${statusNote} \xB7 ${modeNote}`;
25597
25892
  });
25598
- writeFileSync15(join24(dir, "CONNECTORS.md"), ["# \u53EF\u7528\u8FDE\u63A5\u5668", "", "\u4EE5\u4E0B\u8FDE\u63A5\u5668\u5DF2\u4E3A\u672C\u6B21\u4F1A\u8BDD\u914D\u7F6E\uFF0C\u51ED\u8BC1\u5DF2\u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u6216 CLI wrapper \u6CE8\u5165\uFF0C\u65E0\u9700\u624B\u52A8\u914D\u7F6E\uFF1A", "", ...lines].join("\n"));
25893
+ writeFileSync15(join23(dir, "CONNECTORS.md"), ["# \u53EF\u7528\u8FDE\u63A5\u5668", "", "\u4EE5\u4E0B\u8FDE\u63A5\u5668\u5DF2\u4E3A\u672C\u6B21\u4F1A\u8BDD\u914D\u7F6E\uFF0C\u51ED\u8BC1\u5DF2\u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u6216 CLI wrapper \u6CE8\u5165\uFF0C\u65E0\u9700\u624B\u52A8\u914D\u7F6E\uFF1A", "", ...lines].join("\n"));
25599
25894
  }
25600
25895
  spawnCwd = dir;
25601
25896
  }
@@ -26037,16 +26332,16 @@ function createTokenIssuer(opts = {}) {
26037
26332
  function createNodeTokenStore(file) {
26038
26333
  const read = () => {
26039
26334
  try {
26040
- return JSON.parse(fs3.readFileSync(file, "utf8"));
26335
+ return JSON.parse(fs4.readFileSync(file, "utf8"));
26041
26336
  } catch {
26042
26337
  return {};
26043
26338
  }
26044
26339
  };
26045
- const write = (table) => fs3.writeFileSync(file, JSON.stringify(table, null, 2), { mode: 384 });
26340
+ const write = (table) => fs4.writeFileSync(file, JSON.stringify(table, null, 2), { mode: 384 });
26046
26341
  return {
26047
26342
  issue(nodeId) {
26048
26343
  const table = read();
26049
- const token = `ont_${(0, import_node_crypto4.randomBytes)(24).toString("base64url")}`;
26344
+ const token = `ont_${(0, import_node_crypto3.randomBytes)(24).toString("base64url")}`;
26050
26345
  table[token] = nodeId;
26051
26346
  write(table);
26052
26347
  return token;
@@ -26068,14 +26363,14 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
26068
26363
  const read = () => {
26069
26364
  if (!file) return table;
26070
26365
  try {
26071
- return new Map(Object.entries(JSON.parse(fs3.readFileSync(file, "utf8"))));
26366
+ return new Map(Object.entries(JSON.parse(fs4.readFileSync(file, "utf8"))));
26072
26367
  } catch {
26073
26368
  return /* @__PURE__ */ new Map();
26074
26369
  }
26075
26370
  };
26076
26371
  const write = (m2) => {
26077
26372
  if (!file) return;
26078
- fs3.writeFileSync(file, JSON.stringify(Object.fromEntries(m2), null, 2), { mode: 384 });
26373
+ fs4.writeFileSync(file, JSON.stringify(Object.fromEntries(m2), null, 2), { mode: 384 });
26079
26374
  };
26080
26375
  const table = /* @__PURE__ */ new Map();
26081
26376
  const prune = () => {
@@ -26088,7 +26383,7 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
26088
26383
  return {
26089
26384
  issue(name, ttlMs = defaultTtlMs) {
26090
26385
  const m2 = prune();
26091
- const token = `ent_${(0, import_node_crypto4.randomBytes)(24).toString("base64url")}`;
26386
+ const token = `ent_${(0, import_node_crypto3.randomBytes)(24).toString("base64url")}`;
26092
26387
  const expiresAt = Date.now() + ttlMs;
26093
26388
  m2.set(token, { name, expiresAt });
26094
26389
  write(m2);
@@ -26118,33 +26413,33 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
26118
26413
  }
26119
26414
  };
26120
26415
  }
26121
- var import_node_crypto4, fs3, path2, SESSION_TOKEN_PREFIX, b64url, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
26416
+ var import_node_crypto3, fs4, path3, SESSION_TOKEN_PREFIX, b64url, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
26122
26417
  var init_tokens = __esm({
26123
26418
  "../server/src/tokens.ts"() {
26124
26419
  "use strict";
26125
- import_node_crypto4 = require("node:crypto");
26126
- fs3 = __toESM(require("node:fs"), 1);
26127
- path2 = __toESM(require("node:path"), 1);
26420
+ import_node_crypto3 = require("node:crypto");
26421
+ fs4 = __toESM(require("node:fs"), 1);
26422
+ path3 = __toESM(require("node:path"), 1);
26128
26423
  SESSION_TOKEN_PREFIX = "oat_v2_";
26129
26424
  b64url = (value) => Buffer.from(value).toString("base64url");
26130
26425
  fromB64url = (value) => Buffer.from(value, "base64url").toString("utf8");
26131
26426
  readOrCreateSecret = (file) => {
26132
- if (!file) return (0, import_node_crypto4.randomBytes)(32);
26427
+ if (!file) return (0, import_node_crypto3.randomBytes)(32);
26133
26428
  try {
26134
- const raw = fs3.readFileSync(file, "utf8").trim();
26429
+ const raw = fs4.readFileSync(file, "utf8").trim();
26135
26430
  if (raw) return Buffer.from(raw, "base64url");
26136
26431
  } catch {
26137
26432
  }
26138
- const secret = (0, import_node_crypto4.randomBytes)(32);
26139
- fs3.mkdirSync(path2.dirname(file), { recursive: true });
26140
- fs3.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
26433
+ const secret = (0, import_node_crypto3.randomBytes)(32);
26434
+ fs4.mkdirSync(path3.dirname(file), { recursive: true });
26435
+ fs4.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
26141
26436
  return secret;
26142
26437
  };
26143
- sign = (secret, payload) => (0, import_node_crypto4.createHmac)("sha256", secret).update(payload).digest("base64url");
26438
+ sign = (secret, payload) => (0, import_node_crypto3.createHmac)("sha256", secret).update(payload).digest("base64url");
26144
26439
  signatureMatches = (actual, expected) => {
26145
26440
  const a = Buffer.from(actual);
26146
26441
  const b2 = Buffer.from(expected);
26147
- return a.length === b2.length && (0, import_node_crypto4.timingSafeEqual)(a, b2);
26442
+ return a.length === b2.length && (0, import_node_crypto3.timingSafeEqual)(a, b2);
26148
26443
  };
26149
26444
  isTokenClaims = (value) => {
26150
26445
  if (value === null || typeof value !== "object") return false;
@@ -26499,6 +26794,15 @@ var init_memory_trace_store = __esm({
26499
26794
  const nextCursor = items.length > limit ? page[page.length - 1]?.id ?? null : null;
26500
26795
  return { items: page.map((r) => this.decorate(structuredClone(r))), nextCursor };
26501
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
+ }
26502
26806
  async appendEvent(runId, event) {
26503
26807
  return (await this.appendEvents(runId, [event]))[0];
26504
26808
  }
@@ -26992,9 +27296,9 @@ var init_memory_control_plane_store = __esm({
26992
27296
  const c = this.companies.get(id);
26993
27297
  return c ? { ...c } : null;
26994
27298
  }
26995
- async getCompanyBySlug(slug6) {
27299
+ async getCompanyBySlug(slug4) {
26996
27300
  for (const c of this.companies.values()) {
26997
- if (c.slug === slug6) return { ...c };
27301
+ if (c.slug === slug4) return { ...c };
26998
27302
  }
26999
27303
  return null;
27000
27304
  }
@@ -27489,8 +27793,8 @@ function namedError(name, message) {
27489
27793
  return err;
27490
27794
  }
27491
27795
  function slugProjectId(name) {
27492
- const slug6 = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
27493
- return `proj_${slug6 || Date.now()}`;
27796
+ const slug4 = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
27797
+ return `proj_${slug4 || Date.now()}`;
27494
27798
  }
27495
27799
  function runStateKey(workOrderId, nodeId) {
27496
27800
  return `${workOrderId ?? ""}::${nodeId ?? ""}`;
@@ -27920,13 +28224,13 @@ var init_service = __esm({
27920
28224
  async readDocument(contentRef) {
27921
28225
  for (const [projectId, space] of this.spaces) {
27922
28226
  if (!contentRef.startsWith(`${space.rootRef}/`)) continue;
27923
- const path20 = contentRef.slice(space.rootRef.length + 1);
27924
- const content = space.files.get(path20);
28227
+ const path19 = contentRef.slice(space.rootRef.length + 1);
28228
+ const content = space.files.get(path19);
27925
28229
  if (content === void 0) return null;
27926
28230
  return {
27927
28231
  contentRef,
27928
28232
  content,
27929
- url: `https://outline.local/${encodeURIComponent(projectId)}/${path20.split("/").map(encodeURIComponent).join("/")}`
28233
+ url: `https://outline.local/${encodeURIComponent(projectId)}/${path19.split("/").map(encodeURIComponent).join("/")}`
27930
28234
  };
27931
28235
  }
27932
28236
  return null;
@@ -28010,8 +28314,8 @@ var init_service = __esm({
28010
28314
  const space = this.requireSpace(state.projectId);
28011
28315
  const stored = { ...state, payload: { ...state.payload }, updatedAt: state.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString() };
28012
28316
  space.runStates.set(runStateKey(state.workOrderId, state.nodeId), stored);
28013
- const path20 = `run_state/${state.workOrderId ?? "project"}/${state.nodeId ?? "default"}.json`;
28014
- space.files.set(path20, JSON.stringify(stored, null, 2));
28317
+ const path19 = `run_state/${state.workOrderId ?? "project"}/${state.nodeId ?? "default"}.json`;
28318
+ space.files.set(path19, JSON.stringify(stored, null, 2));
28015
28319
  return { ...stored, payload: { ...stored.payload } };
28016
28320
  }
28017
28321
  requireSpace(projectId) {
@@ -28711,13 +29015,13 @@ var init_service = __esm({
28711
29015
  });
28712
29016
 
28713
29017
  // ../server/src/dev-store.ts
28714
- var import_node_crypto5, fs4, path3, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRegistryStore, FileProjectStateStore, FileProjectDocumentStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore;
29018
+ var import_node_crypto4, fs5, path4, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRegistryStore, FileProjectStateStore, FileProjectDocumentStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore;
28715
29019
  var init_dev_store = __esm({
28716
29020
  "../server/src/dev-store.ts"() {
28717
29021
  "use strict";
28718
- import_node_crypto5 = require("node:crypto");
28719
- fs4 = __toESM(require("node:fs"), 1);
28720
- path3 = __toESM(require("node:path"), 1);
29022
+ import_node_crypto4 = require("node:crypto");
29023
+ fs5 = __toESM(require("node:fs"), 1);
29024
+ path4 = __toESM(require("node:path"), 1);
28721
29025
  init_src();
28722
29026
  init_src3();
28723
29027
  init_src3();
@@ -28729,8 +29033,8 @@ var init_dev_store = __esm({
28729
29033
  }
28730
29034
  static async open(file) {
28731
29035
  const memory = new MemoryOplogStore();
28732
- if (fs4.existsSync(file)) {
28733
- for (const line of fs4.readFileSync(file, "utf8").split("\n")) {
29036
+ if (fs5.existsSync(file)) {
29037
+ for (const line of fs5.readFileSync(file, "utf8").split("\n")) {
28734
29038
  if (!line.trim()) continue;
28735
29039
  const stored = JSON.parse(line);
28736
29040
  const { artifactId, seq, ...op } = stored;
@@ -28738,15 +29042,15 @@ var init_dev_store = __esm({
28738
29042
  if (assigned !== seq) throw new Error(`corrupt ndjson log: seq mismatch at ${line}`);
28739
29043
  }
28740
29044
  } else {
28741
- fs4.mkdirSync(path3.dirname(file), { recursive: true });
28742
- fs4.writeFileSync(file, "");
29045
+ fs5.mkdirSync(path4.dirname(file), { recursive: true });
29046
+ fs5.writeFileSync(file, "");
28743
29047
  }
28744
29048
  return new _NdjsonOplogStore(file, memory);
28745
29049
  }
28746
29050
  async append(artifactId, op, expectedSeq) {
28747
29051
  const seq = await this.memory.append(artifactId, op, expectedSeq);
28748
29052
  const stored = { ...op, artifactId, seq };
28749
- fs4.appendFileSync(this.file, JSON.stringify(stored) + "\n");
29053
+ fs5.appendFileSync(this.file, JSON.stringify(stored) + "\n");
28750
29054
  return seq;
28751
29055
  }
28752
29056
  read(artifactId, fromSeq) {
@@ -28762,39 +29066,39 @@ var init_dev_store = __esm({
28762
29066
  DirBlobStore = class {
28763
29067
  constructor(dir) {
28764
29068
  this.dir = dir;
28765
- fs4.mkdirSync(dir, { recursive: true });
29069
+ fs5.mkdirSync(dir, { recursive: true });
28766
29070
  }
28767
29071
  fileOf(hash) {
28768
- return path3.join(this.dir, hash);
29072
+ return path4.join(this.dir, hash);
28769
29073
  }
28770
29074
  async put(bytes) {
28771
- const hash = (0, import_node_crypto5.createHash)("sha256").update(bytes).digest("hex");
29075
+ const hash = (0, import_node_crypto4.createHash)("sha256").update(bytes).digest("hex");
28772
29076
  const file = this.fileOf(hash);
28773
- if (!fs4.existsSync(file)) fs4.writeFileSync(file, bytes);
29077
+ if (!fs5.existsSync(file)) fs5.writeFileSync(file, bytes);
28774
29078
  return hash;
28775
29079
  }
28776
29080
  async meta(hash) {
28777
29081
  const file = this.fileOf(hash);
28778
- if (!fs4.existsSync(file)) return null;
28779
- const size = fs4.statSync(file).size;
28780
- const fd = fs4.openSync(file, "r");
29082
+ if (!fs5.existsSync(file)) return null;
29083
+ const size = fs5.statSync(file).size;
29084
+ const fd = fs5.openSync(file, "r");
28781
29085
  const head = Buffer.alloc(16);
28782
- fs4.readSync(fd, head, 0, 16, 0);
28783
- fs4.closeSync(fd);
29086
+ fs5.readSync(fd, head, 0, 16, 0);
29087
+ fs5.closeSync(fd);
28784
29088
  const contentType = sniffContentType(new Uint8Array(head));
28785
29089
  return { size, ...contentType ? { contentType } : {} };
28786
29090
  }
28787
29091
  async get(hash) {
28788
29092
  const file = this.fileOf(hash);
28789
- if (!fs4.existsSync(file)) throw new BlobNotFoundError(hash);
28790
- return new Uint8Array(fs4.readFileSync(file));
29093
+ if (!fs5.existsSync(file)) throw new BlobNotFoundError(hash);
29094
+ return new Uint8Array(fs5.readFileSync(file));
28791
29095
  }
28792
29096
  async has(hash) {
28793
- return fs4.existsSync(this.fileOf(hash));
29097
+ return fs5.existsSync(this.fileOf(hash));
28794
29098
  }
28795
29099
  async sweep(reachable) {
28796
- for (const name of fs4.readdirSync(this.dir)) {
28797
- if (!reachable.has(name)) fs4.unlinkSync(this.fileOf(name));
29100
+ for (const name of fs5.readdirSync(this.dir)) {
29101
+ if (!reachable.has(name)) fs5.unlinkSync(this.fileOf(name));
28798
29102
  }
28799
29103
  }
28800
29104
  };
@@ -28822,7 +29126,7 @@ var init_dev_store = __esm({
28822
29126
  this.file = file;
28823
29127
  }
28824
29128
  static async open(file) {
28825
- const seed = fs4.existsSync(file) ? JSON.parse(fs4.readFileSync(file, "utf8")) : [];
29129
+ const seed = fs5.existsSync(file) ? JSON.parse(fs5.readFileSync(file, "utf8")) : [];
28826
29130
  return new _FileTypeRegistryStore(file, seed);
28827
29131
  }
28828
29132
  async put(def) {
@@ -28835,8 +29139,8 @@ var init_dev_store = __esm({
28835
29139
  }
28836
29140
  async save() {
28837
29141
  const tmp = `${this.file}.tmp`;
28838
- fs4.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
28839
- fs4.renameSync(tmp, this.file);
29142
+ fs5.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
29143
+ fs5.renameSync(tmp, this.file);
28840
29144
  }
28841
29145
  };
28842
29146
  FileRegistryStore = class _FileRegistryStore extends MemoryRegistryStore {
@@ -28854,8 +29158,8 @@ var init_dev_store = __esm({
28854
29158
  }
28855
29159
  static async open(file) {
28856
29160
  const store = new _FileRegistryStore(file);
28857
- if (fs4.existsSync(file)) {
28858
- const snap = JSON.parse(fs4.readFileSync(file, "utf8"));
29161
+ if (fs5.existsSync(file)) {
29162
+ const snap = JSON.parse(fs5.readFileSync(file, "utf8"));
28859
29163
  for (const a of snap.actors) await MemoryRegistryStore.prototype.upsertActor.call(store, a);
28860
29164
  for (const c of snap.configs) await MemoryRegistryStore.prototype.appendConfig.call(store, c);
28861
29165
  for (const t of snap.teams) await MemoryRegistryStore.prototype.upsertTeam.call(store, t);
@@ -28896,7 +29200,7 @@ var init_dev_store = __esm({
28896
29200
  skillFiles: skillFilesEntries,
28897
29201
  connections
28898
29202
  };
28899
- fs4.writeFileSync(this.file, JSON.stringify(snap, null, 2));
29203
+ fs5.writeFileSync(this.file, JSON.stringify(snap, null, 2));
28900
29204
  }
28901
29205
  };
28902
29206
  FileProjectStateStore = class _FileProjectStateStore extends MemoryProjectStateStore {
@@ -28906,8 +29210,8 @@ var init_dev_store = __esm({
28906
29210
  }
28907
29211
  static async open(file) {
28908
29212
  const store = new _FileProjectStateStore(file);
28909
- if (fs4.existsSync(file)) {
28910
- const snap = JSON.parse(fs4.readFileSync(file, "utf8"));
29213
+ if (fs5.existsSync(file)) {
29214
+ const snap = JSON.parse(fs5.readFileSync(file, "utf8"));
28911
29215
  for (const project of snap.projects ?? []) await MemoryProjectStateStore.prototype.upsertProject.call(store, project);
28912
29216
  const membersByProject = /* @__PURE__ */ new Map();
28913
29217
  for (const member of snap.members ?? []) {
@@ -28919,8 +29223,8 @@ var init_dev_store = __esm({
28919
29223
  await MemoryProjectStateStore.prototype.replaceProjectMembers.call(store, projectId, members);
28920
29224
  }
28921
29225
  } else {
28922
- fs4.mkdirSync(path3.dirname(file), { recursive: true });
28923
- fs4.writeFileSync(file, JSON.stringify({ projects: [], members: [] }, null, 2));
29226
+ fs5.mkdirSync(path4.dirname(file), { recursive: true });
29227
+ fs5.writeFileSync(file, JSON.stringify({ projects: [], members: [] }, null, 2));
28924
29228
  }
28925
29229
  return store;
28926
29230
  }
@@ -28939,7 +29243,7 @@ var init_dev_store = __esm({
28939
29243
  async save() {
28940
29244
  const projects = await this.listProjects();
28941
29245
  const members = (await Promise.all(projects.map((project) => this.listProjectMembers(project.id)))).flat();
28942
- fs4.writeFileSync(this.file, JSON.stringify({ projects, members }, null, 2));
29246
+ fs5.writeFileSync(this.file, JSON.stringify({ projects, members }, null, 2));
28943
29247
  }
28944
29248
  };
28945
29249
  FileProjectDocumentStore = class _FileProjectDocumentStore extends MemoryProjectDocumentStore {
@@ -28949,8 +29253,8 @@ var init_dev_store = __esm({
28949
29253
  }
28950
29254
  static async open(file) {
28951
29255
  const store = new _FileProjectDocumentStore(file);
28952
- if (fs4.existsSync(file)) {
28953
- const snap = JSON.parse(fs4.readFileSync(file, "utf8"));
29256
+ if (fs5.existsSync(file)) {
29257
+ const snap = JSON.parse(fs5.readFileSync(file, "utf8"));
28954
29258
  for (const [projectId, space] of Object.entries(snap.spaces ?? {})) {
28955
29259
  store.spaces.set(projectId, {
28956
29260
  rootRef: space.rootRef,
@@ -28964,8 +29268,8 @@ var init_dev_store = __esm({
28964
29268
  });
28965
29269
  }
28966
29270
  } else {
28967
- fs4.mkdirSync(path3.dirname(file), { recursive: true });
28968
- fs4.writeFileSync(file, JSON.stringify({ spaces: {} }, null, 2));
29271
+ fs5.mkdirSync(path4.dirname(file), { recursive: true });
29272
+ fs5.writeFileSync(file, JSON.stringify({ spaces: {} }, null, 2));
28969
29273
  }
28970
29274
  return store;
28971
29275
  }
@@ -28998,7 +29302,7 @@ var init_dev_store = __esm({
28998
29302
  runStates: [...space.runStates.values()]
28999
29303
  };
29000
29304
  }
29001
- fs4.writeFileSync(this.file, JSON.stringify({ spaces }, null, 2));
29305
+ fs5.writeFileSync(this.file, JSON.stringify({ spaces }, null, 2));
29002
29306
  }
29003
29307
  };
29004
29308
  FileArtifactStateStore = class _FileArtifactStateStore extends MemoryArtifactStateStore {
@@ -29008,8 +29312,8 @@ var init_dev_store = __esm({
29008
29312
  }
29009
29313
  static async open(file) {
29010
29314
  const store = new _FileArtifactStateStore(file);
29011
- if (fs4.existsSync(file)) {
29012
- const snap = JSON.parse(fs4.readFileSync(file, "utf8"));
29315
+ if (fs5.existsSync(file)) {
29316
+ const snap = JSON.parse(fs5.readFileSync(file, "utf8"));
29013
29317
  for (const artifact of snap.artifacts ?? []) await MemoryArtifactStateStore.prototype.upsertArtifact.call(store, artifact);
29014
29318
  for (const revision of snap.revisions ?? []) await MemoryArtifactStateStore.prototype.upsertRevision.call(store, revision);
29015
29319
  for (const event of snap.events ?? []) await MemoryArtifactStateStore.prototype.appendEvent.call(store, event);
@@ -29018,8 +29322,8 @@ var init_dev_store = __esm({
29018
29322
  for (const binding of snap.workspaceBindings ?? []) await MemoryArtifactStateStore.prototype.upsertWorkspaceBinding.call(store, binding);
29019
29323
  for (const ws of snap.workspaceDispatchHeld ?? []) await MemoryArtifactStateStore.prototype.setWorkspaceDispatchHold.call(store, ws, true);
29020
29324
  } else {
29021
- fs4.mkdirSync(path3.dirname(file), { recursive: true });
29022
- fs4.writeFileSync(file, JSON.stringify({ artifacts: [], revisions: [], events: [], annotations: [], nodeOutputs: [], workspaceBindings: [], workspaceDispatchHeld: [] }, null, 2));
29325
+ fs5.mkdirSync(path4.dirname(file), { recursive: true });
29326
+ fs5.writeFileSync(file, JSON.stringify({ artifacts: [], revisions: [], events: [], annotations: [], nodeOutputs: [], workspaceBindings: [], workspaceDispatchHeld: [] }, null, 2));
29023
29327
  }
29024
29328
  return store;
29025
29329
  }
@@ -29052,7 +29356,7 @@ var init_dev_store = __esm({
29052
29356
  await this.save();
29053
29357
  }
29054
29358
  async save() {
29055
- fs4.writeFileSync(this.file, JSON.stringify(await this.listAll(), null, 2));
29359
+ fs5.writeFileSync(this.file, JSON.stringify(await this.listAll(), null, 2));
29056
29360
  }
29057
29361
  };
29058
29362
  FileTraceStore = class _FileTraceStore extends MemoryTraceStore {
@@ -29061,39 +29365,39 @@ var init_dev_store = __esm({
29061
29365
  this.dir = dir;
29062
29366
  }
29063
29367
  get runsFile() {
29064
- return path3.join(this.dir, "runs.ndjson");
29368
+ return path4.join(this.dir, "runs.ndjson");
29065
29369
  }
29066
29370
  get toolCallsFile() {
29067
- return path3.join(this.dir, "tool-calls.ndjson");
29371
+ return path4.join(this.dir, "tool-calls.ndjson");
29068
29372
  }
29069
29373
  get linksFile() {
29070
- return path3.join(this.dir, "links.ndjson");
29374
+ return path4.join(this.dir, "links.ndjson");
29071
29375
  }
29072
29376
  get artifactsFile() {
29073
- return path3.join(this.dir, "artifacts.ndjson");
29377
+ return path4.join(this.dir, "artifacts.ndjson");
29074
29378
  }
29075
29379
  get eventsDir() {
29076
- return path3.join(this.dir, "events");
29380
+ return path4.join(this.dir, "events");
29077
29381
  }
29078
29382
  eventsFile(runId) {
29079
- return path3.join(this.eventsDir, `${encodeURIComponent(runId)}.ndjson`);
29383
+ return path4.join(this.eventsDir, `${encodeURIComponent(runId)}.ndjson`);
29080
29384
  }
29081
29385
  appendLine(file, obj) {
29082
- fs4.appendFileSync(file, JSON.stringify(obj) + "\n");
29386
+ fs5.appendFileSync(file, JSON.stringify(obj) + "\n");
29083
29387
  }
29084
29388
  static replay(file, fn) {
29085
- if (!fs4.existsSync(file)) return;
29086
- for (const line of fs4.readFileSync(file, "utf8").split("\n")) {
29389
+ if (!fs5.existsSync(file)) return;
29390
+ for (const line of fs5.readFileSync(file, "utf8").split("\n")) {
29087
29391
  if (!line.trim()) continue;
29088
29392
  fn(JSON.parse(line));
29089
29393
  }
29090
29394
  }
29091
29395
  static async open(dir) {
29092
29396
  const store = new _FileTraceStore(dir);
29093
- fs4.mkdirSync(store.eventsDir, { recursive: true });
29397
+ fs5.mkdirSync(store.eventsDir, { recursive: true });
29094
29398
  _FileTraceStore.replay(store.runsFile, (r) => store.restoreRun(r));
29095
- for (const name of fs4.readdirSync(store.eventsDir)) {
29096
- _FileTraceStore.replay(path3.join(store.eventsDir, name), (e) => store.restoreEvent(e));
29399
+ for (const name of fs5.readdirSync(store.eventsDir)) {
29400
+ _FileTraceStore.replay(path4.join(store.eventsDir, name), (e) => store.restoreEvent(e));
29097
29401
  }
29098
29402
  _FileTraceStore.replay(store.toolCallsFile, (c) => store.restoreToolCall(c));
29099
29403
  _FileTraceStore.replay(store.linksFile, (l) => store.restoreLink(l));
@@ -29196,14 +29500,14 @@ var init_dev_store = __esm({
29196
29500
  }
29197
29501
  static async open(file) {
29198
29502
  const store = new _FileChatSessionStore(file);
29199
- if (fs4.existsSync(file)) {
29200
- const snap = JSON.parse(fs4.readFileSync(file, "utf8"));
29503
+ if (fs5.existsSync(file)) {
29504
+ const snap = JSON.parse(fs5.readFileSync(file, "utf8"));
29201
29505
  for (const s2 of snap.sessions ?? []) store.sessions.set(s2.id, s2);
29202
29506
  for (const [sid, list] of Object.entries(snap.messages ?? {})) store.messages.set(sid, list);
29203
29507
  for (const [sid, ids] of Object.entries(snap.sessionWorkOrders ?? {})) store.sessionWorkOrders.set(sid, new Set(ids));
29204
29508
  } else {
29205
- fs4.mkdirSync(path3.dirname(file), { recursive: true });
29206
- fs4.writeFileSync(file, JSON.stringify({ sessions: [], messages: {}, sessionWorkOrders: {} }, null, 2));
29509
+ fs5.mkdirSync(path4.dirname(file), { recursive: true });
29510
+ fs5.writeFileSync(file, JSON.stringify({ sessions: [], messages: {}, sessionWorkOrders: {} }, null, 2));
29207
29511
  }
29208
29512
  return store;
29209
29513
  }
@@ -29238,19 +29542,19 @@ var init_dev_store = __esm({
29238
29542
  messages: Object.fromEntries(this.messages),
29239
29543
  sessionWorkOrders: Object.fromEntries([...this.sessionWorkOrders].map(([k2, v2]) => [k2, [...v2]]))
29240
29544
  };
29241
- fs4.writeFileSync(this.file, JSON.stringify(snap, null, 2));
29545
+ fs5.writeFileSync(this.file, JSON.stringify(snap, null, 2));
29242
29546
  }
29243
29547
  };
29244
29548
  }
29245
29549
  });
29246
29550
 
29247
29551
  // ../server/src/control-plane-file-store.ts
29248
- var fs5, path4, MUTATORS2, FileControlPlaneStore;
29552
+ var fs6, path5, MUTATORS2, FileControlPlaneStore;
29249
29553
  var init_control_plane_file_store = __esm({
29250
29554
  "../server/src/control-plane-file-store.ts"() {
29251
29555
  "use strict";
29252
- fs5 = __toESM(require("node:fs"), 1);
29253
- path4 = __toESM(require("node:path"), 1);
29556
+ fs6 = __toESM(require("node:fs"), 1);
29557
+ path5 = __toESM(require("node:path"), 1);
29254
29558
  init_src3();
29255
29559
  MUTATORS2 = [
29256
29560
  "createCompany",
@@ -29278,15 +29582,15 @@ var init_control_plane_file_store = __esm({
29278
29582
  }
29279
29583
  static async open(file) {
29280
29584
  const store = new _FileControlPlaneStore(file);
29281
- if (fs5.existsSync(file)) {
29282
- const snap = JSON.parse(fs5.readFileSync(file, "utf8"));
29585
+ if (fs6.existsSync(file)) {
29586
+ const snap = JSON.parse(fs6.readFileSync(file, "utf8"));
29283
29587
  for (const c of snap.companies ?? []) await MemoryControlPlaneStore.prototype.createCompany.call(store, c);
29284
29588
  for (const m2 of snap.members ?? []) await MemoryControlPlaneStore.prototype.upsertMember.call(store, m2);
29285
29589
  for (const inv of snap.invitations ?? []) await MemoryControlPlaneStore.prototype.createInvitation.call(store, inv);
29286
29590
  for (const a of snap.accounts ?? []) await MemoryControlPlaneStore.prototype.upsertAccount.call(store, a);
29287
29591
  for (const e of snap.accountSecrets ?? []) store.accountSecrets.set(e.accountId, { ...e.secret });
29288
29592
  } else {
29289
- fs5.mkdirSync(path4.dirname(file), { recursive: true });
29593
+ fs6.mkdirSync(path5.dirname(file), { recursive: true });
29290
29594
  }
29291
29595
  return store;
29292
29596
  }
@@ -29307,7 +29611,7 @@ var init_control_plane_file_store = __esm({
29307
29611
  accounts,
29308
29612
  accountSecrets
29309
29613
  };
29310
- fs5.writeFileSync(this.file, JSON.stringify(snap, null, 2));
29614
+ fs6.writeFileSync(this.file, JSON.stringify(snap, null, 2));
29311
29615
  }
29312
29616
  };
29313
29617
  }
@@ -29666,8 +29970,8 @@ var init_recovery_plan = __esm({
29666
29970
 
29667
29971
  // ../server/src/auth/crypto.ts
29668
29972
  function hashPassword(password) {
29669
- const salt = (0, import_node_crypto6.randomBytes)(16);
29670
- const dk = (0, import_node_crypto6.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
29973
+ const salt = (0, import_node_crypto5.randomBytes)(16);
29974
+ const dk = (0, import_node_crypto5.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
29671
29975
  return `scrypt$${SCRYPT_N}$${salt.toString("base64url")}$${dk.toString("base64url")}`;
29672
29976
  }
29673
29977
  function verifyPassword(password, stored) {
@@ -29684,8 +29988,8 @@ function verifyPassword(password, stored) {
29684
29988
  return false;
29685
29989
  }
29686
29990
  if (expected.length === 0) return false;
29687
- const dk = (0, import_node_crypto6.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
29688
- return dk.length === expected.length && (0, import_node_crypto6.timingSafeEqual)(dk, expected);
29991
+ const dk = (0, import_node_crypto5.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
29992
+ return dk.length === expected.length && (0, import_node_crypto5.timingSafeEqual)(dk, expected);
29689
29993
  }
29690
29994
  function b64urlJson(value) {
29691
29995
  return Buffer.from(JSON.stringify(value)).toString("base64url");
@@ -29693,17 +29997,17 @@ function b64urlJson(value) {
29693
29997
  function signSession(claims, secret) {
29694
29998
  const head = b64urlJson({ alg: "HS256", typ: "JWT" });
29695
29999
  const body = b64urlJson(claims);
29696
- const sig = (0, import_node_crypto6.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
30000
+ const sig = (0, import_node_crypto5.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
29697
30001
  return `${head}.${body}.${sig}`;
29698
30002
  }
29699
30003
  function verifySession(token, secret, nowMs) {
29700
30004
  const parts = token.split(".");
29701
30005
  if (parts.length !== 3) return null;
29702
30006
  const [head, body, sig] = parts;
29703
- const expected = (0, import_node_crypto6.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
30007
+ const expected = (0, import_node_crypto5.createHmac)("sha256", secret).update(`${head}.${body}`).digest("base64url");
29704
30008
  const got = Buffer.from(sig);
29705
30009
  const exp = Buffer.from(expected);
29706
- if (got.length !== exp.length || !(0, import_node_crypto6.timingSafeEqual)(got, exp)) return null;
30010
+ if (got.length !== exp.length || !(0, import_node_crypto5.timingSafeEqual)(got, exp)) return null;
29707
30011
  let claims;
29708
30012
  try {
29709
30013
  claims = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
@@ -29715,21 +30019,21 @@ function verifySession(token, secret, nowMs) {
29715
30019
  return claims;
29716
30020
  }
29717
30021
  function generateCode() {
29718
- return String((0, import_node_crypto6.randomInt)(0, 1e6)).padStart(6, "0");
30022
+ return String((0, import_node_crypto5.randomInt)(0, 1e6)).padStart(6, "0");
29719
30023
  }
29720
30024
  function hashCode(code, secret) {
29721
- return (0, import_node_crypto6.createHmac)("sha256", secret).update(`code:${code}`).digest("base64url");
30025
+ return (0, import_node_crypto5.createHmac)("sha256", secret).update(`code:${code}`).digest("base64url");
29722
30026
  }
29723
30027
  function safeEqualHash(a, b2) {
29724
30028
  const ba = Buffer.from(a);
29725
30029
  const bb = Buffer.from(b2);
29726
- return ba.length === bb.length && (0, import_node_crypto6.timingSafeEqual)(ba, bb);
30030
+ return ba.length === bb.length && (0, import_node_crypto5.timingSafeEqual)(ba, bb);
29727
30031
  }
29728
- var import_node_crypto6, SCRYPT_N, SCRYPT_KEYLEN;
30032
+ var import_node_crypto5, SCRYPT_N, SCRYPT_KEYLEN;
29729
30033
  var init_crypto = __esm({
29730
30034
  "../server/src/auth/crypto.ts"() {
29731
30035
  "use strict";
29732
- import_node_crypto6 = require("node:crypto");
30036
+ import_node_crypto5 = require("node:crypto");
29733
30037
  SCRYPT_N = 16384;
29734
30038
  SCRYPT_KEYLEN = 32;
29735
30039
  }
@@ -29737,11 +30041,11 @@ var init_crypto = __esm({
29737
30041
 
29738
30042
  // ../server/src/file-engine.ts
29739
30043
  async function openFileEngine(dir, seed = {}) {
29740
- fs6.mkdirSync(dir, { recursive: true });
29741
- const oplog = await NdjsonOplogStore.open(path5.join(dir, "oplog.ndjson"));
29742
- const blobs = new DirBlobStore(path5.join(dir, "blobs"));
29743
- const assets = new DirBlobStore(path5.join(dir, "assets"));
29744
- const registry2 = await FileRegistryStore.open(path5.join(dir, "registry-store.json"));
30044
+ fs7.mkdirSync(dir, { recursive: true });
30045
+ const oplog = await NdjsonOplogStore.open(path6.join(dir, "oplog.ndjson"));
30046
+ const blobs = new DirBlobStore(path6.join(dir, "blobs"));
30047
+ const assets = new DirBlobStore(path6.join(dir, "assets"));
30048
+ const registry2 = await FileRegistryStore.open(path6.join(dir, "registry-store.json"));
29745
30049
  const kernel = await Kernel.open(oplog, {
29746
30050
  schema: seed.schema ?? [],
29747
30051
  registry: seed.registry ?? [],
@@ -29754,12 +30058,12 @@ async function openFileEngine(dir, seed = {}) {
29754
30058
  function companyEngineDirName(companyId) {
29755
30059
  return companyId.replace(/[^A-Za-z0-9._-]/g, "_");
29756
30060
  }
29757
- var fs6, path5;
30061
+ var fs7, path6;
29758
30062
  var init_file_engine = __esm({
29759
30063
  "../server/src/file-engine.ts"() {
29760
30064
  "use strict";
29761
- fs6 = __toESM(require("node:fs"), 1);
29762
- path5 = __toESM(require("node:path"), 1);
30065
+ fs7 = __toESM(require("node:fs"), 1);
30066
+ path6 = __toESM(require("node:path"), 1);
29763
30067
  init_src2();
29764
30068
  init_dev_store();
29765
30069
  }
@@ -32013,7 +32317,7 @@ var require_websocket = __commonJS({
32013
32317
  var http2 = require("http");
32014
32318
  var net = require("net");
32015
32319
  var tls = require("tls");
32016
- var { randomBytes: randomBytes5, createHash: createHash12 } = require("crypto");
32320
+ var { randomBytes: randomBytes5, createHash: createHash11 } = require("crypto");
32017
32321
  var { Duplex, Readable } = require("stream");
32018
32322
  var { URL: URL2 } = require("url");
32019
32323
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -32681,7 +32985,7 @@ var require_websocket = __commonJS({
32681
32985
  abortHandshake(websocket, socket, "Invalid Upgrade header");
32682
32986
  return;
32683
32987
  }
32684
- const digest = createHash12("sha1").update(key + GUID).digest("base64");
32988
+ const digest = createHash11("sha1").update(key + GUID).digest("base64");
32685
32989
  if (res.headers["sec-websocket-accept"] !== digest) {
32686
32990
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
32687
32991
  return;
@@ -33050,7 +33354,7 @@ var require_websocket_server = __commonJS({
33050
33354
  var EventEmitter = require("events");
33051
33355
  var http2 = require("http");
33052
33356
  var { Duplex } = require("stream");
33053
- var { createHash: createHash12 } = require("crypto");
33357
+ var { createHash: createHash11 } = require("crypto");
33054
33358
  var extension2 = require_extension();
33055
33359
  var PerMessageDeflate2 = require_permessage_deflate();
33056
33360
  var subprotocol2 = require_subprotocol();
@@ -33357,7 +33661,7 @@ var require_websocket_server = __commonJS({
33357
33661
  );
33358
33662
  }
33359
33663
  if (this._state > RUNNING) return abortHandshake(socket, 503);
33360
- const digest = createHash12("sha1").update(key + GUID).digest("base64");
33664
+ const digest = createHash11("sha1").update(key + GUID).digest("base64");
33361
33665
  const headers = [
33362
33666
  "HTTP/1.1 101 Switching Protocols",
33363
33667
  "Upgrade: websocket",
@@ -33501,12 +33805,12 @@ var init_daemon_hub = __esm({
33501
33805
  this.disconnectListeners.add(cb);
33502
33806
  return () => this.disconnectListeners.delete(cb);
33503
33807
  }
33504
- constructor(server, path20 = "/daemon/ws", resolveNode, opts = {}) {
33808
+ constructor(server, path19 = "/daemon/ws", resolveNode, opts = {}) {
33505
33809
  this.pingIntervalMs = opts.pingIntervalMs ?? 2e4;
33506
33810
  this.silenceTimeoutMs = opts.silenceTimeoutMs ?? 6e4;
33507
33811
  this.wss = new import_websocket_server.default({
33508
33812
  server,
33509
- path: path20,
33813
+ path: path19,
33510
33814
  ...resolveNode ? {
33511
33815
  verifyClient: (info, cb) => {
33512
33816
  const authHeader = info.req.headers.authorization ?? "";
@@ -33688,11 +33992,11 @@ async function fetchGitHubApi(url) {
33688
33992
  async function fetchFromClawHub(sourceUrl) {
33689
33993
  const parsed = new URL(sourceUrl);
33690
33994
  const parts = parsed.pathname.replace(/^\//, "").split("/").filter(Boolean);
33691
- const slug6 = parts[parts.length - 1];
33692
- if (!slug6) throw new Error(`Cannot extract ClawHub slug from URL: ${sourceUrl}`);
33995
+ const slug4 = parts[parts.length - 1];
33996
+ if (!slug4) throw new Error(`Cannot extract ClawHub slug from URL: ${sourceUrl}`);
33693
33997
  const apiBase = "https://clawhub.ai/api/v1";
33694
- const meta = await fetch(`${apiBase}/skills/${encodeURIComponent(slug6)}`).then((r) => {
33695
- if (r.status === 404) throw new Error(`Skill not found on ClawHub: ${slug6}`);
33998
+ const meta = await fetch(`${apiBase}/skills/${encodeURIComponent(slug4)}`).then((r) => {
33999
+ if (r.status === 404) throw new Error(`Skill not found on ClawHub: ${slug4}`);
33696
34000
  if (!r.ok) throw new Error(`ClawHub returned ${r.status}`);
33697
34001
  return r.json();
33698
34002
  });
@@ -33700,13 +34004,13 @@ async function fetchFromClawHub(sourceUrl) {
33700
34004
  let latestVersion = (chSkill.tags?.["latest"] ?? meta.latestVersion?.version) || "";
33701
34005
  let filePaths = [];
33702
34006
  if (latestVersion) {
33703
- const vUrl = `${apiBase}/skills/${encodeURIComponent(slug6)}/versions/${encodeURIComponent(latestVersion)}`;
34007
+ const vUrl = `${apiBase}/skills/${encodeURIComponent(slug4)}/versions/${encodeURIComponent(latestVersion)}`;
33704
34008
  const vMeta = await fetch(vUrl).then((r) => r.ok ? r.json() : null);
33705
34009
  if (vMeta) filePaths = vMeta.version.files.map((f2) => f2.path);
33706
34010
  }
33707
34011
  const bundle = new Bundle();
33708
34012
  for (const fp of filePaths) {
33709
- let fileUrl = `${apiBase}/skills/${encodeURIComponent(slug6)}/file?path=${encodeURIComponent(fp)}`;
34013
+ let fileUrl = `${apiBase}/skills/${encodeURIComponent(slug4)}/file?path=${encodeURIComponent(fp)}`;
33710
34014
  if (latestVersion) fileUrl += `&version=${encodeURIComponent(latestVersion)}`;
33711
34015
  try {
33712
34016
  const content = await fetchText(fileUrl);
@@ -33717,11 +34021,11 @@ async function fetchFromClawHub(sourceUrl) {
33717
34021
  console.warn(`[skill-fetcher] ClawHub: skipping ${fp}: ${err}`);
33718
34022
  }
33719
34023
  }
33720
- if (!bundle.files["SKILL.md"]) throw new Error(`ClawHub: SKILL.md missing for ${slug6}`);
34024
+ if (!bundle.files["SKILL.md"]) throw new Error(`ClawHub: SKILL.md missing for ${slug4}`);
33721
34025
  const { name, description } = parseSkillFrontmatter(bundle.files["SKILL.md"]);
33722
34026
  return {
33723
- id: `claw-${slug6.replace(/[^a-z0-9-]/g, "-")}`,
33724
- name: name || chSkill.displayName || slug6,
34027
+ id: `claw-${slug4.replace(/[^a-z0-9-]/g, "-")}`,
34028
+ name: name || chSkill.displayName || slug4,
33725
34029
  description: description || chSkill.summary || "",
33726
34030
  files: bundle.files
33727
34031
  };
@@ -33861,13 +34165,13 @@ var init_skill_fetcher = __esm({
33861
34165
  files = {};
33862
34166
  totalBytes = 0;
33863
34167
  fileCount = 0;
33864
- add(path20, content) {
34168
+ add(path19, content) {
33865
34169
  const bytes = new TextEncoder().encode(content).length;
33866
34170
  if (this.fileCount >= MAX_FILE_COUNT)
33867
34171
  throw new Error(`Import bundle exceeds ${MAX_FILE_COUNT} file limit`);
33868
34172
  if (this.totalBytes + bytes > MAX_TOTAL_BYTES)
33869
34173
  throw new Error(`Import bundle exceeds ${MAX_TOTAL_BYTES} byte limit`);
33870
- this.files[path20] = content;
34174
+ this.files[path19] = content;
33871
34175
  this.totalBytes += bytes;
33872
34176
  this.fileCount++;
33873
34177
  }
@@ -33884,13 +34188,13 @@ function variableKeyFromEnv(env = process.env) {
33884
34188
  return buf;
33885
34189
  }
33886
34190
  if (env.NODE_ENV === "production") throw new Error("OASIS_VAR_KEY is required in production");
33887
- return (0, import_node_crypto7.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
34191
+ return (0, import_node_crypto6.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
33888
34192
  }
33889
- var import_node_crypto7, REDACTED_MARKER, ActorsService, mask, changedKeys;
34193
+ var import_node_crypto6, REDACTED_MARKER, ActorsService, mask, changedKeys;
33890
34194
  var init_service2 = __esm({
33891
34195
  "../server/src/domains/actors/service.ts"() {
33892
34196
  "use strict";
33893
- import_node_crypto7 = require("node:crypto");
34197
+ import_node_crypto6 = require("node:crypto");
33894
34198
  init_skill_fetcher();
33895
34199
  init_identity();
33896
34200
  init_feishu_oauth();
@@ -34136,7 +34440,7 @@ ${input.description}
34136
34440
  };
34137
34441
  await this.opts.store.putInstalledSkill(installed);
34138
34442
  const skillFiles = Object.entries(fetched.files).map(
34139
- ([path20, content]) => this.buildSkillFile(id, path20, content, now)
34443
+ ([path19, content]) => this.buildSkillFile(id, path19, content, now)
34140
34444
  );
34141
34445
  await this.opts.store.putSkillFiles(id, skillFiles);
34142
34446
  await this.audit({ kind: "skill_install", actorId: by, by, at: now, detail: { skillId: id, source: input.sourceKind, sourceUrl } });
@@ -34171,7 +34475,7 @@ ${input.description}
34171
34475
  connectorId
34172
34476
  });
34173
34477
  const skillFiles = Object.entries(entry.files).map(
34174
- ([path20, content]) => this.buildSkillFile(id, path20, content, now)
34478
+ ([path19, content]) => this.buildSkillFile(id, path19, content, now)
34175
34479
  );
34176
34480
  await this.opts.store.putSkillFiles(id, skillFiles);
34177
34481
  }
@@ -34189,25 +34493,25 @@ ${input.description}
34189
34493
  * 构造 SkillFile:正文内联进 content(决策 0036);blobHash/size 计算为 content 的 sha256
34190
34494
  * 与字节数,仅作元数据/审计(不再有读取方,正文直接从 content 出)。
34191
34495
  */
34192
- buildSkillFile(skillId, path20, content, now) {
34496
+ buildSkillFile(skillId, path19, content, now) {
34193
34497
  const bytes = new TextEncoder().encode(content);
34194
- const blobHash = (0, import_node_crypto7.createHash)("sha256").update(bytes).digest("hex");
34195
- return { skillId, path: path20, content, blobHash, size: bytes.length, updatedAt: now };
34498
+ const blobHash = (0, import_node_crypto6.createHash)("sha256").update(bytes).digest("hex");
34499
+ return { skillId, path: path19, content, blobHash, size: bytes.length, updatedAt: now };
34196
34500
  }
34197
34501
  /**
34198
34502
  * 更新或新建技能的单个文件。
34199
34503
  * 正文内联进 skill_files(决策 0036),更新文件索引,但**不**改写技能的 name/description——
34200
34504
  * 如果修改的是 SKILL.md,调用方应另外调用 updateInstalledSkill 同步 frontmatter。
34201
34505
  */
34202
- async putSkillFile(skillId, path20, content) {
34506
+ async putSkillFile(skillId, path19, content) {
34203
34507
  const cur = (await this.opts.store.listInstalledSkills()).find((x2) => x2.id === skillId);
34204
34508
  if (!cur) throw new Error(`\u672A\u5B89\u88C5\u8BE5\u6280\u80FD\uFF1A${skillId}`);
34205
34509
  const now = this.now();
34206
- const file = this.buildSkillFile(skillId, path20, content, now);
34510
+ const file = this.buildSkillFile(skillId, path19, content, now);
34207
34511
  const existing = await this.opts.store.listSkillFiles(skillId);
34208
- const next = [...existing.filter((f2) => f2.path !== path20), file];
34512
+ const next = [...existing.filter((f2) => f2.path !== path19), file];
34209
34513
  await this.opts.store.putSkillFiles(skillId, next);
34210
- if (path20 === "SKILL.md") {
34514
+ if (path19 === "SKILL.md") {
34211
34515
  const { name, description } = parseSkillFrontmatter(content);
34212
34516
  if (name || description) {
34213
34517
  await this.opts.store.putInstalledSkill({
@@ -34223,11 +34527,11 @@ ${input.description}
34223
34527
  * 删除技能的单个支撑文件(SKILL.md 主文档受保护,调用方应先拦截)。
34224
34528
  * 单文件删除=读现有清单→剔除该 path→整体回写(putSkillFiles 是 replace-all 语义);blob 残留由 GC 回收。
34225
34529
  */
34226
- async deleteSkillFile(skillId, path20) {
34530
+ async deleteSkillFile(skillId, path19) {
34227
34531
  const cur = (await this.opts.store.listInstalledSkills()).find((x2) => x2.id === skillId);
34228
34532
  if (!cur) throw new Error(`\u672A\u5B89\u88C5\u8BE5\u6280\u80FD\uFF1A${skillId}`);
34229
34533
  const existing = await this.opts.store.listSkillFiles(skillId);
34230
- const next = existing.filter((f2) => f2.path !== path20);
34534
+ const next = existing.filter((f2) => f2.path !== path19);
34231
34535
  if (next.length === existing.length) return;
34232
34536
  await this.opts.store.putSkillFiles(skillId, next);
34233
34537
  }
@@ -34525,15 +34829,15 @@ ${input.description}
34525
34829
  return env;
34526
34830
  }
34527
34831
  encrypt(plain) {
34528
- const iv = (0, import_node_crypto7.randomBytes)(12);
34529
- const cipher = (0, import_node_crypto7.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
34832
+ const iv = (0, import_node_crypto6.randomBytes)(12);
34833
+ const cipher = (0, import_node_crypto6.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
34530
34834
  const enc4 = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
34531
34835
  return [iv.toString("base64"), cipher.getAuthTag().toString("base64"), enc4.toString("base64")].join(".");
34532
34836
  }
34533
34837
  decrypt(packed) {
34534
34838
  const [iv, tag, data] = packed.split(".");
34535
34839
  if (!iv || !tag || typeof data !== "string") throw new Error("malformed ciphertext");
34536
- const decipher = (0, import_node_crypto7.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
34840
+ const decipher = (0, import_node_crypto6.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
34537
34841
  decipher.setAuthTag(Buffer.from(tag, "base64"));
34538
34842
  return Buffer.concat([decipher.update(Buffer.from(data, "base64")), decipher.final()]).toString("utf8");
34539
34843
  }
@@ -35067,7 +35371,7 @@ async function listSkillMetadataForActor(args) {
35067
35371
  usedDirs.add(dir);
35068
35372
  const sorted = [...metas].sort((a, b2) => a.path.localeCompare(b2.path));
35069
35373
  let latest = "1970-01-01T00:00:00.000Z";
35070
- const hasher = (0, import_node_crypto8.createHash)("sha256");
35374
+ const hasher = (0, import_node_crypto7.createHash)("sha256");
35071
35375
  for (const m2 of sorted) {
35072
35376
  if (m2.updatedAt > latest) latest = m2.updatedAt;
35073
35377
  hasher.update(m2.path);
@@ -35090,11 +35394,11 @@ async function listSkillMetadataForActor(args) {
35090
35394
  const dir = sanitizeSkillDir(b2.name || b2.id);
35091
35395
  if (usedDirs.has(dir)) continue;
35092
35396
  usedDirs.add(dir);
35093
- const hasher = (0, import_node_crypto8.createHash)("sha256");
35397
+ const hasher = (0, import_node_crypto7.createHash)("sha256");
35094
35398
  for (const p2 of paths) {
35095
35399
  hasher.update(p2);
35096
35400
  hasher.update("\0");
35097
- hasher.update((0, import_node_crypto8.createHash)("sha256").update(b2.files[p2]).digest("hex"));
35401
+ hasher.update((0, import_node_crypto7.createHash)("sha256").update(b2.files[p2]).digest("hex"));
35098
35402
  hasher.update("\0");
35099
35403
  }
35100
35404
  out.push({
@@ -35110,11 +35414,11 @@ async function listSkillMetadataForActor(args) {
35110
35414
  return out;
35111
35415
  }
35112
35416
  function builtinSkillToFiles(b2) {
35113
- return Object.entries(b2.files ?? {}).map(([path20, content]) => ({
35417
+ return Object.entries(b2.files ?? {}).map(([path19, content]) => ({
35114
35418
  skillId: `builtin:${b2.id}`,
35115
- path: path20,
35419
+ path: path19,
35116
35420
  content,
35117
- blobHash: (0, import_node_crypto8.createHash)("sha256").update(content).digest("hex"),
35421
+ blobHash: (0, import_node_crypto7.createHash)("sha256").update(content).digest("hex"),
35118
35422
  size: Buffer.byteLength(content, "utf8"),
35119
35423
  updatedAt: "1970-01-01T00:00:00.000Z"
35120
35424
  }));
@@ -35124,6 +35428,10 @@ function skillsDirForRuntime(runtimeKind) {
35124
35428
  case "claude":
35125
35429
  case "claude-code":
35126
35430
  return ".claude/skills";
35431
+ case "codex":
35432
+ return ".codex/skills";
35433
+ case "cursor":
35434
+ return ".cursor/skills";
35127
35435
  default:
35128
35436
  return ".agent_context/skills";
35129
35437
  }
@@ -35158,11 +35466,11 @@ async function materializeSkillFiles(args) {
35158
35466
  }
35159
35467
  return out;
35160
35468
  }
35161
- var import_node_crypto8;
35469
+ var import_node_crypto7;
35162
35470
  var init_skill_materializer = __esm({
35163
35471
  "../server/src/domains/actors/skill-materializer.ts"() {
35164
35472
  "use strict";
35165
- import_node_crypto8 = require("node:crypto");
35473
+ import_node_crypto7 = require("node:crypto");
35166
35474
  }
35167
35475
  });
35168
35476
 
@@ -35652,7 +35960,7 @@ function createActorsDomain(opts) {
35652
35960
  ...kernel !== void 0 ? { onRolesChanged: (a, r) => kernel.setActorRoles(a, r) } : {},
35653
35961
  audit: async (entry) => {
35654
35962
  await opts.audit?.({
35655
- id: `reg_${(0, import_node_crypto9.randomUUID)()}`,
35963
+ id: `reg_${(0, import_node_crypto8.randomUUID)()}`,
35656
35964
  actor: entry.by,
35657
35965
  kind: "registry_change",
35658
35966
  target: entry.actorId,
@@ -35687,11 +35995,11 @@ function createActorsDomain(opts) {
35687
35995
  register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {} })
35688
35996
  };
35689
35997
  }
35690
- var import_node_crypto9;
35998
+ var import_node_crypto8;
35691
35999
  var init_actors = __esm({
35692
36000
  "../server/src/domains/actors/index.ts"() {
35693
36001
  "use strict";
35694
- import_node_crypto9 = require("node:crypto");
36002
+ import_node_crypto8 = require("node:crypto");
35695
36003
  init_service2();
35696
36004
  init_routes();
35697
36005
  init_service2();
@@ -36524,11 +36832,11 @@ var init_projects = __esm({
36524
36832
  });
36525
36833
 
36526
36834
  // ../server/src/domains/companies/service.ts
36527
- var import_node_crypto10, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, SLUG_RE, CompanyError, CompaniesService;
36835
+ var import_node_crypto9, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, SLUG_RE, CompanyError, CompaniesService;
36528
36836
  var init_service3 = __esm({
36529
36837
  "../server/src/domains/companies/service.ts"() {
36530
36838
  "use strict";
36531
- import_node_crypto10 = require("node:crypto");
36839
+ import_node_crypto9 = require("node:crypto");
36532
36840
  ROLES = ["owner", "admin", "member"];
36533
36841
  INVITABLE_ROLES = ["admin", "member"];
36534
36842
  INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -36580,7 +36888,7 @@ var init_service3 = __esm({
36580
36888
  const existing = await this.store.getAccountByEmail(normalized);
36581
36889
  if (existing) return existing;
36582
36890
  const account = {
36583
- id: `actor:human:${(0, import_node_crypto10.randomUUID)()}`,
36891
+ id: `actor:human:${(0, import_node_crypto9.randomUUID)()}`,
36584
36892
  email: normalized,
36585
36893
  name: name ?? normalized,
36586
36894
  status: "active"
@@ -36625,14 +36933,14 @@ var init_service3 = __esm({
36625
36933
  }
36626
36934
  /** 新建公司:校验 slug、生成 id=company:<slug>、把创建者设为 owner。 */
36627
36935
  async create(input, creatorAccountId) {
36628
- const slug6 = input.slug?.trim() ?? "";
36629
- if (!SLUG_RE.test(slug6)) throw new CompanyError(400, "BAD_SLUG", "slug \u987B\u4E3A URL \u5B89\u5168\u7684\u5C0F\u5199\u4E32\uFF08^[a-z][a-z0-9-]*$\uFF09");
36936
+ const slug4 = input.slug?.trim() ?? "";
36937
+ if (!SLUG_RE.test(slug4)) throw new CompanyError(400, "BAD_SLUG", "slug \u987B\u4E3A URL \u5B89\u5168\u7684\u5C0F\u5199\u4E32\uFF08^[a-z][a-z0-9-]*$\uFF09");
36630
36938
  const name = input.name?.trim() ?? "";
36631
36939
  if (!name) throw new CompanyError(400, "BAD_REQUEST", "\u7F3A\u5C11 name");
36632
- if (await this.store.getCompanyBySlug(slug6)) throw new CompanyError(409, "SLUG_TAKEN", `slug \u5DF2\u88AB\u5360\u7528\uFF1A${slug6}`);
36940
+ if (await this.store.getCompanyBySlug(slug4)) throw new CompanyError(409, "SLUG_TAKEN", `slug \u5DF2\u88AB\u5360\u7528\uFF1A${slug4}`);
36633
36941
  const company = {
36634
- id: `company:${slug6}`,
36635
- slug: slug6,
36942
+ id: `company:${slug4}`,
36943
+ slug: slug4,
36636
36944
  name,
36637
36945
  status: "active",
36638
36946
  createdAt: this.now(),
@@ -36704,7 +37012,7 @@ var init_service3 = __esm({
36704
37012
  }
36705
37013
  const nowMs = Date.parse(this.now());
36706
37014
  const invitation = {
36707
- id: `invitation:${(0, import_node_crypto10.randomUUID)()}`,
37015
+ id: `invitation:${(0, import_node_crypto9.randomUUID)()}`,
36708
37016
  companyId,
36709
37017
  email: mail,
36710
37018
  role,
@@ -36862,9 +37170,9 @@ var init_routes3 = __esm({
36862
37170
 
36863
37171
  // ../server/src/domains/companies/migrate.ts
36864
37172
  function resolveDefaultCompanyConfig(env = process.env) {
36865
- const slug6 = env["OASIS_DEFAULT_COMPANY_SLUG"]?.trim() || "oasis";
37173
+ const slug4 = env["OASIS_DEFAULT_COMPANY_SLUG"]?.trim() || "oasis";
36866
37174
  const name = env["OASIS_DEFAULT_COMPANY_NAME"]?.trim() || "Oasis \u603B\u90E8";
36867
- return { id: `company:${slug6}`, slug: slug6, name };
37175
+ return { id: `company:${slug4}`, slug: slug4, name };
36868
37176
  }
36869
37177
  async function migrateDefaultCompany(store, actors, opts = {}) {
36870
37178
  const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
@@ -37188,7 +37496,7 @@ var init_sender = __esm({
37188
37496
  function deriveBoardKey(artifactId) {
37189
37497
  const tail = artifactId.split(":").pop() ?? artifactId;
37190
37498
  const ascii = tail.replace(/[^a-zA-Z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 24);
37191
- const sha8 = (0, import_node_crypto11.createHash)("sha256").update(artifactId).digest("hex").slice(0, 8);
37499
+ const sha8 = (0, import_node_crypto10.createHash)("sha256").update(artifactId).digest("hex").slice(0, 8);
37192
37500
  return ascii ? `od-${ascii}-${sha8}` : `od-${sha8}`;
37193
37501
  }
37194
37502
  async function odApi(base, fetchImpl, method, p2, body) {
@@ -37221,13 +37529,13 @@ async function ensureProject(base, fetchImpl, boardKey, title) {
37221
37529
  }
37222
37530
  function materializeContext(resolvedDir, files) {
37223
37531
  if (!resolvedDir) return;
37224
- fs7.mkdirSync(resolvedDir, { recursive: true });
37225
- const root = path6.join(resolvedDir, "task-context");
37226
- fs7.rmSync(root, { recursive: true, force: true });
37532
+ fs8.mkdirSync(resolvedDir, { recursive: true });
37533
+ const root = path7.join(resolvedDir, "task-context");
37534
+ fs8.rmSync(root, { recursive: true, force: true });
37227
37535
  for (const [rel, content] of Object.entries(files)) {
37228
- const f2 = path6.join(root, rel);
37229
- fs7.mkdirSync(path6.dirname(f2), { recursive: true });
37230
- fs7.writeFileSync(f2, content);
37536
+ const f2 = path7.join(root, rel);
37537
+ fs8.mkdirSync(path7.dirname(f2), { recursive: true });
37538
+ fs8.writeFileSync(f2, content);
37231
37539
  }
37232
37540
  }
37233
37541
  async function startDesignRun(base, fetchImpl, args) {
@@ -37235,8 +37543,8 @@ async function startDesignRun(base, fetchImpl, args) {
37235
37543
  agentId: args.agentId,
37236
37544
  projectId: args.boardKey,
37237
37545
  conversationId: args.conversationId,
37238
- assistantMessageId: (0, import_node_crypto11.randomUUID)(),
37239
- clientRequestId: (0, import_node_crypto11.randomUUID)(),
37546
+ assistantMessageId: (0, import_node_crypto10.randomUUID)(),
37547
+ clientRequestId: (0, import_node_crypto10.randomUUID)(),
37240
37548
  message: args.message,
37241
37549
  systemPrompt: args.systemPrompt
37242
37550
  });
@@ -37289,23 +37597,23 @@ async function harvestBoard(base, fetchImpl, publicBase, workRoot, boardKey, run
37289
37597
  const htmls = (listing.files ?? []).filter((f2) => f2.kind === "html" && !f2.path.startsWith("task-context/")).filter((f2) => (f2.artifactManifest?.status ?? "complete") === "complete").sort((a, b2) => (b2.mtime ?? 0) - (a.mtime ?? 0));
37290
37598
  const entry = htmls[0]?.path;
37291
37599
  if (!entry) throw new Error("\u753B\u677F\u9879\u76EE\u65E0\u5DF2\u5B8C\u6210\u7684 HTML \u5165\u53E3\u7A3F\u2014\u2014\u8BBE\u8BA1\u4F1A\u8BDD\u672A\u4EA7\u51FA\u53EF\u4EA4\u4ED8\u754C\u9762");
37292
- const dir = fs7.mkdtempSync(path6.join(workRoot, "od-deliverable-"));
37600
+ const dir = fs8.mkdtempSync(path7.join(workRoot, "od-deliverable-"));
37293
37601
  const fetchInline = async (rel) => {
37294
37602
  const res = await fetchImpl(`${base}/api/projects/${encodeURIComponent(boardKey)}/export/${encodeURIComponent(rel)}?inline=1`);
37295
37603
  if (!res.ok) throw new Error(`od export ${rel} \u2192 ${res.status}`);
37296
37604
  return await res.text();
37297
37605
  };
37298
- fs7.writeFileSync(path6.join(dir, "index.html"), await fetchInline(entry));
37606
+ fs8.writeFileSync(path7.join(dir, "index.html"), await fetchInline(entry));
37299
37607
  for (const f2 of listing.files ?? []) {
37300
37608
  if (f2.path.endsWith(".md") && !f2.path.includes("/")) {
37301
37609
  try {
37302
- fs7.writeFileSync(path6.join(dir, f2.path), await fetchInline(f2.path));
37610
+ fs8.writeFileSync(path7.join(dir, f2.path), await fetchInline(f2.path));
37303
37611
  } catch {
37304
37612
  }
37305
37613
  }
37306
37614
  }
37307
- fs7.writeFileSync(
37308
- path6.join(dir, "board.link"),
37615
+ fs8.writeFileSync(
37616
+ path7.join(dir, "board.link"),
37309
37617
  `${publicBase}/projects/${boardKey}
37310
37618
  \u5165\u53E3\u7A3F\uFF1A${entry}
37311
37619
  \u5FEB\u7167\u6765\u6E90 run\uFF1A${runId}
@@ -37338,14 +37646,14 @@ async function runOpenDesignSession(opts) {
37338
37646
  const { dir, entry } = await harvestBoard(opts.base, fetchImpl, opts.publicBase, workRoot, boardKey, runId);
37339
37647
  return { dir, entry, boardKey, runId };
37340
37648
  }
37341
- var import_node_crypto11, fs7, os, path6;
37649
+ var import_node_crypto10, fs8, os, path7;
37342
37650
  var init_driver = __esm({
37343
37651
  "../adapters/src/open-design/driver.ts"() {
37344
37652
  "use strict";
37345
- import_node_crypto11 = require("node:crypto");
37346
- fs7 = __toESM(require("node:fs"), 1);
37653
+ import_node_crypto10 = require("node:crypto");
37654
+ fs8 = __toESM(require("node:fs"), 1);
37347
37655
  os = __toESM(require("node:os"), 1);
37348
- path6 = __toESM(require("node:path"), 1);
37656
+ path7 = __toESM(require("node:path"), 1);
37349
37657
  }
37350
37658
  });
37351
37659
 
@@ -37471,26 +37779,26 @@ function classifyExit(result) {
37471
37779
  return void 0;
37472
37780
  }
37473
37781
  function conversationExists(sessionId) {
37474
- const configDir = process.env["CLAUDE_CONFIG_DIR"] || path7.join(os2.homedir(), ".claude");
37475
- const projectsDir = path7.join(configDir, "projects");
37782
+ const configDir = process.env["CLAUDE_CONFIG_DIR"] || path8.join(os2.homedir(), ".claude");
37783
+ const projectsDir = path8.join(configDir, "projects");
37476
37784
  const target = `${sessionId}.jsonl`;
37477
37785
  try {
37478
- for (const proj of fs8.readdirSync(projectsDir)) {
37479
- if (fs8.existsSync(path7.join(projectsDir, proj, target))) return true;
37786
+ for (const proj of fs9.readdirSync(projectsDir)) {
37787
+ if (fs9.existsSync(path8.join(projectsDir, proj, target))) return true;
37480
37788
  }
37481
37789
  } catch {
37482
37790
  }
37483
37791
  return false;
37484
37792
  }
37485
- var import_node_child_process3, import_node_crypto12, fs8, os2, path7, readline, ClaudeCodeAdapter;
37793
+ var import_node_child_process4, import_node_crypto11, fs9, os2, path8, readline, ClaudeCodeAdapter;
37486
37794
  var init_claude_code = __esm({
37487
37795
  "../adapters/src/claude-code/index.ts"() {
37488
37796
  "use strict";
37489
- import_node_child_process3 = require("node:child_process");
37490
- import_node_crypto12 = require("node:crypto");
37491
- fs8 = __toESM(require("node:fs"), 1);
37797
+ import_node_child_process4 = require("node:child_process");
37798
+ import_node_crypto11 = require("node:crypto");
37799
+ fs9 = __toESM(require("node:fs"), 1);
37492
37800
  os2 = __toESM(require("node:os"), 1);
37493
- path7 = __toESM(require("node:path"), 1);
37801
+ path8 = __toESM(require("node:path"), 1);
37494
37802
  readline = __toESM(require("node:readline"), 1);
37495
37803
  init_sync_skills();
37496
37804
  ClaudeCodeAdapter = class {
@@ -37519,10 +37827,10 @@ var init_claude_code = __esm({
37519
37827
  };
37520
37828
  }
37521
37829
  async spawn(job) {
37522
- const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
37523
- const id = `claude-${slug6}-${(0, import_node_crypto12.randomUUID)().slice(0, 8)}`;
37524
- const dir = job.runtimeSessionId ? path7.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-chat-sessions", job.runtimeSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_")) : fs8.mkdtempSync(path7.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-session-"));
37525
- fs8.mkdirSync(dir, { recursive: true });
37830
+ const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
37831
+ const id = `claude-${slug4}-${(0, import_node_crypto11.randomUUID)().slice(0, 8)}`;
37832
+ const dir = job.runtimeSessionId ? path8.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-chat-sessions", job.runtimeSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_")) : fs9.mkdtempSync(path8.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-session-"));
37833
+ fs9.mkdirSync(dir, { recursive: true });
37526
37834
  await runSyncSkills(this.opts.syncSkills, {
37527
37835
  actorId: job.actor,
37528
37836
  actorToken: job.actorToken,
@@ -37531,14 +37839,14 @@ var init_claude_code = __esm({
37531
37839
  sessionDir: dir
37532
37840
  });
37533
37841
  for (const [rel, content] of Object.entries(job.bundle.files)) {
37534
- const file = path7.join(dir, rel);
37535
- fs8.mkdirSync(path7.dirname(file), { recursive: true });
37536
- fs8.writeFileSync(file, content);
37842
+ const file = path8.join(dir, rel);
37843
+ fs9.mkdirSync(path8.dirname(file), { recursive: true });
37844
+ fs9.writeFileSync(file, content);
37537
37845
  }
37538
37846
  for (const [rel, b64] of Object.entries(job.bundle.binaryFiles ?? {})) {
37539
- const file = path7.join(dir, rel);
37540
- fs8.mkdirSync(path7.dirname(file), { recursive: true });
37541
- fs8.writeFileSync(file, Buffer.from(b64, "base64"));
37847
+ const file = path8.join(dir, rel);
37848
+ fs9.mkdirSync(path8.dirname(file), { recursive: true });
37849
+ fs9.writeFileSync(file, Buffer.from(b64, "base64"));
37542
37850
  }
37543
37851
  const prompt = job.bundle.files["TASK.md"];
37544
37852
  if (!prompt) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
@@ -37559,7 +37867,7 @@ var init_claude_code = __esm({
37559
37867
  ...this.opts.extraArgs ?? []
37560
37868
  ];
37561
37869
  const otelEnv = this.resolveOtelEnv(job, id);
37562
- const child = (0, import_node_child_process3.spawn)(this.opts.claudeBin ?? "claude", args, {
37870
+ const child = (0, import_node_child_process4.spawn)(this.opts.claudeBin ?? "claude", args, {
37563
37871
  cwd: dir,
37564
37872
  env: {
37565
37873
  // 凭证泄漏防护:剔除 pnpm/npm 脚本注入的变量——npm_lifecycle_script 等会把 serve 的启动命令
@@ -37570,7 +37878,7 @@ var init_claude_code = __esm({
37570
37878
  ...job.env,
37571
37879
  // BUG-4 缓冲:抬高单响应输出 token 上限(临时缓冲,根治靠分解)
37572
37880
  ...this.opts.maxOutputTokens ? { CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(this.opts.maxOutputTokens) } : {},
37573
- ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path7.dirname(p2)).join(path7.delimiter)}${path7.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
37881
+ ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path8.dirname(p2)).join(path8.delimiter)}${path8.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
37574
37882
  OASIS_SERVER: job.server.url,
37575
37883
  OASIS_TOKEN: job.actorToken,
37576
37884
  // OTel 遥测(默认关;resolveOtelEnv 决定开不开,runId 进 resource 属性自然归位到本次 run)。
@@ -37582,8 +37890,8 @@ var init_claude_code = __esm({
37582
37890
  });
37583
37891
  if (promptViaStdin) child.stdin.write(prompt);
37584
37892
  child.stdin.end();
37585
- let transcriptRef = path7.join(dir, "transcript.txt");
37586
- const out = fs8.createWriteStream(transcriptRef);
37893
+ let transcriptRef = path8.join(dir, "transcript.txt");
37894
+ const out = fs9.createWriteStream(transcriptRef);
37587
37895
  const telemetryCbs = [];
37588
37896
  let eventSeq = 0;
37589
37897
  let lastResult;
@@ -37669,14 +37977,14 @@ var init_claude_code = __esm({
37669
37977
  const reason = killedByUs ? "timeout" : classifyExit(lastResult);
37670
37978
  if (this.opts.cleanupWorkdir && !job.runtimeSessionId) {
37671
37979
  try {
37672
- const keepDir = path7.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-transcripts");
37673
- fs8.mkdirSync(keepDir, { recursive: true });
37674
- const kept = path7.join(keepDir, `${id}.txt`);
37980
+ const keepDir = path8.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-transcripts");
37981
+ fs9.mkdirSync(keepDir, { recursive: true });
37982
+ const kept = path8.join(keepDir, `${id}.txt`);
37675
37983
  const src = transcriptRef;
37676
37984
  out.end(() => {
37677
37985
  try {
37678
- fs8.renameSync(src, kept);
37679
- fs8.rmSync(dir, { recursive: true, force: true });
37986
+ fs9.renameSync(src, kept);
37987
+ fs9.rmSync(dir, { recursive: true, force: true });
37680
37988
  } catch {
37681
37989
  }
37682
37990
  });
@@ -37872,10 +38180,20 @@ var init_resilient = __esm({
37872
38180
 
37873
38181
  // ../adapters/src/_core/skill-cache.ts
37874
38182
  function defaultCacheRoot() {
37875
- return path8.join(os3.homedir(), ".oasis", "skill-cache");
38183
+ return path9.join(os3.homedir(), ".oasis", "skill-cache");
37876
38184
  }
37877
38185
  function sessionSkillsSubdir(runtimeKind) {
37878
- return runtimeKind === "claude" || runtimeKind === "claude-code" ? ".claude/skills" : ".agent_context/skills";
38186
+ switch (runtimeKind) {
38187
+ case "claude":
38188
+ case "claude-code":
38189
+ return ".claude/skills";
38190
+ case "codex":
38191
+ return ".codex/skills";
38192
+ case "cursor":
38193
+ return ".cursor/skills";
38194
+ default:
38195
+ return ".agent_context/skills";
38196
+ }
37879
38197
  }
37880
38198
  async function syncActorSkills(opts) {
37881
38199
  const cacheRoot = opts.cacheRoot ?? defaultCacheRoot();
@@ -37883,7 +38201,7 @@ async function syncActorSkills(opts) {
37883
38201
  });
37884
38202
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
37885
38203
  const actorDir = pathForActor(cacheRoot, opts.actorId);
37886
- const manifestPath = path8.join(actorDir, "manifest.json");
38204
+ const manifestPath = path9.join(actorDir, "manifest.json");
37887
38205
  await fsp.mkdir(actorDir, { recursive: true, mode: 493 });
37888
38206
  const release = await acquireLock(cacheRoot, opts.actorId, now, log3);
37889
38207
  try {
@@ -37901,7 +38219,7 @@ async function syncActorSkills(opts) {
37901
38219
  const cloudIds = new Set(cloudMetadata.map((s2) => s2.id));
37902
38220
  for (const cloud of cloudMetadata) {
37903
38221
  const cached2 = manifest.skills[cloud.id];
37904
- const skillDir = path8.join(actorDir, cloud.dir);
38222
+ const skillDir = path9.join(actorDir, cloud.dir);
37905
38223
  const inSync = cached2 && cached2.dir === cloud.dir && cached2.contentHash === cloud.contentHash && cached2.cloudUpdatedAt === cloud.updatedAt && await dirLooksValid(skillDir, cached2.fileList);
37906
38224
  if (inSync) {
37907
38225
  skipped += 1;
@@ -37929,7 +38247,7 @@ async function syncActorSkills(opts) {
37929
38247
  for (const [id, entry] of Object.entries(manifest.skills)) {
37930
38248
  if (cloudIds.has(id)) continue;
37931
38249
  try {
37932
- await fsp.rm(path8.join(actorDir, entry.dir), { recursive: true, force: true });
38250
+ await fsp.rm(path9.join(actorDir, entry.dir), { recursive: true, force: true });
37933
38251
  } catch (err) {
37934
38252
  log3("warn", `\u5220\u9664\u5B64\u513F\u6280\u80FD\u76EE\u5F55\u5931\u8D25 ${entry.dir}`, err);
37935
38253
  }
@@ -37946,14 +38264,14 @@ async function syncActorSkills(opts) {
37946
38264
  }
37947
38265
  }
37948
38266
  function pathForActor(cacheRoot, actorId) {
37949
- return path8.join(cacheRoot, "actors", encodeURIComponent(actorId));
38267
+ return path9.join(cacheRoot, "actors", encodeURIComponent(actorId));
37950
38268
  }
37951
38269
  function lockPathFor(cacheRoot, actorId) {
37952
- return path8.join(cacheRoot, "locks", `${encodeURIComponent(actorId)}.lock`);
38270
+ return path9.join(cacheRoot, "locks", `${encodeURIComponent(actorId)}.lock`);
37953
38271
  }
37954
38272
  async function acquireLock(cacheRoot, actorId, now, log3) {
37955
38273
  const lockPath = lockPathFor(cacheRoot, actorId);
37956
- await fsp.mkdir(path8.dirname(lockPath), { recursive: true, mode: 493 });
38274
+ await fsp.mkdir(path9.dirname(lockPath), { recursive: true, mode: 493 });
37957
38275
  const started = now().getTime();
37958
38276
  while (true) {
37959
38277
  try {
@@ -38019,26 +38337,26 @@ async function readManifest2(manifestPath, actorId, log3) {
38019
38337
  };
38020
38338
  }
38021
38339
  async function writeManifest(manifestPath, manifest) {
38022
- const dir = path8.dirname(manifestPath);
38340
+ const dir = path9.dirname(manifestPath);
38023
38341
  await fsp.mkdir(dir, { recursive: true });
38024
38342
  const tmp = `${manifestPath}.${process.pid}.${Date.now()}.tmp`;
38025
38343
  await fsp.writeFile(tmp, JSON.stringify(manifest, null, 2), { mode: 420 });
38026
38344
  await fsp.rename(tmp, manifestPath);
38027
38345
  }
38028
38346
  async function materializeSkillDir(actorDir, targetDir, files, previousDir) {
38029
- const targetPath = path8.join(actorDir, targetDir);
38030
- const tmpPath = path8.join(actorDir, `${targetDir}.new.${process.pid}.${Date.now()}`);
38031
- const oldBackup = path8.join(actorDir, `${targetDir}.old.${process.pid}.${Date.now()}`);
38347
+ const targetPath = path9.join(actorDir, targetDir);
38348
+ const tmpPath = path9.join(actorDir, `${targetDir}.new.${process.pid}.${Date.now()}`);
38349
+ const oldBackup = path9.join(actorDir, `${targetDir}.old.${process.pid}.${Date.now()}`);
38032
38350
  await fsp.rm(tmpPath, { recursive: true, force: true });
38033
38351
  await fsp.mkdir(tmpPath, { recursive: true, mode: 493 });
38034
38352
  for (const f2 of files) {
38035
38353
  if (!f2.content) continue;
38036
- const dest = path8.join(tmpPath, f2.path);
38037
- const rel = path8.relative(tmpPath, dest);
38038
- if (rel.startsWith("..") || path8.isAbsolute(rel)) {
38354
+ const dest = path9.join(tmpPath, f2.path);
38355
+ const rel = path9.relative(tmpPath, dest);
38356
+ if (rel.startsWith("..") || path9.isAbsolute(rel)) {
38039
38357
  throw new Error(`SkillFile.path \u4E0D\u5408\u6CD5: ${f2.path}`);
38040
38358
  }
38041
- await fsp.mkdir(path8.dirname(dest), { recursive: true });
38359
+ await fsp.mkdir(path9.dirname(dest), { recursive: true });
38042
38360
  await fsp.writeFile(dest, f2.content, { mode: 420 });
38043
38361
  }
38044
38362
  let hadOld = false;
@@ -38052,14 +38370,14 @@ async function materializeSkillDir(actorDir, targetDir, files, previousDir) {
38052
38370
  await fsp.rename(tmpPath, targetPath);
38053
38371
  if (hadOld) await fsp.rm(oldBackup, { recursive: true, force: true });
38054
38372
  if (previousDir && previousDir !== targetDir) {
38055
- await fsp.rm(path8.join(actorDir, previousDir), { recursive: true, force: true });
38373
+ await fsp.rm(path9.join(actorDir, previousDir), { recursive: true, force: true });
38056
38374
  }
38057
38375
  }
38058
38376
  async function dirLooksValid(dir, expectFileList) {
38059
38377
  if (expectFileList.length === 0) return false;
38060
38378
  try {
38061
38379
  for (const rel of expectFileList) {
38062
- await fsp.stat(path8.join(dir, rel));
38380
+ await fsp.stat(path9.join(dir, rel));
38063
38381
  }
38064
38382
  return true;
38065
38383
  } catch {
@@ -38068,18 +38386,18 @@ async function dirLooksValid(dir, expectFileList) {
38068
38386
  }
38069
38387
  async function syncSessionSymlinks(sessionDir, runtimeKind, actorDir, manifest, log3) {
38070
38388
  const subdir = sessionSkillsSubdir(runtimeKind);
38071
- const skillsDir = path8.join(sessionDir, subdir);
38389
+ const skillsDir = path9.join(sessionDir, subdir);
38072
38390
  await fsp.mkdir(skillsDir, { recursive: true, mode: 493 });
38073
38391
  const desired = /* @__PURE__ */ new Map();
38074
38392
  for (const entry of Object.values(manifest.skills)) {
38075
- desired.set(entry.dir, path8.join(actorDir, entry.dir));
38393
+ desired.set(entry.dir, path9.join(actorDir, entry.dir));
38076
38394
  }
38077
38395
  for (const [name, target] of desired) {
38078
- const link = path8.join(skillsDir, name);
38396
+ const link = path9.join(skillsDir, name);
38079
38397
  let ok = false;
38080
38398
  try {
38081
38399
  const current = await fsp.readlink(link);
38082
- ok = path8.resolve(skillsDir, current) === target;
38400
+ ok = path9.resolve(skillsDir, current) === target;
38083
38401
  } catch {
38084
38402
  }
38085
38403
  if (!ok) {
@@ -38099,19 +38417,19 @@ async function syncSessionSymlinks(sessionDir, runtimeKind, actorDir, manifest,
38099
38417
  for (const name of entries) {
38100
38418
  if (desired.has(name)) continue;
38101
38419
  try {
38102
- await fsp.rm(path8.join(skillsDir, name), { recursive: true, force: true });
38420
+ await fsp.rm(path9.join(skillsDir, name), { recursive: true, force: true });
38103
38421
  } catch (err) {
38104
38422
  log3("warn", `\u6E05\u5B64\u513F\u5931\u8D25 ${name}`, err);
38105
38423
  }
38106
38424
  }
38107
38425
  }
38108
- var fsp, os3, path8, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
38426
+ var fsp, os3, path9, MANIFEST_SCHEMA_VERSION, STALE_LOCK_MS, LOCK_ACQUIRE_TIMEOUT_MS, LOCK_POLL_MS;
38109
38427
  var init_skill_cache = __esm({
38110
38428
  "../adapters/src/_core/skill-cache.ts"() {
38111
38429
  "use strict";
38112
38430
  fsp = __toESM(require("node:fs/promises"), 1);
38113
38431
  os3 = __toESM(require("node:os"), 1);
38114
- path8 = __toESM(require("node:path"), 1);
38432
+ path9 = __toESM(require("node:path"), 1);
38115
38433
  MANIFEST_SCHEMA_VERSION = 1;
38116
38434
  STALE_LOCK_MS = 6e4;
38117
38435
  LOCK_ACQUIRE_TIMEOUT_MS = 3e4;
@@ -38295,7 +38613,7 @@ async function discoverAntigravityModels(bin) {
38295
38613
  async function discoverACPModels(bin, provider, args = ["acp"]) {
38296
38614
  const child = (() => {
38297
38615
  try {
38298
- return (0, import_node_child_process4.spawn)(bin, args, { stdio: ["pipe", "pipe", "ignore"], env: { ...process.env } });
38616
+ return (0, import_node_child_process5.spawn)(bin, args, { stdio: ["pipe", "pipe", "ignore"], env: { ...process.env } });
38299
38617
  } catch {
38300
38618
  return null;
38301
38619
  }
@@ -38401,11 +38719,11 @@ function parseACPSessionNewModels(result) {
38401
38719
  async function discoverOpenclawModels(bin) {
38402
38720
  throw new Error("discoverOpenclawModels: TODO");
38403
38721
  }
38404
- var import_node_child_process4, import_node_fs2, import_node_os, import_node_path, modelCache, CACHE_TTL_MS2;
38722
+ var import_node_child_process5, import_node_fs2, import_node_os, import_node_path, modelCache, CACHE_TTL_MS2;
38405
38723
  var init_models = __esm({
38406
38724
  "../adapters/src/_core/models.ts"() {
38407
38725
  "use strict";
38408
- import_node_child_process4 = require("node:child_process");
38726
+ import_node_child_process5 = require("node:child_process");
38409
38727
  import_node_fs2 = require("node:fs");
38410
38728
  import_node_os = require("node:os");
38411
38729
  import_node_path = require("node:path");
@@ -38480,12 +38798,12 @@ function parseCodexModel(lines) {
38480
38798
  function codexSessionsRoot() {
38481
38799
  const candidates = [];
38482
38800
  const ch = process.env["CODEX_HOME"];
38483
- if (ch) candidates.push(path9.join(ch, "sessions"));
38801
+ if (ch) candidates.push(path10.join(ch, "sessions"));
38484
38802
  const home = process.env["HOME"] || os4.homedir();
38485
- if (home) candidates.push(path9.join(home, ".codex", "sessions"));
38803
+ if (home) candidates.push(path10.join(home, ".codex", "sessions"));
38486
38804
  for (const c of candidates) {
38487
38805
  try {
38488
- if (fs9.statSync(c).isDirectory()) return c;
38806
+ if (fs10.statSync(c).isDirectory()) return c;
38489
38807
  } catch {
38490
38808
  }
38491
38809
  }
@@ -38499,13 +38817,13 @@ function codexRolloutExists(sessionId, root = codexSessionsRoot()) {
38499
38817
  if (hit) return;
38500
38818
  let ents;
38501
38819
  try {
38502
- ents = fs9.readdirSync(d, { withFileTypes: true });
38820
+ ents = fs10.readdirSync(d, { withFileTypes: true });
38503
38821
  } catch {
38504
38822
  return;
38505
38823
  }
38506
38824
  for (const ent of ents) {
38507
38825
  if (hit) return;
38508
- const p2 = path9.join(d, ent.name);
38826
+ const p2 = path10.join(d, ent.name);
38509
38827
  if (ent.isDirectory()) walk(p2);
38510
38828
  else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(suffix)) {
38511
38829
  hit = true;
@@ -38522,17 +38840,17 @@ function listRecentRollouts(root, sinceMs) {
38522
38840
  const walk = (d) => {
38523
38841
  let ents;
38524
38842
  try {
38525
- ents = fs9.readdirSync(d, { withFileTypes: true });
38843
+ ents = fs10.readdirSync(d, { withFileTypes: true });
38526
38844
  } catch {
38527
38845
  return;
38528
38846
  }
38529
38847
  for (const ent of ents) {
38530
- const p2 = path9.join(d, ent.name);
38848
+ const p2 = path10.join(d, ent.name);
38531
38849
  if (ent.isDirectory()) walk(p2);
38532
38850
  else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(".jsonl")) {
38533
38851
  let m2 = 0;
38534
38852
  try {
38535
- m2 = fs9.statSync(p2).mtimeMs;
38853
+ m2 = fs10.statSync(p2).mtimeMs;
38536
38854
  } catch {
38537
38855
  }
38538
38856
  if (m2 >= margin) found.push({ f: p2, m: m2 });
@@ -38557,7 +38875,7 @@ function scanCodexTelemetry(workdir, sinceMs, sessionsRoot = codexSessionsRoot()
38557
38875
  const files = listRecentRollouts(sessionsRoot, sinceMs);
38558
38876
  const readLines = (f2) => {
38559
38877
  try {
38560
- return fs9.readFileSync(f2, "utf8").split("\n");
38878
+ return fs10.readFileSync(f2, "utf8").split("\n");
38561
38879
  } catch {
38562
38880
  return null;
38563
38881
  }
@@ -38757,15 +39075,15 @@ function normalizeCodexConsoleLine(line, state = createCodexNormalizeState()) {
38757
39075
  tool.output.push(line);
38758
39076
  return [];
38759
39077
  }
38760
- var import_node_child_process5, import_node_crypto13, fs9, os4, path9, readline2, CodexAdapter;
39078
+ var import_node_child_process6, import_node_crypto12, fs10, os4, path10, readline2, CodexAdapter;
38761
39079
  var init_codex = __esm({
38762
39080
  "../adapters/src/codex/index.ts"() {
38763
39081
  "use strict";
38764
- import_node_child_process5 = require("node:child_process");
38765
- import_node_crypto13 = require("node:crypto");
38766
- fs9 = __toESM(require("node:fs"), 1);
39082
+ import_node_child_process6 = require("node:child_process");
39083
+ import_node_crypto12 = require("node:crypto");
39084
+ fs10 = __toESM(require("node:fs"), 1);
38767
39085
  os4 = __toESM(require("node:os"), 1);
38768
- path9 = __toESM(require("node:path"), 1);
39086
+ path10 = __toESM(require("node:path"), 1);
38769
39087
  readline2 = __toESM(require("node:readline"), 1);
38770
39088
  init_sync_skills();
38771
39089
  init_claude_code();
@@ -38774,10 +39092,10 @@ var init_codex = __esm({
38774
39092
  this.opts = opts;
38775
39093
  }
38776
39094
  async spawn(job) {
38777
- const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
38778
- const id = `codex-${slug6}-${(0, import_node_crypto13.randomUUID)().slice(0, 8)}`;
39095
+ const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
39096
+ const id = `codex-${slug4}-${(0, import_node_crypto12.randomUUID)().slice(0, 8)}`;
38779
39097
  const startedAtMs = Date.now();
38780
- const dir = fs9.mkdtempSync(path9.join(this.opts.workRoot ?? os4.tmpdir(), "oasis-session-"));
39098
+ const dir = fs10.mkdtempSync(path10.join(this.opts.workRoot ?? os4.tmpdir(), "oasis-session-"));
38781
39099
  await runSyncSkills(this.opts.syncSkills, {
38782
39100
  actorId: job.actor,
38783
39101
  actorToken: job.actorToken,
@@ -38786,9 +39104,9 @@ var init_codex = __esm({
38786
39104
  sessionDir: dir
38787
39105
  });
38788
39106
  for (const [rel, content] of Object.entries(job.bundle.files)) {
38789
- const file = path9.join(dir, rel);
38790
- fs9.mkdirSync(path9.dirname(file), { recursive: true });
38791
- fs9.writeFileSync(file, content);
39107
+ const file = path10.join(dir, rel);
39108
+ fs10.mkdirSync(path10.dirname(file), { recursive: true });
39109
+ fs10.writeFileSync(file, content);
38792
39110
  }
38793
39111
  const task = job.bundle.files["TASK.md"];
38794
39112
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
@@ -38811,20 +39129,20 @@ var init_codex = __esm({
38811
39129
  ...this.opts.extraArgs ?? [],
38812
39130
  prompt
38813
39131
  ];
38814
- const child = (0, import_node_child_process5.spawn)(this.opts.codexBin ?? "codex", args, {
39132
+ const child = (0, import_node_child_process6.spawn)(this.opts.codexBin ?? "codex", args, {
38815
39133
  cwd: dir,
38816
39134
  env: {
38817
39135
  ...scrubLeakyEnv(process.env),
38818
39136
  ...this.opts.env,
38819
39137
  ...job.env,
38820
- ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path9.dirname(p2)).join(path9.delimiter)}${path9.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
39138
+ ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path10.dirname(p2)).join(path10.delimiter)}${path10.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
38821
39139
  OASIS_SERVER: job.server.url,
38822
39140
  OASIS_TOKEN: job.actorToken
38823
39141
  },
38824
39142
  stdio: ["ignore", "pipe", "pipe"]
38825
39143
  });
38826
- let transcriptRef = path9.join(dir, "transcript.txt");
38827
- const out = fs9.createWriteStream(transcriptRef);
39144
+ let transcriptRef = path10.join(dir, "transcript.txt");
39145
+ const out = fs10.createWriteStream(transcriptRef);
38828
39146
  const outputCbs = [];
38829
39147
  const telemetryCbs = [];
38830
39148
  const normalizeState = createCodexNormalizeState();
@@ -38890,14 +39208,14 @@ var init_codex = __esm({
38890
39208
  if (exited) return;
38891
39209
  if (this.opts.cleanupWorkdir) {
38892
39210
  try {
38893
- const keepDir = path9.join(this.opts.workRoot ?? os4.tmpdir(), "oasis-transcripts");
38894
- fs9.mkdirSync(keepDir, { recursive: true });
38895
- const kept = path9.join(keepDir, `${id}.txt`);
39211
+ const keepDir = path10.join(this.opts.workRoot ?? os4.tmpdir(), "oasis-transcripts");
39212
+ fs10.mkdirSync(keepDir, { recursive: true });
39213
+ const kept = path10.join(keepDir, `${id}.txt`);
38896
39214
  const src = transcriptRef;
38897
39215
  out.end(() => {
38898
39216
  try {
38899
- fs9.renameSync(src, kept);
38900
- fs9.rmSync(dir, { recursive: true, force: true });
39217
+ fs10.renameSync(src, kept);
39218
+ fs10.rmSync(dir, { recursive: true, force: true });
38901
39219
  } catch {
38902
39220
  }
38903
39221
  });
@@ -39486,10 +39804,10 @@ function buildReportedUsage(acc, model, inputTokensIncludeCacheRead) {
39486
39804
  return buildUsage(normalized, cost);
39487
39805
  }
39488
39806
  async function runACPSession(job, cfg) {
39489
- const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
39490
- const binTag = path10.basename(cfg.bin).replace(/[^a-zA-Z0-9]/g, "").slice(0, 12) || "acp";
39491
- const id = `${binTag}-${slug6}-${(0, import_node_crypto14.randomUUID)().slice(0, 8)}`;
39492
- const dir = fs10.mkdtempSync(path10.join(cfg.workRoot ?? os5.tmpdir(), "oasis-session-"));
39807
+ const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
39808
+ const binTag = path11.basename(cfg.bin).replace(/[^a-zA-Z0-9]/g, "").slice(0, 12) || "acp";
39809
+ const id = `${binTag}-${slug4}-${(0, import_node_crypto13.randomUUID)().slice(0, 8)}`;
39810
+ const dir = fs11.mkdtempSync(path11.join(cfg.workRoot ?? os5.tmpdir(), "oasis-session-"));
39493
39811
  if (cfg.runtimeKind) {
39494
39812
  await runSyncSkills(cfg.syncSkills, {
39495
39813
  actorId: job.actor,
@@ -39500,27 +39818,29 @@ async function runACPSession(job, cfg) {
39500
39818
  });
39501
39819
  }
39502
39820
  for (const [rel, content] of Object.entries(job.bundle.files)) {
39503
- const file = path10.join(dir, rel);
39504
- fs10.mkdirSync(path10.dirname(file), { recursive: true });
39505
- fs10.writeFileSync(file, content);
39821
+ const file = path11.join(dir, rel);
39822
+ fs11.mkdirSync(path11.dirname(file), { recursive: true });
39823
+ fs11.writeFileSync(file, content);
39506
39824
  }
39507
39825
  const task = job.bundle.files["TASK.md"];
39508
39826
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
39509
- const child = (0, import_node_child_process6.spawn)(cfg.bin, [...cfg.args ?? [], ...cfg.extraArgs ?? []], {
39827
+ const child = (0, import_node_child_process7.spawn)(cfg.bin, [...cfg.args ?? [], ...cfg.extraArgs ?? []], {
39510
39828
  cwd: dir,
39511
39829
  env: {
39512
39830
  ...scrubLeakyEnv(process.env),
39513
39831
  ...cfg.env,
39514
39832
  ...job.env,
39515
39833
  ...cfg.yoloEnv ?? {},
39516
- ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path10.dirname(p2)).join(path10.delimiter)}${path10.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
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 ?? "")) } : {},
39517
39837
  OASIS_SERVER: job.server.url,
39518
39838
  OASIS_TOKEN: job.actorToken
39519
39839
  },
39520
39840
  stdio: ["pipe", "pipe", "pipe"]
39521
39841
  });
39522
- let transcriptRef = path10.join(dir, "transcript.txt");
39523
- const transcriptOut = fs10.createWriteStream(transcriptRef);
39842
+ let transcriptRef = path11.join(dir, "transcript.txt");
39843
+ const transcriptOut = fs11.createWriteStream(transcriptRef);
39524
39844
  const outputCbs = [];
39525
39845
  const telemetryCbs = [];
39526
39846
  const exitCbs = [];
@@ -39537,14 +39857,14 @@ async function runACPSession(job, cfg) {
39537
39857
  if (capturedSessionId && !info.runtimeSessionId) info = { ...info, runtimeSessionId: capturedSessionId };
39538
39858
  if (cfg.cleanupWorkdir) {
39539
39859
  try {
39540
- const keepDir = path10.join(cfg.workRoot ?? os5.tmpdir(), "oasis-transcripts");
39541
- fs10.mkdirSync(keepDir, { recursive: true });
39542
- const kept = path10.join(keepDir, `${id}.txt`);
39860
+ const keepDir = path11.join(cfg.workRoot ?? os5.tmpdir(), "oasis-transcripts");
39861
+ fs11.mkdirSync(keepDir, { recursive: true });
39862
+ const kept = path11.join(keepDir, `${id}.txt`);
39543
39863
  const src = transcriptRef;
39544
39864
  transcriptOut.end(() => {
39545
39865
  try {
39546
- fs10.renameSync(src, kept);
39547
- fs10.rmSync(dir, { recursive: true, force: true });
39866
+ fs11.renameSync(src, kept);
39867
+ fs11.rmSync(dir, { recursive: true, force: true });
39548
39868
  } catch {
39549
39869
  }
39550
39870
  });
@@ -39745,17 +40065,18 @@ ${task}` : task;
39745
40065
  }
39746
40066
  };
39747
40067
  }
39748
- var import_node_child_process6, import_node_crypto14, fs10, os5, path10, readline3, ACPClient;
40068
+ var import_node_child_process7, import_node_crypto13, fs11, os5, path11, readline3, ACPClient;
39749
40069
  var init_acp = __esm({
39750
40070
  "../adapters/src/_core/acp.ts"() {
39751
40071
  "use strict";
39752
- import_node_child_process6 = require("node:child_process");
39753
- import_node_crypto14 = require("node:crypto");
39754
- fs10 = __toESM(require("node:fs"), 1);
40072
+ import_node_child_process7 = require("node:child_process");
40073
+ import_node_crypto13 = require("node:crypto");
40074
+ fs11 = __toESM(require("node:fs"), 1);
39755
40075
  os5 = __toESM(require("node:os"), 1);
39756
- path10 = __toESM(require("node:path"), 1);
40076
+ path11 = __toESM(require("node:path"), 1);
39757
40077
  readline3 = __toESM(require("node:readline"), 1);
39758
40078
  init_sync_skills();
40079
+ init_skill_cache();
39759
40080
  init_claude_code();
39760
40081
  init_usage();
39761
40082
  init_pricing();
@@ -40028,6 +40349,11 @@ var init_hermes = __esm({
40028
40349
  },
40029
40350
  extraArgs: this.opts.extraArgs,
40030
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",
40031
40357
  ...this.opts.syncSkills ? { syncSkills: this.opts.syncSkills } : {}
40032
40358
  });
40033
40359
  }
@@ -40087,9 +40413,9 @@ var init_kiro = __esm({
40087
40413
 
40088
40414
  // ../adapters/src/_core/subprocess.ts
40089
40415
  async function materialize(job, cfg) {
40090
- const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
40091
- const id = `${slug6}-${(0, import_node_crypto15.randomUUID)().slice(0, 8)}`;
40092
- const dir = fs11.mkdtempSync(path11.join(cfg.workRoot ?? os6.tmpdir(), "oasis-session-"));
40416
+ const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
40417
+ const id = `${slug4}-${(0, import_node_crypto14.randomUUID)().slice(0, 8)}`;
40418
+ const dir = fs12.mkdtempSync(path12.join(cfg.workRoot ?? os6.tmpdir(), "oasis-session-"));
40093
40419
  if (cfg.runtimeKind) {
40094
40420
  await runSyncSkills(cfg.syncSkills, {
40095
40421
  actorId: job.actor,
@@ -40100,9 +40426,9 @@ async function materialize(job, cfg) {
40100
40426
  });
40101
40427
  }
40102
40428
  for (const [rel, content] of Object.entries(job.bundle.files)) {
40103
- const file = path11.join(dir, rel);
40104
- fs11.mkdirSync(path11.dirname(file), { recursive: true });
40105
- fs11.writeFileSync(file, content);
40429
+ const file = path12.join(dir, rel);
40430
+ fs12.mkdirSync(path12.dirname(file), { recursive: true });
40431
+ fs12.writeFileSync(file, content);
40106
40432
  }
40107
40433
  const task = job.bundle.files["TASK.md"];
40108
40434
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
@@ -40113,7 +40439,7 @@ function buildEnv(job, extra) {
40113
40439
  ...scrubLeakyEnv(process.env),
40114
40440
  ...extra,
40115
40441
  ...job.env,
40116
- ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path11.dirname(p2)).join(path11.delimiter)}${path11.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
40442
+ ...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path12.dirname(p2)).join(path12.delimiter)}${path12.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
40117
40443
  OASIS_SERVER: job.server.url,
40118
40444
  OASIS_TOKEN: job.actorToken
40119
40445
  };
@@ -40125,14 +40451,14 @@ function makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, exitCbs) {
40125
40451
  if (cfg.model && !info.model) info = { ...info, model: cfg.model };
40126
40452
  if (cfg.cleanupWorkdir) {
40127
40453
  try {
40128
- const keepDir = path11.join(cfg.workRoot ?? os6.tmpdir(), "oasis-transcripts");
40129
- fs11.mkdirSync(keepDir, { recursive: true });
40130
- const kept = path11.join(keepDir, `${id}.txt`);
40454
+ const keepDir = path12.join(cfg.workRoot ?? os6.tmpdir(), "oasis-transcripts");
40455
+ fs12.mkdirSync(keepDir, { recursive: true });
40456
+ const kept = path12.join(keepDir, `${id}.txt`);
40131
40457
  const src = transcriptRefPtr.value;
40132
40458
  transcriptOut.end(() => {
40133
40459
  try {
40134
- fs11.renameSync(src, kept);
40135
- fs11.rmSync(dir, { recursive: true, force: true });
40460
+ fs12.renameSync(src, kept);
40461
+ fs12.rmSync(dir, { recursive: true, force: true });
40136
40462
  } catch {
40137
40463
  }
40138
40464
  });
@@ -40149,13 +40475,13 @@ function makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, exitCbs) {
40149
40475
  async function runStreamJson(job, cfg) {
40150
40476
  const { id, dir, task: _task } = await materialize(job, cfg);
40151
40477
  const args = [...cfg.buildArgs(job, dir), ...cfg.extraArgs ?? []];
40152
- const child = (0, import_node_child_process7.spawn)(cfg.bin, args, {
40478
+ const child = (0, import_node_child_process8.spawn)(cfg.bin, args, {
40153
40479
  cwd: dir,
40154
40480
  env: buildEnv(job, cfg.env),
40155
40481
  stdio: ["ignore", "pipe", "pipe"]
40156
40482
  });
40157
- const transcriptRefPtr = { value: path11.join(dir, "transcript.txt") };
40158
- const transcriptOut = fs11.createWriteStream(transcriptRefPtr.value);
40483
+ const transcriptRefPtr = { value: path12.join(dir, "transcript.txt") };
40484
+ const transcriptOut = fs12.createWriteStream(transcriptRefPtr.value);
40159
40485
  const outputCbs = [];
40160
40486
  const telemetryCbs = [];
40161
40487
  const exitCbs = [];
@@ -40225,13 +40551,13 @@ async function runStreamJson(job, cfg) {
40225
40551
  async function runOneShotText(job, cfg) {
40226
40552
  const { id, dir } = await materialize(job, cfg);
40227
40553
  const args = [...cfg.buildArgs(job, dir), ...cfg.extraArgs ?? []];
40228
- const child = (0, import_node_child_process7.spawn)(cfg.bin, args, {
40554
+ const child = (0, import_node_child_process8.spawn)(cfg.bin, args, {
40229
40555
  cwd: dir,
40230
40556
  env: buildEnv(job, cfg.env),
40231
40557
  stdio: ["ignore", "pipe", "pipe"]
40232
40558
  });
40233
- const transcriptRefPtr = { value: path11.join(dir, "transcript.txt") };
40234
- const transcriptOut = fs11.createWriteStream(transcriptRefPtr.value);
40559
+ const transcriptRefPtr = { value: path12.join(dir, "transcript.txt") };
40560
+ const transcriptOut = fs12.createWriteStream(transcriptRefPtr.value);
40235
40561
  const outputCbs = [];
40236
40562
  const exitCbs = [];
40237
40563
  const { finish, exited } = makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, exitCbs);
@@ -40288,15 +40614,15 @@ async function runOneShotText(job, cfg) {
40288
40614
  }
40289
40615
  });
40290
40616
  }
40291
- var import_node_child_process7, import_node_crypto15, fs11, os6, path11, readline4;
40617
+ var import_node_child_process8, import_node_crypto14, fs12, os6, path12, readline4;
40292
40618
  var init_subprocess = __esm({
40293
40619
  "../adapters/src/_core/subprocess.ts"() {
40294
40620
  "use strict";
40295
- import_node_child_process7 = require("node:child_process");
40296
- import_node_crypto15 = require("node:crypto");
40297
- fs11 = __toESM(require("node:fs"), 1);
40621
+ import_node_child_process8 = require("node:child_process");
40622
+ import_node_crypto14 = require("node:crypto");
40623
+ fs12 = __toESM(require("node:fs"), 1);
40298
40624
  os6 = __toESM(require("node:os"), 1);
40299
- path11 = __toESM(require("node:path"), 1);
40625
+ path12 = __toESM(require("node:path"), 1);
40300
40626
  readline4 = __toESM(require("node:readline"), 1);
40301
40627
  init_claude_code();
40302
40628
  init_sync_skills();
@@ -40703,11 +41029,11 @@ function normalizeOpenClaw(line) {
40703
41029
  }
40704
41030
  return [];
40705
41031
  }
40706
- var import_node_crypto16, OpenClawAdapter;
41032
+ var import_node_crypto15, OpenClawAdapter;
40707
41033
  var init_openclaw = __esm({
40708
41034
  "../adapters/src/openclaw/index.ts"() {
40709
41035
  "use strict";
40710
- import_node_crypto16 = require("node:crypto");
41036
+ import_node_crypto15 = require("node:crypto");
40711
41037
  init_subprocess();
40712
41038
  OpenClawAdapter = class {
40713
41039
  constructor(opts = {}) {
@@ -40725,7 +41051,7 @@ var init_openclaw = __esm({
40725
41051
  ---
40726
41052
 
40727
41053
  ${task}` : task;
40728
- const sessionId = job2.runtimeSessionId ?? `oasis-${(0, import_node_crypto16.randomUUID)().slice(0, 8)}`;
41054
+ const sessionId = job2.runtimeSessionId ?? `oasis-${(0, import_node_crypto15.randomUUID)().slice(0, 8)}`;
40729
41055
  return [
40730
41056
  "agent",
40731
41057
  ...opts.mode !== "gateway" ? ["--local"] : [],
@@ -40846,6 +41172,45 @@ if command -v hermes &>/dev/null; then
40846
41172
  "$HERMES_PIP" install 'agent-client-protocol>=0.9.0,<1.0'
40847
41173
  fi
40848
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
40849
41214
  fi
40850
41215
 
40851
41216
  OASIS_DIR="$(pwd)"
@@ -41197,7 +41562,7 @@ function nodesDomain(deps) {
41197
41562
  nodeId = incomingNodeId;
41198
41563
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
41199
41564
  } else {
41200
- nodeId = `node-${(0, import_node_crypto17.randomUUID)().slice(0, 8)}`;
41565
+ nodeId = `node-${(0, import_node_crypto16.randomUUID)().slice(0, 8)}`;
41201
41566
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
41202
41567
  }
41203
41568
  const existingNode = await deps.nodeStore.getNode(nodeId);
@@ -41337,11 +41702,11 @@ function nodesDomain(deps) {
41337
41702
  });
41338
41703
  };
41339
41704
  }
41340
- var import_node_crypto17, import_node_fs3, import_node_url, import_node_path2;
41705
+ var import_node_crypto16, import_node_fs3, import_node_url, import_node_path2;
41341
41706
  var init_routes4 = __esm({
41342
41707
  "../server/src/domains/nodes/routes.ts"() {
41343
41708
  "use strict";
41344
- import_node_crypto17 = require("node:crypto");
41709
+ import_node_crypto16 = require("node:crypto");
41345
41710
  import_node_fs3 = require("node:fs");
41346
41711
  import_node_url = require("node:url");
41347
41712
  import_node_path2 = require("node:path");
@@ -41403,11 +41768,11 @@ var init_types3 = __esm({
41403
41768
  });
41404
41769
 
41405
41770
  // ../server/src/node-store.ts
41406
- var fs12, MemoryNodeStore, FileNodeStore;
41771
+ var fs13, MemoryNodeStore, FileNodeStore;
41407
41772
  var init_node_store = __esm({
41408
41773
  "../server/src/node-store.ts"() {
41409
41774
  "use strict";
41410
- fs12 = __toESM(require("node:fs"), 1);
41775
+ fs13 = __toESM(require("node:fs"), 1);
41411
41776
  MemoryNodeStore = class {
41412
41777
  nodes = /* @__PURE__ */ new Map();
41413
41778
  runtimes = /* @__PURE__ */ new Map();
@@ -41492,16 +41857,16 @@ var init_node_store = __esm({
41492
41857
  static async open(file) {
41493
41858
  const s2 = new _FileNodeStore(file);
41494
41859
  try {
41495
- const snap = JSON.parse(fs12.readFileSync(file, "utf8"));
41860
+ const snap = JSON.parse(fs13.readFileSync(file, "utf8"));
41496
41861
  for (const n of snap.nodes ?? []) s2.nodes.set(n.id, n);
41497
41862
  for (const r of snap.runtimes ?? []) s2.runtimes.set(r.id, r);
41498
41863
  } catch {
41499
- fs12.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
41864
+ fs13.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
41500
41865
  }
41501
41866
  return s2;
41502
41867
  }
41503
41868
  flush() {
41504
- fs12.writeFileSync(this.file, JSON.stringify({
41869
+ fs13.writeFileSync(this.file, JSON.stringify({
41505
41870
  nodes: [...this.nodes.values()],
41506
41871
  runtimes: [...this.runtimes.values()]
41507
41872
  }, null, 2));
@@ -41553,8 +41918,18 @@ var init_node_store = __esm({
41553
41918
  });
41554
41919
 
41555
41920
  // ../server/src/domains/collab/inbox.ts
41556
- function buildInbox(model, me, leadOf) {
41921
+ function buildInbox(model, me, leadOf, resolveActor) {
41557
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
+ };
41558
41933
  const creatorOf = /* @__PURE__ */ new Map();
41559
41934
  for (const a of model.artifacts.values()) {
41560
41935
  if (a.type === "brief") creatorOf.set(a.workspace, a.owner);
@@ -41575,48 +41950,61 @@ function buildInbox(model, me, leadOf) {
41575
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`
41576
41951
  });
41577
41952
  }
41578
- const gapEscalationSeen = /* @__PURE__ */ new Set();
41579
41953
  for (const a of model.artifacts.values()) {
41580
- const escs = unresolvedEscalationsOf(model, a.id);
41581
- if (escs.length === 0) continue;
41582
41954
  const isOwnerRecipient = creatorOf.get(a.workspace) === me;
41583
41955
  const isLeadRecipient = !!leadOf && leadOf(a.owner) === me;
41584
41956
  if (!isOwnerRecipient && !isLeadRecipient) continue;
41585
- const key = `${a.workspace}:${a.id}`;
41586
- if (gapEscalationSeen.has(key)) continue;
41587
- gapEscalationSeen.add(key);
41588
- const who = isOwnerRecipient ? "\uFF08\u4F60\u662F\u5DE5\u5355\u5F52\u5C5E\u4EBA\uFF09" : "\uFF08\u4F60\u662F\u5361\u4F4F\u8282\u70B9 owner \u7684\u4E0A\u7EA7\uFF09";
41589
- out.push({
41590
- kind: "gap-escalation",
41591
- artifactId: a.id,
41592
- workspace: a.workspace,
41593
- since: escs.at(-1).at,
41594
- actionRequired: true,
41595
- 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` : ""}`
41596
- });
41597
- }
41598
- for (const a of model.artifacts.values()) {
41599
41957
  const gaps = unresolvedGapsOf(model, a.id);
41600
- if (gaps.length === 0) continue;
41601
- const isOwnerRecipient = creatorOf.get(a.workspace) === me;
41602
- const isLeadRecipient = !!leadOf && leadOf(a.owner) === me;
41603
- if (!isOwnerRecipient && !isLeadRecipient) continue;
41604
- const key = `${a.workspace}:${a.id}`;
41605
- if (gapEscalationSeen.has(key)) continue;
41606
- gapEscalationSeen.add(key);
41958
+ const escalations = unresolvedEscalationsOf(model, a.id);
41607
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";
41608
- out.push({
41609
- kind: "gap-escalation",
41610
- artifactId: a.id,
41611
- workspace: a.workspace,
41612
- since: model.lastOpAt.get(a.id) ?? "",
41613
- actionRequired: true,
41614
- 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` : ""}`
41615
- });
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
+ }
41616
42000
  }
41617
42001
  for (const r of listPendingReviews(model)) {
41618
42002
  if (r.origin) continue;
41619
- if (!r.workspace || creatorOf.get(r.workspace) !== me) continue;
42003
+ if (!r.workspace) continue;
42004
+ const anchorOwner = model.artifacts.get(r.anchor)?.owner;
42005
+ const isOwnerRecipient = creatorOf.get(r.workspace) === me;
42006
+ const isLeadRecipient = !!anchorOwner && !!leadOf && leadOf(anchorOwner) === me;
42007
+ if (!isOwnerRecipient && !isLeadRecipient) continue;
41620
42008
  out.push({
41621
42009
  kind: "change-confirm",
41622
42010
  artifactId: r.anchor,
@@ -41716,7 +42104,10 @@ function buildInbox(model, me, leadOf) {
41716
42104
  if (gate) continue;
41717
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}` });
41718
42106
  }
41719
- 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];
41720
42111
  }
41721
42112
  var init_inbox = __esm({
41722
42113
  "../server/src/domains/collab/inbox.ts"() {
@@ -41776,7 +42167,9 @@ var init_audit = __esm({
41776
42167
  reopen: "\u91CD\u5F00",
41777
42168
  seal: "\u5C01\u5B58",
41778
42169
  link_input: "\u63A5\u4F9D\u8D56",
41779
- report_gap: "\u7559\u7F3A\u53E3"
42170
+ report_gap: "\u7559\u7F3A\u53E3",
42171
+ hold: "\u6682\u505C\u6D3E\u53D1",
42172
+ release: "\u89E3\u963B"
41780
42173
  };
41781
42174
  }
41782
42175
  });
@@ -41827,6 +42220,139 @@ var init_dashboard = __esm({
41827
42220
  }
41828
42221
  });
41829
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
+
41830
42356
  // ../server/src/domains/collab/index.ts
41831
42357
  async function resolveLead(registry2, me) {
41832
42358
  const rec = await registry2.getActor(me);
@@ -41904,6 +42430,34 @@ function collabDomain(opts) {
41904
42430
  return ctx;
41905
42431
  }
41906
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
+ });
41907
42461
  router.get("/api/workorders", async (req) => {
41908
42462
  const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
41909
42463
  const resolve5 = await buildResolver(registry2);
@@ -41971,8 +42525,13 @@ function collabDomain(opts) {
41971
42525
  });
41972
42526
  router.get("/api/inbox", async (req) => {
41973
42527
  const { kernel, registry: registry2 } = await resolveCtx(req.auth.companyId);
41974
- const leadOf = await buildLeadResolver(registry2);
41975
- 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) } };
41976
42535
  });
41977
42536
  router.get("/api/audit", async (req) => {
41978
42537
  const { oplog } = await resolveCtx(req.auth.companyId);
@@ -42033,19 +42592,21 @@ var init_collab = __esm({
42033
42592
  init_inbox();
42034
42593
  init_audit();
42035
42594
  init_dashboard();
42595
+ init_organization_usage();
42036
42596
  init_node_state();
42037
42597
  init_workorders();
42038
42598
  init_workorder_detail();
42039
42599
  init_inbox();
42040
42600
  init_audit();
42041
42601
  init_dashboard();
42602
+ init_organization_usage();
42042
42603
  init_planner();
42043
42604
  }
42044
42605
  });
42045
42606
 
42046
42607
  // ../server/src/domains/collab/create-seeded-workorder.ts
42047
42608
  function makeSeededWorkorderCreator(deps) {
42048
- const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto18.randomUUID)().slice(0, 8)}`);
42609
+ const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto17.randomUUID)().slice(0, 8)}`);
42049
42610
  return async (input) => {
42050
42611
  const workspace = genWorkspace();
42051
42612
  if (input.projectId && deps.artifactState) {
@@ -42082,11 +42643,11 @@ function makeSeededWorkorderCreator(deps) {
42082
42643
  return { workspace, rootArtifactId: briefId, spawned: planned.spawned };
42083
42644
  };
42084
42645
  }
42085
- var import_node_crypto18, enc2;
42646
+ var import_node_crypto17, enc2;
42086
42647
  var init_create_seeded_workorder = __esm({
42087
42648
  "../server/src/domains/collab/create-seeded-workorder.ts"() {
42088
42649
  "use strict";
42089
- import_node_crypto18 = require("node:crypto");
42650
+ import_node_crypto17 = require("node:crypto");
42090
42651
  init_planner();
42091
42652
  enc2 = (s2) => new TextEncoder().encode(s2);
42092
42653
  }
@@ -42559,6 +43120,17 @@ var init_service4 = __esm({
42559
43120
  const page = await this.store.listRuns(filter);
42560
43121
  return { ...page, items: page.items.map((run) => this.withDisplayCost(run)) };
42561
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
+ }
42562
43134
  /** 按维度查聚合用量(per-session/per-agent plan §4)。store 未实现聚合时返回空。
42563
43135
  * 读侧补全展示成本:adapter 未上报 cost 时按价格快照估算(与员工周统计同口径)。 */
42564
43136
  async queryUsageStats(filter) {
@@ -43164,7 +43736,7 @@ function createChatSessionsDomain(opts) {
43164
43736
  const runtimeId = await currentRuntimeId(body.aiActorId) ?? "unknown";
43165
43737
  const now = (/* @__PURE__ */ new Date()).toISOString();
43166
43738
  const session = {
43167
- id: (0, import_node_crypto19.randomUUID)(),
43739
+ id: (0, import_node_crypto18.randomUUID)(),
43168
43740
  humanActorId: req.auth.actor,
43169
43741
  aiActorId: body.aiActorId,
43170
43742
  runtimeId,
@@ -43215,7 +43787,7 @@ function createChatSessionsDomain(opts) {
43215
43787
  if (!body?.role || !body?.content) throw new ApiError(400, "BAD_REQUEST", "role and content required");
43216
43788
  if (body.role !== "user" && body.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
43217
43789
  const msg = await store.appendMessage({
43218
- id: (0, import_node_crypto19.randomUUID)(),
43790
+ id: (0, import_node_crypto18.randomUUID)(),
43219
43791
  sessionId: req.params.id,
43220
43792
  role: body.role,
43221
43793
  content: body.content,
@@ -43252,11 +43824,11 @@ function createChatSessionsDomain(opts) {
43252
43824
  });
43253
43825
  };
43254
43826
  }
43255
- var import_node_crypto19;
43827
+ var import_node_crypto18;
43256
43828
  var init_chat_sessions = __esm({
43257
43829
  "../server/src/domains/chat-sessions/index.ts"() {
43258
43830
  "use strict";
43259
- import_node_crypto19 = require("node:crypto");
43831
+ import_node_crypto18 = require("node:crypto");
43260
43832
  init_router();
43261
43833
  init_workorders();
43262
43834
  init_chat_parts();
@@ -44622,11 +45194,11 @@ function ackMsFromEnv(fallback) {
44622
45194
  const n = Number(v2);
44623
45195
  return Number.isFinite(n) && n >= 0 ? n : fallback;
44624
45196
  }
44625
- var import_node_crypto20, DaemonHubAdapter, BindingRouterAdapter;
45197
+ var import_node_crypto19, DaemonHubAdapter, BindingRouterAdapter;
44626
45198
  var init_daemon_adapter = __esm({
44627
45199
  "../server/src/daemon-adapter.ts"() {
44628
45200
  "use strict";
44629
- import_node_crypto20 = require("node:crypto");
45201
+ import_node_crypto19 = require("node:crypto");
44630
45202
  DaemonHubAdapter = class {
44631
45203
  constructor(hub, opts = {}) {
44632
45204
  this.hub = hub;
@@ -44713,7 +45285,7 @@ var init_daemon_adapter = __esm({
44713
45285
  async spawn(job) {
44714
45286
  const nodeId = job.binding?.nodeId;
44715
45287
  if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
44716
- const dispatchId = `dispatch:${(0, import_node_crypto20.randomUUID)()}`;
45288
+ const dispatchId = `dispatch:${(0, import_node_crypto19.randomUUID)()}`;
44717
45289
  const entry = {
44718
45290
  nodeId,
44719
45291
  exitCbs: [],
@@ -44857,13 +45429,13 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
44857
45429
  if (sha(body) === lastPushedHash) return { kind: "echo" };
44858
45430
  return { kind: "human-edit", content: body, updatedBy: page.updatedBy };
44859
45431
  }
44860
- var import_node_crypto21, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
45432
+ var import_node_crypto20, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
44861
45433
  var init_engine = __esm({
44862
45434
  "../server/src/mirror/engine.ts"() {
44863
45435
  "use strict";
44864
- import_node_crypto21 = require("node:crypto");
45436
+ import_node_crypto20 = require("node:crypto");
44865
45437
  init_src();
44866
- sha = (s2) => (0, import_node_crypto21.createHash)("sha256").update(s2, "utf8").digest("hex");
45438
+ sha = (s2) => (0, import_node_crypto20.createHash)("sha256").update(s2, "utf8").digest("hex");
44867
45439
  enc3 = (s2) => new TextEncoder().encode(s2);
44868
45440
  dec = (b2) => new TextDecoder().decode(b2);
44869
45441
  MirrorEngine = class {
@@ -44976,22 +45548,22 @@ var init_engine = __esm({
44976
45548
  });
44977
45549
 
44978
45550
  // ../server/src/mirror/fs-state.ts
44979
- var fs14, FsMirrorStateStore;
45551
+ var fs15, FsMirrorStateStore;
44980
45552
  var init_fs_state = __esm({
44981
45553
  "../server/src/mirror/fs-state.ts"() {
44982
45554
  "use strict";
44983
- fs14 = __toESM(require("node:fs"), 1);
45555
+ fs15 = __toESM(require("node:fs"), 1);
44984
45556
  FsMirrorStateStore = class {
44985
45557
  constructor(file) {
44986
45558
  this.file = file;
44987
- if (fs14.existsSync(file)) {
44988
- const obj = JSON.parse(fs14.readFileSync(file, "utf8"));
45559
+ if (fs15.existsSync(file)) {
45560
+ const obj = JSON.parse(fs15.readFileSync(file, "utf8"));
44989
45561
  for (const [k2, v2] of Object.entries(obj)) this.map.set(k2, v2);
44990
45562
  }
44991
45563
  }
44992
45564
  map = /* @__PURE__ */ new Map();
44993
45565
  flush() {
44994
- fs14.writeFileSync(this.file, JSON.stringify(Object.fromEntries(this.map), null, 2));
45566
+ fs15.writeFileSync(this.file, JSON.stringify(Object.fromEntries(this.map), null, 2));
44995
45567
  }
44996
45568
  async get(artifactId) {
44997
45569
  return this.map.get(artifactId) ?? null;
@@ -45008,13 +45580,13 @@ var init_fs_state = __esm({
45008
45580
  });
45009
45581
 
45010
45582
  // ../server/src/git/integrate.ts
45011
- var import_node_child_process8, fs15, path13, GitRemoteIntegrator;
45583
+ var import_node_child_process9, fs16, path14, GitRemoteIntegrator;
45012
45584
  var init_integrate = __esm({
45013
45585
  "../server/src/git/integrate.ts"() {
45014
45586
  "use strict";
45015
- import_node_child_process8 = require("node:child_process");
45016
- fs15 = __toESM(require("node:fs"), 1);
45017
- path13 = __toESM(require("node:path"), 1);
45587
+ import_node_child_process9 = require("node:child_process");
45588
+ fs16 = __toESM(require("node:fs"), 1);
45589
+ path14 = __toESM(require("node:path"), 1);
45018
45590
  GitRemoteIntegrator = class {
45019
45591
  constructor(remote, cloneDir, develop = "develop") {
45020
45592
  this.remote = remote;
@@ -45023,7 +45595,7 @@ var init_integrate = __esm({
45023
45595
  }
45024
45596
  env = { ...process.env, LC_ALL: "C", LANG: "C", GIT_TERMINAL_PROMPT: "0" };
45025
45597
  git(args) {
45026
- return (0, import_node_child_process8.execFileSync)("git", ["-C", this.cloneDir, ...args], {
45598
+ return (0, import_node_child_process9.execFileSync)("git", ["-C", this.cloneDir, ...args], {
45027
45599
  encoding: "utf8",
45028
45600
  env: this.env,
45029
45601
  maxBuffer: 256 * 1024 * 1024
@@ -45031,9 +45603,9 @@ var init_integrate = __esm({
45031
45603
  }
45032
45604
  /** 确保本地工作克隆存在并拉到最新远程态。幂等。 */
45033
45605
  sync() {
45034
- if (!fs15.existsSync(path13.join(this.cloneDir, ".git"))) {
45035
- fs15.mkdirSync(path13.dirname(this.cloneDir), { recursive: true });
45036
- (0, import_node_child_process8.execFileSync)("git", ["clone", "--quiet", this.remote, this.cloneDir], { env: this.env, maxBuffer: 256 * 1024 * 1024 });
45606
+ if (!fs16.existsSync(path14.join(this.cloneDir, ".git"))) {
45607
+ fs16.mkdirSync(path14.dirname(this.cloneDir), { recursive: true });
45608
+ (0, import_node_child_process9.execFileSync)("git", ["clone", "--quiet", this.remote, this.cloneDir], { env: this.env, maxBuffer: 256 * 1024 * 1024 });
45037
45609
  this.git(["config", "user.email", "oasis@local"]);
45038
45610
  this.git(["config", "user.name", "oasis"]);
45039
45611
  } else {
@@ -45083,224 +45655,26 @@ var init_integrate = __esm({
45083
45655
  });
45084
45656
 
45085
45657
  // ../server/src/git/sync.ts
45086
- var os7, path14, slug3, GitSyncWorker;
45087
45658
  var init_sync = __esm({
45088
45659
  "../server/src/git/sync.ts"() {
45089
45660
  "use strict";
45090
- os7 = __toESM(require("node:os"), 1);
45091
- path14 = __toESM(require("node:path"), 1);
45092
- init_src2();
45093
- init_integrate();
45094
- init_collect();
45095
- slug3 = (id) => id.replace(/[^a-zA-Z0-9_-]+/g, "_");
45096
- GitSyncWorker = class {
45097
- // 仓库键(remote||id) → 集成器(缓存)
45098
- constructor(deps) {
45099
- this.deps = deps;
45100
- }
45101
- integrators = /* @__PURE__ */ new Map();
45102
- integratorFor(repo) {
45103
- if (!repo.remote) return null;
45104
- const key = repo.remote;
45105
- let g2 = this.integrators.get(key);
45106
- if (!g2) {
45107
- const root = this.deps.mirrorRoot ?? path14.join(os7.tmpdir(), "oasis-git-mirrors");
45108
- const cloneDir = repo.repoDir ?? path14.join(root, slug3(repo.id));
45109
- g2 = new GitRemoteIntegrator(repo.remote, cloneDir, repo.develop);
45110
- this.integrators.set(key, g2);
45111
- }
45112
- return g2;
45113
- }
45114
- /** 一轮:把每个已 conclude 的代码 artifact 的 commit **逐仓**集成进各自 develop。幂等(git ancestor 判断)。 */
45115
- async tick() {
45116
- const { kernel, log: log3 } = this.deps;
45117
- const synced = /* @__PURE__ */ new Set();
45118
- for (const artifact of kernel.model.artifacts.values()) {
45119
- const p2 = await this.deps.resolveProject(artifact.workspace);
45120
- if (!p2 || !p2.repos?.length) continue;
45121
- if (!isConcluded(kernel.model, artifact.id)) continue;
45122
- const head = artifact.currentRev;
45123
- if (!head) continue;
45124
- const rev = kernel.model.revisions.get(head);
45125
- if (!rev || rev.contentKind !== "external-pin") continue;
45126
- for (const { repo, sha: sha2 } of parseUmbrellaCommits(rev.contentRef, p2.repos)) {
45127
- const integ = this.integratorFor(repo);
45128
- if (!integ) continue;
45129
- const tag = `${artifact.id}/${repo.id}@${sha2.slice(0, 12)}`;
45130
- try {
45131
- if (!synced.has(repo.remote)) {
45132
- integ.sync();
45133
- synced.add(repo.remote);
45134
- }
45135
- const r = integ.integrate(sha2, `integrate ${tag} \u2192 ${repo.develop}`);
45136
- if (r.merged) log3?.(`[git] merged ${tag} \u2192 ${repo.develop}`);
45137
- else if (r.conflicts?.length) log3?.(`[git] ${tag} \u96C6\u6210\u51B2\u7A81: ${r.conflicts.join(", ")}\uFF08\u5F85\u56DE\u704C blocking annotation + rebase\uFF09`);
45138
- } catch (e) {
45139
- log3?.(`[git] ${tag} \u96C6\u6210\u5931\u8D25\uFF08\u4E0B\u4E00\u8F6E\u91CD\u8BD5\uFF09: ${String(e)}`);
45140
- }
45141
- }
45142
- }
45143
- }
45144
- };
45145
- }
45146
- });
45147
-
45148
- // ../server/src/git/ci-provider.ts
45149
- function mirrorDirFor(mirrorRoot, repo) {
45150
- if (repo.repoDir) return repo.repoDir;
45151
- const key = repo.remote ? `${slug4(repo.id)}-${crypto.createHash("sha1").update(repo.remote).digest("hex").slice(0, 8)}` : slug4(repo.id);
45152
- return `${mirrorRoot}/${key}`;
45153
- }
45154
- var import_node_child_process9, crypto, fs16, path15, import_node_util, execFile, slug4, GhCiProvider;
45155
- var init_ci_provider = __esm({
45156
- "../server/src/git/ci-provider.ts"() {
45157
- "use strict";
45158
- import_node_child_process9 = require("node:child_process");
45159
- crypto = __toESM(require("node:crypto"), 1);
45160
- fs16 = __toESM(require("node:fs"), 1);
45161
- path15 = __toESM(require("node:path"), 1);
45162
- import_node_util = require("node:util");
45163
- execFile = (0, import_node_util.promisify)(import_node_child_process9.execFile);
45164
- slug4 = (id) => id.replace(/[^a-zA-Z0-9_-]+/g, "_");
45165
- GhCiProvider = class {
45166
- constructor(opts) {
45167
- this.opts = opts;
45168
- }
45169
- repoDir(repo) {
45170
- return mirrorDirFor(this.opts.mirrorRoot, repo);
45171
- }
45172
- /** 缺则 clone(首次使用/进程重启后本地镜像不存在)——否则 git fetch 会"not a git repo"报错。同 GitRemoteIntegrator。 */
45173
- ensureClone(repo) {
45174
- const dir = this.repoDir(repo);
45175
- if (fs16.existsSync(path15.join(dir, ".git"))) {
45176
- if (repo.remote) {
45177
- let origin = "";
45178
- try {
45179
- origin = (0, import_node_child_process9.execFileSync)("git", ["remote", "get-url", "origin"], { cwd: dir, env: process.env }).toString().trim();
45180
- } catch {
45181
- }
45182
- if (origin !== repo.remote) {
45183
- (0, import_node_child_process9.execFileSync)("git", ["remote", "set-url", "origin", repo.remote], { cwd: dir, env: process.env });
45184
- this.opts.log?.(`[ci] ${repo.id} \u955C\u50CF origin \u7EA0\u504F\uFF1A${origin || "(\u65E0)"} \u2192 ${repo.remote}`);
45185
- }
45186
- }
45187
- return;
45188
- }
45189
- if (!repo.remote) throw new Error(`repo ${repo.id} \u65E0 remote\uFF0C\u65E0\u6CD5 clone`);
45190
- fs16.mkdirSync(path15.dirname(dir), { recursive: true });
45191
- (0, import_node_child_process9.execFileSync)("git", ["clone", "--quiet", repo.remote, dir], { env: process.env });
45192
- this.opts.log?.(`[ci] cloned ${repo.id} \u2192 ${dir}`);
45193
- }
45194
- async gh(repo, args) {
45195
- this.ensureClone(repo);
45196
- const { stdout } = await execFile("gh", args, { cwd: this.repoDir(repo), env: process.env });
45197
- return stdout.trim();
45198
- }
45199
- async git(repo, args) {
45200
- this.ensureClone(repo);
45201
- const { stdout } = await execFile("git", args, { cwd: this.repoDir(repo), env: process.env });
45202
- return stdout.trim();
45203
- }
45204
- async ensurePr(ref, meta) {
45205
- if (!ref.repo.remote) throw new Error(`repo ${ref.repo.id} \u65E0 remote\uFF0C\u65E0\u6CD5\u5F00 PR`);
45206
- await this.git(ref.repo, ["fetch", "--quiet", "--prune", "origin"]);
45207
- await this.git(ref.repo, ["push", "--quiet", "--force-with-lease", "origin", `${ref.sha}:refs/heads/${ref.branch}`]);
45208
- const existing = await this.gh(ref.repo, ["pr", "list", "--head", ref.branch, "--base", ref.repo.develop, "--state", "open", "--json", "number,url"]);
45209
- const list = JSON.parse(existing || "[]");
45210
- if (list[0]) return list[0];
45211
- const url = await this.gh(ref.repo, ["pr", "create", "--base", ref.repo.develop, "--head", ref.branch, "--title", meta.title, "--body", meta.body]);
45212
- const created = JSON.parse(await this.gh(ref.repo, ["pr", "view", ref.branch, "--json", "number,url"]));
45213
- this.opts.log?.(`[ci] opened PR ${created.url} (${ref.branch} \u2192 ${ref.repo.develop})`);
45214
- return created.url ? created : { number: created.number, url };
45215
- }
45216
- async status(ref) {
45217
- let raw;
45218
- try {
45219
- raw = await this.gh(ref.repo, ["pr", "view", ref.branch, "--json", "statusCheckRollup,createdAt"]);
45220
- } catch (e) {
45221
- return { state: "pending", summary: `\u8BFB PR \u72B6\u6001\u5931\u8D25\uFF08\u4E0B\u8F6E\u91CD\u8BD5\uFF09\uFF1A${String(e)}` };
45222
- }
45223
- const parsed = JSON.parse(raw || "{}");
45224
- const rollup = parsed.statusCheckRollup ?? [];
45225
- if (rollup.length === 0) {
45226
- const grace = this.opts.noCheckGraceMs ?? 12e4;
45227
- const ageMs = parsed.createdAt ? Date.now() - Date.parse(parsed.createdAt) : Infinity;
45228
- if (ageMs < grace) return { state: "pending" };
45229
- 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" };
45230
- 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`);
45231
- return { state: "success", summary: "\u65E0 CI check\uFF0C\u95F8\u7A7A\u8FC7" };
45232
- }
45233
- const failed = [];
45234
- let anyPending = false;
45235
- for (const c of rollup) {
45236
- const concl = (c.conclusion ?? "").toUpperCase();
45237
- const st = (c.status ?? "").toUpperCase();
45238
- if (st && st !== "COMPLETED") {
45239
- anyPending = true;
45240
- continue;
45241
- }
45242
- if (concl && !["SUCCESS", "NEUTRAL", "SKIPPED"].includes(concl)) failed.push(c);
45243
- }
45244
- if (failed.length > 0) {
45245
- const blocks = [];
45246
- for (const c of failed) {
45247
- const name = c.name ?? c.context ?? "check";
45248
- const concl = (c.conclusion ?? "").toUpperCase();
45249
- const url = c.detailsUrl ?? c.targetUrl ?? "";
45250
- let block = ` - ${name} \u2192 ${concl}${url ? `
45251
- ${url}` : ""}`;
45252
- const tail = await this.failedLogTail(ref.repo, url);
45253
- if (tail) block += `
45254
- \u5931\u8D25\u65E5\u5FD7\uFF08\u672B\u5C3E\uFF09\uFF1A
45255
- ${tail.split("\n").map((l) => " " + l).join("\n")}`;
45256
- blocks.push(block);
45257
- }
45258
- return { state: "failure", summary: `CI \u672A\u8FC7\uFF08${failed.length} \u4E2A check\uFF09\uFF1A
45259
- ${blocks.join("\n")}` };
45260
- }
45261
- if (anyPending) return { state: "pending" };
45262
- return { state: "success", summary: `CI \u901A\u8FC7\uFF08${rollup.length} \u4E2A check\uFF09` };
45263
- }
45264
- /** best-effort:从 check 的 detailsUrl 提 Actions run id,取失败步骤日志末尾(截断)。取不到返回空。 */
45265
- async failedLogTail(repo, detailsUrl) {
45266
- const m2 = /\/runs\/(\d+)/.exec(detailsUrl);
45267
- if (!m2) return "";
45268
- try {
45269
- const log3 = await this.gh(repo, ["run", "view", m2[1], "--log-failed"]);
45270
- return log3.split("\n").filter(Boolean).slice(-40).join("\n").slice(-2e3);
45271
- } catch {
45272
- return "";
45273
- }
45274
- }
45275
- async merge(ref) {
45276
- try {
45277
- await this.gh(ref.repo, ["pr", "merge", ref.branch, "--merge", "--delete-branch"]);
45278
- this.opts.log?.(`[ci] merged PR ${ref.branch} \u2192 ${ref.repo.develop}`);
45279
- return { merged: true };
45280
- } catch (e) {
45281
- return { merged: false, reason: String(e) };
45282
- }
45283
- }
45284
- };
45285
45661
  }
45286
45662
  });
45287
45663
 
45288
45664
  // ../server/src/git/ci-gate.ts
45289
- var slug5, CiGateWorker;
45665
+ var CiGateWorker;
45290
45666
  var init_ci_gate = __esm({
45291
45667
  "../server/src/git/ci-gate.ts"() {
45292
45668
  "use strict";
45293
45669
  init_src2();
45294
45670
  init_collect();
45295
- slug5 = (id) => id.replace(/[^a-zA-Z0-9_-]+/g, "_");
45671
+ init_ci_provider();
45296
45672
  CiGateWorker = class {
45297
45673
  constructor(deps) {
45298
45674
  this.deps = deps;
45299
45675
  this.ciActor = deps.ciActor ?? "actor:ci";
45300
45676
  }
45301
45677
  ciActor;
45302
- /** 进程内"已尝试合并"去重(避免每轮重复 gh pr merge;重启后至多多试一次,gh 报已合并即无害)。 */
45303
- mergedOnce = /* @__PURE__ */ new Set();
45304
45678
  /** 逐产物失败退避:连续失败指数拉长重试间隔,超过阈值响亮升级一次(不再静默每轮刷日志)。 */
45305
45679
  failures = /* @__PURE__ */ new Map();
45306
45680
  inBackoff(artifactId) {
@@ -45336,9 +45710,8 @@ var init_ci_gate = __esm({
45336
45710
  if (!p2 || !p2.repos?.length) return null;
45337
45711
  const units = [];
45338
45712
  for (const { repo, sha: sha2 } of parseUmbrellaCommits(contentRef, p2.repos)) {
45339
- if (!repo.remote) continue;
45340
- const artifactSlug2 = slug5(artifactId.replace(/^artifact:/, "")).slice(0, 48);
45341
- units.push({ repo, sha: sha2, branch: `oasis-ci/${artifactSlug2}/${sha2.slice(0, 8)}` });
45713
+ if (!isGitHubRemote(repo.remote)) continue;
45714
+ units.push({ repo, sha: sha2, branch: ciBranchFor(artifactId, sha2) });
45342
45715
  }
45343
45716
  return units;
45344
45717
  }
@@ -45386,14 +45759,6 @@ var init_ci_gate = __esm({
45386
45759
  log3?.(`[ci] vote ${artifact.id} \u5931\u8D25\uFF08\u9000\u907F\u91CD\u8BD5\uFF09\uFF1A${String(e)}`);
45387
45760
  }
45388
45761
  );
45389
- } else if (isConcluded(kernel.model, artifact.id) && rev.contentKind === "external-pin") {
45390
- await this.mergeMilestone(artifact.id, artifact.workspace, rev.contentRef, head).then(
45391
- () => this.recordOk(artifact.id),
45392
- (e) => {
45393
- this.recordFail(artifact.id);
45394
- log3?.(`[ci] merge ${artifact.id} \u5931\u8D25\uFF08\u9000\u907F\u91CD\u8BD5\uFF09\uFF1A${String(e)}`);
45395
- }
45396
- );
45397
45762
  } else {
45398
45763
  this.clearStatus(artifact.id);
45399
45764
  }
@@ -45430,32 +45795,122 @@ var init_ci_gate = __esm({
45430
45795
  this.clearStatus(artifactId);
45431
45796
  log3?.(`[ci] ${artifactId} CI \u5168\u7EFF \u2192 approve`);
45432
45797
  }
45433
- async mergeMilestone(artifactId, workspace, contentRef, head) {
45434
- const { ci, log: log3 } = this.deps;
45435
- const units = await this.unitsFor(artifactId, workspace, contentRef);
45436
- if (!units) {
45437
- this.clearStatus(artifactId);
45438
- return;
45798
+ };
45799
+ }
45800
+ });
45801
+
45802
+ // ../server/src/git/merge-worker.ts
45803
+ var MergeWorker;
45804
+ var init_merge_worker = __esm({
45805
+ "../server/src/git/merge-worker.ts"() {
45806
+ "use strict";
45807
+ init_src2();
45808
+ init_collect();
45809
+ init_integrate();
45810
+ init_ci_provider();
45811
+ MergeWorker = class {
45812
+ constructor(deps) {
45813
+ this.deps = deps;
45814
+ }
45815
+ integrators = /* @__PURE__ */ new Map();
45816
+ mergedOnce = /* @__PURE__ */ new Set();
45817
+ failures = /* @__PURE__ */ new Map();
45818
+ inBackoff(id) {
45819
+ const f2 = this.failures.get(id);
45820
+ return !!f2 && Date.now() < f2.nextAt;
45821
+ }
45822
+ recordFail(id) {
45823
+ const base = this.deps.backoff?.baseMs ?? 3e4;
45824
+ const cap = this.deps.backoff?.capMs ?? 6e5;
45825
+ const escalateAfter = this.deps.backoff?.escalateAfter ?? 5;
45826
+ const f2 = this.failures.get(id) ?? { count: 0, nextAt: 0, escalated: false };
45827
+ f2.count += 1;
45828
+ f2.nextAt = Date.now() + Math.min(base * 2 ** (f2.count - 1), cap);
45829
+ if (f2.count >= escalateAfter && !f2.escalated) {
45830
+ f2.escalated = true;
45831
+ this.deps.log?.(`[merge] \u26A0\uFE0F ${id} \u8FDE\u7EED\u5931\u8D25 ${f2.count} \u6B21\uFF0C\u5DF2\u8FDB\u5165\u957F\u9000\u907F\uFF08\u4E0A\u9650 ${Math.round(cap / 1e3)}s\uFF09\u2014\u2014\u8BF7\u4EBA\u5DE5\u68C0\u67E5\u8FDC\u7AEF\u4ED3 / \u5206\u652F\u4FDD\u62A4\u72B6\u6001`);
45439
45832
  }
45440
- const pending = units.filter((u) => !this.mergedOnce.has(`${artifactId}::${head}::${u.repo.id}`));
45441
- if (pending.length === 0) {
45442
- this.clearStatus(artifactId);
45443
- return;
45833
+ this.failures.set(id, f2);
45834
+ }
45835
+ recordOk(id) {
45836
+ this.failures.delete(id);
45837
+ }
45838
+ setStatus(id, s2) {
45839
+ this.deps.statusSink?.set(id, { ...s2, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
45840
+ }
45841
+ clearStatus(id) {
45842
+ this.deps.statusSink?.delete(id);
45843
+ }
45844
+ /** 非 GitHub 仓的本地集成器(按归一化 remote 缓存;克隆目录与 CI 闸同套 mirrorDirFor,逻辑 id 重名不撞)。 */
45845
+ integratorFor(repo) {
45846
+ if (!repo.remote) return null;
45847
+ const key = normalizeRemote(repo.remote);
45848
+ let g2 = this.integrators.get(key);
45849
+ if (!g2) {
45850
+ g2 = new GitRemoteIntegrator(repo.remote, mirrorDirFor(this.deps.mirrorRoot, repo), repo.develop);
45851
+ this.integrators.set(key, g2);
45444
45852
  }
45445
- this.setStatus(artifactId, { phase: "merging", repos: pending.map((u) => ({ repoId: u.repo.id, sha: u.sha.slice(0, 8), state: "success" })) });
45446
- for (const unit of units) {
45447
- const key = `${artifactId}::${head}::${unit.repo.id}`;
45448
- if (this.mergedOnce.has(key)) continue;
45449
- const r = await ci.merge({ repo: unit.repo, branch: unit.branch });
45450
- if (r.merged) {
45451
- this.mergedOnce.add(key);
45452
- log3?.(`[ci] merged ${artifactId}/${unit.repo.id} \u2192 ${unit.repo.develop}`);
45453
- } else {
45454
- this.mergedOnce.add(key);
45455
- log3?.(`[ci] ${artifactId}/${unit.repo.id} \u672A\u5408\uFF08${r.reason ?? "not mergeable"}\uFF09\u2014\u2014\u82E5\u5DF2\u5408\u5E76\u53EF\u5FFD\u7565`);
45853
+ return g2;
45854
+ }
45855
+ async tick() {
45856
+ const { kernel, log: log3 } = this.deps;
45857
+ for (const artifact of kernel.model.artifacts.values()) {
45858
+ const head = artifact.currentRev;
45859
+ if (!head) continue;
45860
+ const rev = kernel.model.revisions.get(head);
45861
+ if (!rev || rev.contentKind !== "external-pin") continue;
45862
+ if (!isConcluded(kernel.model, artifact.id)) continue;
45863
+ if (this.inBackoff(artifact.id)) continue;
45864
+ const p2 = await this.deps.resolveProject(artifact.workspace);
45865
+ if (!p2 || !p2.repos?.length) {
45866
+ this.clearStatus(artifact.id);
45867
+ continue;
45868
+ }
45869
+ const units = parseUmbrellaCommits(rev.contentRef, p2.repos).filter((u) => u.repo.remote);
45870
+ const keyOf = (u) => `${artifact.id}::${head}::${normalizeRemote(u.repo.remote)}`;
45871
+ const pending = units.filter((u) => !this.mergedOnce.has(keyOf(u)));
45872
+ if (pending.length === 0) {
45873
+ this.clearStatus(artifact.id);
45874
+ continue;
45875
+ }
45876
+ this.setStatus(artifact.id, { phase: "merging", repos: pending.map((u) => ({ repoId: u.repo.id, sha: u.sha.slice(0, 8), state: "success" })) });
45877
+ try {
45878
+ for (const { repo, sha: sha2 } of pending) {
45879
+ if (isGitHubRemote(repo.remote)) {
45880
+ if (await this.deps.ci.isMerged({ repo, sha: sha2 })) {
45881
+ this.mergedOnce.add(keyOf({ repo }));
45882
+ continue;
45883
+ }
45884
+ const branch = ciBranchFor(artifact.id, sha2);
45885
+ await this.deps.ci.ensurePr(
45886
+ { repo, branch, sha: sha2 },
45887
+ { title: `[oasis-ci] ${artifact.id} \u2192 ${repo.develop}`, body: `merge artifact ${artifact.id} @ ${sha2}` }
45888
+ );
45889
+ const r = await this.deps.ci.merge({ repo, branch });
45890
+ this.mergedOnce.add(keyOf({ repo }));
45891
+ log3?.(r.merged ? `[merge] merged ${artifact.id}/${repo.id} \u2192 ${repo.develop}\uFF08PR\uFF09` : `[merge] ${artifact.id}/${repo.id} \u672A\u5408\uFF08${r.reason ?? "not mergeable"}\uFF09\u2014\u2014\u82E5\u5DF2\u5408\u53EF\u5FFD\u7565`);
45892
+ } else {
45893
+ const integ = this.integratorFor(repo);
45894
+ if (!integ) continue;
45895
+ integ.sync();
45896
+ const r = integ.integrate(sha2, `integrate ${artifact.id}/${repo.id}@${sha2.slice(0, 12)} \u2192 ${repo.develop}`);
45897
+ if (r.merged || r.already) {
45898
+ this.mergedOnce.add(keyOf({ repo }));
45899
+ if (r.merged) log3?.(`[merge] merged ${artifact.id}/${repo.id} \u2192 ${repo.develop}\uFF08\u672C\u5730\uFF09`);
45900
+ } else if (r.conflicts?.length) {
45901
+ log3?.(`[merge] ${artifact.id}/${repo.id} \u96C6\u6210\u51B2\u7A81: ${r.conflicts.join(", ")}\uFF08\u5F85\u56DE\u704C blocking annotation + rebase\uFF09`);
45902
+ }
45903
+ }
45904
+ }
45905
+ this.recordOk(artifact.id);
45906
+ this.clearStatus(artifact.id);
45907
+ } catch (e) {
45908
+ this.recordFail(artifact.id);
45909
+ const f2 = this.failures.get(artifact.id);
45910
+ this.setStatus(artifact.id, { phase: "stuck", repos: [], reason: String(e).slice(0, 500), ...f2 ? { retryAt: new Date(f2.nextAt).toISOString() } : {} });
45911
+ log3?.(`[merge] ${artifact.id} \u5408\u5E76\u5931\u8D25\uFF08\u9000\u907F\u91CD\u8BD5\uFF09\uFF1A${String(e)}`);
45456
45912
  }
45457
45913
  }
45458
- this.clearStatus(artifactId);
45459
45914
  }
45460
45915
  };
45461
45916
  }
@@ -45492,11 +45947,11 @@ var init_resolve = __esm({
45492
45947
  });
45493
45948
 
45494
45949
  // ../server/src/coordinator/worker.ts
45495
- var import_node_crypto22, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_MANAGER_BODY, CoordinatorWorker;
45950
+ var import_node_crypto21, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_MANAGER_BODY, CoordinatorWorker;
45496
45951
  var init_worker = __esm({
45497
45952
  "../server/src/coordinator/worker.ts"() {
45498
45953
  "use strict";
45499
- import_node_crypto22 = require("node:crypto");
45954
+ import_node_crypto21 = require("node:crypto");
45500
45955
  init_src2();
45501
45956
  init_identity();
45502
45957
  AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
@@ -45509,7 +45964,7 @@ var init_worker = __esm({
45509
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
45510
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
45511
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
45512
- \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`;
45513
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
45514
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
45515
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
@@ -45536,7 +45991,7 @@ var init_worker = __esm({
45536
45991
 
45537
45992
  \u3010\u6536\u5C3E gap / \u4E0A\u62A5\u5361\u70B9\u3011\uFF08\u4E0D\u8D70\u6682\u5B58\u7F13\u51B2\uFF0C\u76F4\u63A5\u751F\u6548\uFF09
45538
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
45539
- - \`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
45540
45995
 
45541
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
45542
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
@@ -45672,7 +46127,7 @@ ${SHARED_GRAPH_BODY}`;
45672
46127
  return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
45673
46128
  }
45674
46129
  evaluationKey(kind, artifactId, evidence) {
45675
- const fingerprint = (0, import_node_crypto22.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
46130
+ const fingerprint = (0, import_node_crypto21.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
45676
46131
  return `${kind}:${artifactId}:${fingerprint}`;
45677
46132
  }
45678
46133
  artifactEvidence(id) {
@@ -45702,7 +46157,7 @@ ${SHARED_GRAPH_BODY}`;
45702
46157
  const out = [];
45703
46158
  for (const artifact of this.deps.kernel.model.artifacts.values()) {
45704
46159
  for (const g2 of unresolvedGapsOf(this.deps.kernel.model, artifact.id)) {
45705
- 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 });
45706
46161
  }
45707
46162
  }
45708
46163
  return out;
@@ -45737,6 +46192,10 @@ ${SHARED_GRAPH_BODY}`;
45737
46192
  }
45738
46193
  return lines.length > 0 ? lines.join("\n") : "\uFF08\u56FE\u91CC\u6682\u65E0\u5176\u5B83\u4EA7\u7269\uFF09";
45739
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
+ }
45740
46199
  async reconcile(gap) {
45741
46200
  const gapper = this.deps.kernel.model.artifacts.get(gap.artifactId);
45742
46201
  const brief = gapper?.title ?? gapper?.description ?? "";
@@ -45749,7 +46208,7 @@ ${SHARED_GRAPH_BODY}`;
45749
46208
  `## \u8981\u4F60\u505A\u4EC0\u4E48\uFF08\u5148\u5206\u8BCA\uFF09`,
45750
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`,
45751
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`,
45752
- `- **\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`,
45753
46212
  ``,
45754
46213
  `## \u73B0\u6709\u4EA7\u7269\uFF08id / type / \u662F\u5426\u5DF2\u4EA4\u4ED8 / brief / \u4F9D\u8D56\uFF09`,
45755
46214
  this.structDigest(gap.artifactId),
@@ -45763,11 +46222,11 @@ ${SHARED_GRAPH_BODY}`;
45763
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`,
45764
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`,
45765
46224
  `**B. \u8981\u4EBA**\uFF08\u51B3\u7B56/\u80FD\u529B\u95EE\u9898\uFF0C\u56FE\u4E0A\u4FEE\u4E0D\u4E86\uFF09\uFF1A`,
45766
- `- \`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`,
45767
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`,
45768
46227
  `**\u5176\u5B83**\uFF1A`,
45769
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`,
45770
- `- **\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**`
45771
46230
  ].join("\n");
45772
46231
  const subjectKey = `gap:${gap.gapId}`;
45773
46232
  const emptyCount = this.emptyHandedGap.get(subjectKey) ?? 0;
@@ -45777,7 +46236,7 @@ ${SHARED_GRAPH_BODY}`;
45777
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`,
45778
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`,
45779
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>"\``,
45780
- `- \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>")}\``,
45781
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**`
45782
46241
  ].join("\n");
45783
46242
  const resumeId = emptyCount > 0 ? this.emptyHandedSession.get(subjectKey) : void 0;
@@ -45800,7 +46259,9 @@ ${SHARED_GRAPH_BODY}`;
45800
46259
  await this.deps.kernel.escalate({
45801
46260
  artifactId: gap.artifactId,
45802
46261
  actor: run.managerActorId,
45803
- 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 } : {}
45804
46265
  }).catch((e) => this.deps.log?.(`[coordinator] ${gap.artifactId} \u81EA\u52A8 escalate \u5931\u8D25\uFF1A${String(e)}`));
45805
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`);
45806
46267
  this.emptyHandedGap.delete(subjectKey);
@@ -45912,7 +46373,7 @@ ${SHARED_GRAPH_BODY}`;
45912
46373
  const token = this.deps.tokenFor(actorId, ctx);
45913
46374
  const serverUrl = this.deps.serverUrlFor ? await this.deps.serverUrlFor(ctx) : this.deps.serverUrl;
45914
46375
  const workspace = this.deps.kernel.model.artifacts.get(artifactId)?.workspace;
45915
- const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto22.randomUUID)();
46376
+ const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto21.randomUUID)();
45916
46377
  const job = {
45917
46378
  actor: actorId,
45918
46379
  actorToken: token,
@@ -46008,22 +46469,22 @@ async function loadBuiltinSkills(skillsDir) {
46008
46469
  const skills = [];
46009
46470
  for (const d of dirents) {
46010
46471
  if (!d.isDirectory()) continue;
46011
- const slug6 = d.name;
46012
- const skillDir = (0, import_node_path4.join)(skillsDir, slug6);
46472
+ const slug4 = d.name;
46473
+ const skillDir = (0, import_node_path4.join)(skillsDir, slug4);
46013
46474
  const files = {};
46014
46475
  try {
46015
46476
  await collectDir(skillDir, skillDir, files);
46016
46477
  } catch (err) {
46017
- console.warn(`[builtin-skills] failed reading ${slug6}: ${String(err)}`);
46478
+ console.warn(`[builtin-skills] failed reading ${slug4}: ${String(err)}`);
46018
46479
  continue;
46019
46480
  }
46020
46481
  const skillMd = files["SKILL.md"];
46021
46482
  if (!skillMd) {
46022
- console.warn(`[builtin-skills] ${slug6} has no SKILL.md, skipping`);
46483
+ console.warn(`[builtin-skills] ${slug4} has no SKILL.md, skipping`);
46023
46484
  continue;
46024
46485
  }
46025
46486
  const { name, description } = parseSkillFrontmatter(skillMd);
46026
- skills.push({ id: slug6, name: name || slug6, description, files });
46487
+ skills.push({ id: slug4, name: name || slug4, description, files });
46027
46488
  }
46028
46489
  return skills;
46029
46490
  }
@@ -46105,6 +46566,7 @@ var init_src5 = __esm({
46105
46566
  init_sync();
46106
46567
  init_ci_provider();
46107
46568
  init_ci_gate();
46569
+ init_merge_worker();
46108
46570
  init_resolve();
46109
46571
  init_identity();
46110
46572
  init_worker();
@@ -49475,7 +49937,7 @@ var require_split2 = __commonJS({
49475
49937
  var require_helper = __commonJS({
49476
49938
  "../../node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/helper.js"(exports2, module2) {
49477
49939
  "use strict";
49478
- var path20 = require("path");
49940
+ var path19 = require("path");
49479
49941
  var Stream = require("stream").Stream;
49480
49942
  var split = require_split2();
49481
49943
  var util2 = require("util");
@@ -49514,7 +49976,7 @@ var require_helper = __commonJS({
49514
49976
  };
49515
49977
  module2.exports.getFileName = function(rawEnv) {
49516
49978
  var env = rawEnv || process.env;
49517
- var file = env.PGPASSFILE || (isWin ? path20.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path20.join(env.HOME || "./", ".pgpass"));
49979
+ var file = env.PGPASSFILE || (isWin ? path19.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path19.join(env.HOME || "./", ".pgpass"));
49518
49980
  return file;
49519
49981
  };
49520
49982
  module2.exports.usePgPass = function(stats, fname) {
@@ -49646,7 +50108,7 @@ var require_helper = __commonJS({
49646
50108
  var require_lib = __commonJS({
49647
50109
  "../../node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/index.js"(exports2, module2) {
49648
50110
  "use strict";
49649
- var path20 = require("path");
50111
+ var path19 = require("path");
49650
50112
  var fs21 = require("fs");
49651
50113
  var helper = require_helper();
49652
50114
  module2.exports = function(connInfo, cb) {
@@ -51208,33 +51670,33 @@ __export(trajectory_exports, {
51208
51670
  exportTrajectories: () => exportTrajectories
51209
51671
  });
51210
51672
  async function exportTrajectories(dataDir) {
51211
- const root = path16.join(dataDir, "trajectories");
51673
+ const root = path15.join(dataDir, "trajectories");
51212
51674
  if (!fs17.existsSync(root)) return [];
51213
51675
  const model = emptyReadModel();
51214
- const oplogFile = path16.join(dataDir, "oplog.ndjson");
51676
+ const oplogFile = path15.join(dataDir, "oplog.ndjson");
51215
51677
  if (fs17.existsSync(oplogFile)) {
51216
51678
  const oplog = await NdjsonOplogStore.open(oplogFile);
51217
51679
  for await (const entry of oplog.readAll()) fold(model, entry.op);
51218
51680
  }
51219
51681
  const samples = [];
51220
51682
  for (const sessionId of fs17.readdirSync(root).sort()) {
51221
- const dir = path16.join(root, sessionId);
51222
- const sessionFile = path16.join(dir, "session.json");
51683
+ const dir = path15.join(root, sessionId);
51684
+ const sessionFile = path15.join(dir, "session.json");
51223
51685
  if (!fs17.existsSync(sessionFile)) continue;
51224
51686
  const session = JSON.parse(fs17.readFileSync(sessionFile, "utf8"));
51225
51687
  const events = [];
51226
- const eventsFile = path16.join(dir, "events.ndjson");
51688
+ const eventsFile = path15.join(dir, "events.ndjson");
51227
51689
  if (fs17.existsSync(eventsFile)) {
51228
51690
  for (const line of fs17.readFileSync(eventsFile, "utf8").split("\n")) {
51229
51691
  if (line.trim()) events.push(JSON.parse(line));
51230
51692
  }
51231
51693
  }
51232
51694
  const bundle = {};
51233
- const bundleDir = path16.join(dir, "bundle");
51695
+ const bundleDir = path15.join(dir, "bundle");
51234
51696
  const walk = (sub) => {
51235
- for (const name of fs17.readdirSync(path16.join(bundleDir, sub))) {
51697
+ for (const name of fs17.readdirSync(path15.join(bundleDir, sub))) {
51236
51698
  const rel = sub ? `${sub}/${name}` : name;
51237
- const full = path16.join(bundleDir, rel);
51699
+ const full = path15.join(bundleDir, rel);
51238
51700
  if (fs17.statSync(full).isDirectory()) walk(rel);
51239
51701
  else bundle[rel] = fs17.readFileSync(full, "utf8");
51240
51702
  }
@@ -51251,47 +51713,47 @@ async function exportTrajectories(dataDir) {
51251
51713
  }
51252
51714
  return samples;
51253
51715
  }
51254
- var fs17, path16, FsTrajectorySink;
51716
+ var fs17, path15, FsTrajectorySink;
51255
51717
  var init_trajectory = __esm({
51256
51718
  "../cli/src/trajectory.ts"() {
51257
51719
  "use strict";
51258
51720
  fs17 = __toESM(require("node:fs"), 1);
51259
- path16 = __toESM(require("node:path"), 1);
51721
+ path15 = __toESM(require("node:path"), 1);
51260
51722
  init_src2();
51261
51723
  init_src5();
51262
51724
  FsTrajectorySink = class {
51263
51725
  constructor(dataDir, broadcast) {
51264
51726
  this.broadcast = broadcast;
51265
- this.root = path16.join(dataDir, "trajectories");
51727
+ this.root = path15.join(dataDir, "trajectories");
51266
51728
  }
51267
51729
  root;
51268
51730
  dir(sessionId) {
51269
- return path16.join(this.root, sessionId);
51731
+ return path15.join(this.root, sessionId);
51270
51732
  }
51271
51733
  begin(session, bundleFiles) {
51272
51734
  const dir = this.dir(session.sessionId);
51273
- fs17.mkdirSync(path16.join(dir, "bundle"), { recursive: true });
51274
- fs17.writeFileSync(path16.join(dir, "session.json"), JSON.stringify(session, null, 2));
51735
+ fs17.mkdirSync(path15.join(dir, "bundle"), { recursive: true });
51736
+ fs17.writeFileSync(path15.join(dir, "session.json"), JSON.stringify(session, null, 2));
51275
51737
  for (const [rel, content] of Object.entries(bundleFiles)) {
51276
- const file = path16.join(dir, "bundle", rel);
51277
- fs17.mkdirSync(path16.dirname(file), { recursive: true });
51738
+ const file = path15.join(dir, "bundle", rel);
51739
+ fs17.mkdirSync(path15.dirname(file), { recursive: true });
51278
51740
  fs17.writeFileSync(file, content);
51279
51741
  }
51280
51742
  this.broadcast?.({ type: "begin", sessionId: session.sessionId, data: session });
51281
51743
  }
51282
51744
  recover(session) {
51283
51745
  fs17.mkdirSync(this.dir(session.sessionId), { recursive: true });
51284
- const file = path16.join(this.dir(session.sessionId), "session.json");
51746
+ const file = path15.join(this.dir(session.sessionId), "session.json");
51285
51747
  if (!fs17.existsSync(file)) fs17.writeFileSync(file, JSON.stringify(session, null, 2));
51286
51748
  this.broadcast?.({ type: "begin", sessionId: session.sessionId, data: { ...session, recovered: true } });
51287
51749
  }
51288
51750
  event(sessionId, event) {
51289
51751
  fs17.mkdirSync(this.dir(sessionId), { recursive: true });
51290
- fs17.appendFileSync(path16.join(this.dir(sessionId), "events.ndjson"), JSON.stringify(event) + "\n");
51752
+ fs17.appendFileSync(path15.join(this.dir(sessionId), "events.ndjson"), JSON.stringify(event) + "\n");
51291
51753
  this.broadcast?.({ type: "event", sessionId, data: event });
51292
51754
  }
51293
51755
  end(sessionId, update) {
51294
- const file = path16.join(this.dir(sessionId), "session.json");
51756
+ const file = path15.join(this.dir(sessionId), "session.json");
51295
51757
  const session = JSON.parse(fs17.readFileSync(file, "utf8"));
51296
51758
  session.exit = update.exit;
51297
51759
  session.opRefs = update.opRefs;
@@ -51302,23 +51764,23 @@ var init_trajectory = __esm({
51302
51764
  * 路径逻辑复用写入侧 `this.dir()`(单一真相,随 --dir 迁移同步);带路径穿越防护。 */
51303
51765
  readBundleFile(sessionId, rel) {
51304
51766
  if (!sessionId || sessionId.includes("/") || sessionId.includes("..")) return null;
51305
- const bundleRoot = path16.join(this.dir(sessionId), "bundle");
51306
- const target = path16.resolve(bundleRoot, rel);
51307
- if (target !== bundleRoot && !target.startsWith(bundleRoot + path16.sep)) return null;
51767
+ const bundleRoot = path15.join(this.dir(sessionId), "bundle");
51768
+ const target = path15.resolve(bundleRoot, rel);
51769
+ if (target !== bundleRoot && !target.startsWith(bundleRoot + path15.sep)) return null;
51308
51770
  if (!fs17.existsSync(target) || !fs17.statSync(target).isFile()) return null;
51309
51771
  return fs17.readFileSync(target, "utf8");
51310
51772
  }
51311
51773
  /** 列出某会话 bundle 下所有文件的相对路径(供前端「本次输入」点开清单)。 */
51312
51774
  listBundle(sessionId) {
51313
51775
  if (!sessionId || sessionId.includes("/") || sessionId.includes("..")) return [];
51314
- const bundleRoot = path16.join(this.dir(sessionId), "bundle");
51776
+ const bundleRoot = path15.join(this.dir(sessionId), "bundle");
51315
51777
  if (!fs17.existsSync(bundleRoot)) return [];
51316
51778
  const out = [];
51317
51779
  const walk = (d) => {
51318
51780
  for (const e of fs17.readdirSync(d, { withFileTypes: true })) {
51319
- const full = path16.join(d, e.name);
51781
+ const full = path15.join(d, e.name);
51320
51782
  if (e.isDirectory()) walk(full);
51321
- else out.push(path16.relative(bundleRoot, full));
51783
+ else out.push(path15.relative(bundleRoot, full));
51322
51784
  }
51323
51785
  };
51324
51786
  walk(bundleRoot);
@@ -51330,21 +51792,21 @@ var init_trajectory = __esm({
51330
51792
 
51331
51793
  // src/index.ts
51332
51794
  var fs20 = __toESM(require("node:fs"));
51333
- var os10 = __toESM(require("node:os"));
51334
- var path19 = __toESM(require("node:path"));
51795
+ var os9 = __toESM(require("node:os"));
51796
+ var path18 = __toESM(require("node:path"));
51335
51797
  var import_node_child_process15 = require("node:child_process");
51336
51798
 
51337
51799
  // ../cli/src/cli.ts
51338
51800
  var fs19 = __toESM(require("node:fs"), 1);
51339
- var os9 = __toESM(require("node:os"), 1);
51340
- var path18 = __toESM(require("node:path"), 1);
51341
- var import_node_crypto27 = require("node:crypto");
51801
+ var os8 = __toESM(require("node:os"), 1);
51802
+ var path17 = __toESM(require("node:path"), 1);
51803
+ var import_node_crypto26 = require("node:crypto");
51342
51804
 
51343
51805
  // ../cli/src/serve.ts
51344
51806
  var fs18 = __toESM(require("node:fs"), 1);
51345
- var os8 = __toESM(require("node:os"), 1);
51346
- var path17 = __toESM(require("node:path"), 1);
51347
- var import_node_crypto25 = require("node:crypto");
51807
+ var os7 = __toESM(require("node:os"), 1);
51808
+ var path16 = __toESM(require("node:path"), 1);
51809
+ var import_node_crypto24 = require("node:crypto");
51348
51810
  var import_node_url4 = require("node:url");
51349
51811
  init_src2();
51350
51812
  init_src5();
@@ -52261,7 +52723,7 @@ function defaultDocumentPath2(input) {
52261
52723
  init_src4();
52262
52724
 
52263
52725
  // ../storage/src/postgres.ts
52264
- var import_node_crypto23 = require("node:crypto");
52726
+ var import_node_crypto22 = require("node:crypto");
52265
52727
 
52266
52728
  // ../../node_modules/.pnpm/pg@8.21.0/node_modules/pg/esm/index.mjs
52267
52729
  var import_lib = __toESM(require_lib2(), 1);
@@ -52396,7 +52858,7 @@ var PostgresBlobStore = class _PostgresBlobStore {
52396
52858
  return new _PostgresBlobStore(pool, schema);
52397
52859
  }
52398
52860
  async put(bytes) {
52399
- const hash = (0, import_node_crypto23.createHash)("sha256").update(bytes).digest("hex");
52861
+ const hash = (0, import_node_crypto22.createHash)("sha256").update(bytes).digest("hex");
52400
52862
  await this.pool.query(
52401
52863
  `INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
52402
52864
  [hash, Buffer.from(bytes), bytes.byteLength, sniffContentType(bytes) ?? null]
@@ -53861,8 +54323,8 @@ var PostgresControlPlaneStore = class _PostgresControlPlaneStore {
53861
54323
  const r = await this.pool.query(`SELECT * FROM ${this.s}.companies WHERE id = $1`, [id]);
53862
54324
  return r.rows[0] ? rowToCompany(r.rows[0]) : null;
53863
54325
  }
53864
- async getCompanyBySlug(slug6) {
53865
- const r = await this.pool.query(`SELECT * FROM ${this.s}.companies WHERE slug = $1`, [slug6]);
54326
+ async getCompanyBySlug(slug4) {
54327
+ const r = await this.pool.query(`SELECT * FROM ${this.s}.companies WHERE slug = $1`, [slug4]);
53866
54328
  return r.rows[0] ? rowToCompany(r.rows[0]) : null;
53867
54329
  }
53868
54330
  async listCompanies() {
@@ -54306,7 +54768,7 @@ var PostgresTraceStore = class _PostgresTraceStore {
54306
54768
  run.exitReason,
54307
54769
  run.errorCode,
54308
54770
  run.errorMessage,
54309
- JSON.stringify(run.usage),
54771
+ run.usage === null ? null : JSON.stringify(run.usage),
54310
54772
  run.effectiveModel,
54311
54773
  run.modelSource,
54312
54774
  run.transcriptRef,
@@ -54353,7 +54815,7 @@ var PostgresTraceStore = class _PostgresTraceStore {
54353
54815
  for (const [key, spec] of Object.entries(_PostgresTraceStore.RUN_COLS)) {
54354
54816
  if (!(key in patch)) continue;
54355
54817
  const v2 = patch[key];
54356
- args.push(spec.json ? JSON.stringify(v2 ?? null) : v2 ?? null);
54818
+ args.push(spec.json ? v2 == null ? null : JSON.stringify(v2) : v2 ?? null);
54357
54819
  sets.push(spec.json ? `${spec.col}=$${args.length}::jsonb` : `${spec.col}=$${args.length}`);
54358
54820
  }
54359
54821
  args.push(this.now());
@@ -54418,6 +54880,22 @@ var PostgresTraceStore = class _PostgresTraceStore {
54418
54880
  const nextCursor = hasMore ? page[page.length - 1]?.id ?? null : null;
54419
54881
  return { items: page, nextCursor };
54420
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
+ }
54421
54899
  /* ---------- 事件流(单写者 seq + 幂等吸收)---------- */
54422
54900
  async appendEvent(runId, event) {
54423
54901
  return (await this.appendEvents(runId, [event]))[0];
@@ -54706,7 +55184,7 @@ var PostgresTraceStore = class _PostgresTraceStore {
54706
55184
  COALESCE(SUM(COALESCE((usage->>'cacheCreationTokens')::bigint, 0)), 0),
54707
55185
  COALESCE(SUM(COALESCE((usage->>'costUsdMicros')::bigint, 0)), 0)
54708
55186
  FROM ${this.s}.agent_runs
54709
- WHERE usage IS NOT NULL
55187
+ WHERE jsonb_typeof(usage) = 'object'
54710
55188
  AND started_at >= $2 AND started_at < $3
54711
55189
  GROUP BY actor_id, runtime_instance_id, runtime_kind, effective_model, hour`,
54712
55190
  [cid, from, to]
@@ -55412,7 +55890,7 @@ var rowToMessage = (row) => ({
55412
55890
  });
55413
55891
 
55414
55892
  // ../storage/src/postgres-nodes.ts
55415
- var import_node_crypto24 = require("node:crypto");
55893
+ var import_node_crypto23 = require("node:crypto");
55416
55894
  var ident8 = (s2) => {
55417
55895
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
55418
55896
  return s2;
@@ -55550,7 +56028,7 @@ var PostgresNodeTokenStore = class _PostgresNodeTokenStore {
55550
56028
  return store;
55551
56029
  }
55552
56030
  issue(nodeId) {
55553
- const token = `ont_${(0, import_node_crypto24.randomBytes)(24).toString("base64url")}`;
56031
+ const token = `ont_${(0, import_node_crypto23.randomBytes)(24).toString("base64url")}`;
55554
56032
  this.cache.set(token, nodeId);
55555
56033
  void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
55556
56034
  return token;
@@ -55697,7 +56175,7 @@ function builtinSkillsDir() {
55697
56175
  const override = process.env["OASIS_BUILTIN_SKILLS_DIR"]?.trim();
55698
56176
  if (override) return override;
55699
56177
  const repoRoot = (0, import_node_url4.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
55700
- return path17.join(repoRoot, "skills");
56178
+ return path16.join(repoRoot, "skills");
55701
56179
  }
55702
56180
  function materializeBuiltins(builtins, runtimeKind) {
55703
56181
  if (builtins.length === 0) return {};
@@ -55731,10 +56209,10 @@ var oasisWrapperCache;
55731
56209
  function oasisWrapperScript() {
55732
56210
  if (oasisWrapperCache && fs18.existsSync(oasisWrapperCache)) return oasisWrapperCache;
55733
56211
  const repoRoot = (0, import_node_url4.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
55734
- const tsx = path17.join(repoRoot, "node_modules/.bin/tsx");
55735
- const main = path17.join(repoRoot, "packages/cli/src/main.ts");
55736
- const dir = fs18.mkdtempSync(path17.join(os8.tmpdir(), "oasis-cli-"));
55737
- const wrapper = path17.join(dir, "oasis");
56212
+ const tsx = path16.join(repoRoot, "node_modules/.bin/tsx");
56213
+ const main = path16.join(repoRoot, "packages/cli/src/main.ts");
56214
+ const dir = fs18.mkdtempSync(path16.join(os7.tmpdir(), "oasis-cli-"));
56215
+ const wrapper = path16.join(dir, "oasis");
55738
56216
  fs18.writeFileSync(wrapper, `#!/usr/bin/env bash
55739
56217
  exec ${JSON.stringify(tsx)} ${JSON.stringify(main)} "$@"
55740
56218
  `);
@@ -55826,7 +56304,7 @@ function traceRuntimeKind(runtimeKind) {
55826
56304
  async function startServe(opts) {
55827
56305
  const serverStartedAt = (/* @__PURE__ */ new Date()).toISOString();
55828
56306
  fs18.mkdirSync(opts.dir, { recursive: true });
55829
- const serverHeartbeatFile = path17.join(opts.dir, "serve-heartbeat.json");
56307
+ const serverHeartbeatFile = path16.join(opts.dir, "serve-heartbeat.json");
55830
56308
  let previousServerHeartbeatAt;
55831
56309
  if (fs18.existsSync(serverHeartbeatFile)) {
55832
56310
  try {
@@ -55842,7 +56320,7 @@ async function startServe(opts) {
55842
56320
  writeServerHeartbeat();
55843
56321
  const serverHeartbeatTimer = setInterval(writeServerHeartbeat, 5e3);
55844
56322
  serverHeartbeatTimer.unref?.();
55845
- const lock = path17.join(opts.dir, "serve.lock");
56323
+ const lock = path16.join(opts.dir, "serve.lock");
55846
56324
  if (fs18.existsSync(lock)) {
55847
56325
  const pid = Number(fs18.readFileSync(lock, "utf8"));
55848
56326
  let alive = false;
@@ -55856,15 +56334,15 @@ async function startServe(opts) {
55856
56334
  }
55857
56335
  fs18.writeFileSync(lock, String(process.pid));
55858
56336
  const loadJson = (name) => {
55859
- const file = path17.join(opts.dir, name);
56337
+ const file = path16.join(opts.dir, name);
55860
56338
  return fs18.existsSync(file) ? JSON.parse(fs18.readFileSync(file, "utf8")) : void 0;
55861
56339
  };
55862
56340
  const pgDsn = process.env["OASIS_PG_DSN"] ?? process.env["DATABASE_URL"];
55863
56341
  const pgSchema = process.env["OASIS_PG_SCHEMA"] ?? "public";
55864
56342
  const pgPool = pgDsn ? createPgPool(pgDsn) : void 0;
55865
- const oplog = pgDsn && pgPool ? await PostgresOplogStore.open(pgPool, pgSchema) : await NdjsonOplogStore.open(path17.join(opts.dir, "oplog.ndjson"));
55866
- const blobs = pgDsn && pgPool ? await PostgresBlobStore.open(pgPool, pgSchema) : new DirBlobStore(path17.join(opts.dir, "blobs"));
55867
- const assets = new DirBlobStore(path17.join(opts.dir, "assets"));
56343
+ const oplog = pgDsn && pgPool ? await PostgresOplogStore.open(pgPool, pgSchema) : await NdjsonOplogStore.open(path16.join(opts.dir, "oplog.ndjson"));
56344
+ const blobs = pgDsn && pgPool ? await PostgresBlobStore.open(pgPool, pgSchema) : new DirBlobStore(path16.join(opts.dir, "blobs"));
56345
+ const assets = new DirBlobStore(path16.join(opts.dir, "assets"));
55868
56346
  const defaultRootSchema = [
55869
56347
  { name: "brief", description: "\u5DE5\u5355\u603B\u7EB2\uFF1A\u53D1\u8D77\u65B9\u5BF9\u9F50\u7684\u603B\u76EE\u6807\u4E0E\u8303\u56F4\uFF0C\u6574\u5F20\u534F\u4F5C\u56FE\u7684\u6839\u3002", contentType: "text", ownerRole: "founder" },
55870
56348
  {
@@ -55882,7 +56360,7 @@ async function startServe(opts) {
55882
56360
  if (!schema.some((d) => d.name === "document")) {
55883
56361
  schema.push({ name: "document", description: "\u901A\u7528\u6587\u6863\uFF1A\u672A\u7EC6\u5206\u7684\u6587\u5B57\u4EA7\u7269\uFF08\u5177\u4F53\u7C7B\u578B\u89C1 docType\uFF09\u3002", contentType: "text", ownerRole: "writer" });
55884
56362
  }
55885
- const typeStore = await FileTypeRegistryStore.open(path17.join(opts.dir, "type-registry.json"));
56363
+ const typeStore = await FileTypeRegistryStore.open(path16.join(opts.dir, "type-registry.json"));
55886
56364
  if ((await typeStore.list()).length === 0) for (const d of schema) await typeStore.put(d);
55887
56365
  {
55888
56366
  const live = await typeStore.list();
@@ -55890,7 +56368,7 @@ async function startServe(opts) {
55890
56368
  schema.length = 0;
55891
56369
  schema.push(...live);
55892
56370
  }
55893
- const registryStore = pgDsn && pgPool ? await PostgresRegistryStore.open(pgPool, pgSchema) : await FileRegistryStore.open(path17.join(opts.dir, "registry-store.json"));
56371
+ const registryStore = pgDsn && pgPool ? await PostgresRegistryStore.open(pgPool, pgSchema) : await FileRegistryStore.open(path16.join(opts.dir, "registry-store.json"));
55894
56372
  try {
55895
56373
  await backfillSkillContentFromAssets(registryStore, assets);
55896
56374
  } catch (err) {
@@ -55914,14 +56392,14 @@ async function startServe(opts) {
55914
56392
  // per-contentType merge facet(§5.6):text=expensive→group-commit、code/structured=cheap→顺序 rebase
55915
56393
  handlers: defaultHandlersByContentType()
55916
56394
  });
55917
- const issuer = createTokenIssuer({ secretFile: path17.join(opts.dir, "session-token-secret") });
55918
- const operatorActor = `actor:human:${os8.userInfo().username}`;
56395
+ const issuer = createTokenIssuer({ secretFile: path16.join(opts.dir, "session-token-secret") });
56396
+ const operatorActor = `actor:human:${os7.userInfo().username}`;
55919
56397
  const operatorToken = issuer.issue(operatorActor);
55920
- fs18.writeFileSync(path17.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
55921
- let nodeTokens = createNodeTokenStore(path17.join(opts.dir, "node-tokens.json"));
55922
- const enrollTokens = createEnrollTokenStore(30 * 60 * 1e3, path17.join(opts.dir, "enroll-tokens.json"));
55923
- let nodeStore = await FileNodeStore.open(path17.join(opts.dir, "nodes.json"));
55924
- const registryAuditFile = path17.join(opts.dir, "registry-audit.ndjson");
56398
+ fs18.writeFileSync(path16.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
56399
+ let nodeTokens = createNodeTokenStore(path16.join(opts.dir, "node-tokens.json"));
56400
+ const enrollTokens = createEnrollTokenStore(30 * 60 * 1e3, path16.join(opts.dir, "enroll-tokens.json"));
56401
+ let nodeStore = await FileNodeStore.open(path16.join(opts.dir, "nodes.json"));
56402
+ const registryAuditFile = path16.join(opts.dir, "registry-audit.ndjson");
55925
56403
  const registryListeners = /* @__PURE__ */ new Set();
55926
56404
  const registryAudit = (record5) => {
55927
56405
  fs18.appendFile(registryAuditFile, JSON.stringify(record5) + "\n", () => {
@@ -55963,9 +56441,9 @@ async function startServe(opts) {
55963
56441
  nodeTokens = await PostgresNodeTokenStore.open(pgPool, pgSchema);
55964
56442
  console.log(`[serve] \u4E8B\u5B9E/\u72B6\u6001\u4F53\u7CFB\uFF1APostgres schema=${pgSchema}\uFF08oplog / registry / blobs / artifacts / chat\uFF09`);
55965
56443
  } else {
55966
- projectStateStore = await FileProjectStateStore.open(path17.join(opts.dir, "artifact-document-projects.json"));
55967
- artifactStateStore = await FileArtifactStateStore.open(path17.join(opts.dir, "artifact-document-state.json"));
55968
- chatSessionStore = await FileChatSessionStore.open(path17.join(opts.dir, "chat-sessions.json"));
56444
+ projectStateStore = await FileProjectStateStore.open(path16.join(opts.dir, "artifact-document-projects.json"));
56445
+ artifactStateStore = await FileArtifactStateStore.open(path16.join(opts.dir, "artifact-document-state.json"));
56446
+ chatSessionStore = await FileChatSessionStore.open(path16.join(opts.dir, "chat-sessions.json"));
55969
56447
  console.log("[serve] \u4EA4\u4ED8\u7269\u72B6\u6001\u4F53\u7CFB\uFF1A\u672C\u5730 JSON dev store");
55970
56448
  console.log("[serve] \u8282\u70B9/\u8FD0\u884C\u65F6\u4F53\u7CFB\uFF1A\u672C\u5730 JSON dev store");
55971
56449
  }
@@ -55984,7 +56462,7 @@ async function startServe(opts) {
55984
56462
  });
55985
56463
  console.log(`[serve] \u9879\u76EE\u6587\u6863\u4F53\u7CFB\uFF1AOutline ${outlineUrl}\uFF08\u6302\u5728 Team Workspace\u300C\u9879\u76EE\u300D\u4E0B\uFF09`);
55986
56464
  } else {
55987
- projectDocumentStore = await FileProjectDocumentStore.open(path17.join(opts.dir, "project-document-spaces.json"));
56465
+ projectDocumentStore = await FileProjectDocumentStore.open(path16.join(opts.dir, "project-document-spaces.json"));
55988
56466
  console.log("[serve] \u9879\u76EE\u6587\u6863\u4F53\u7CFB\uFF1A\u672C\u5730\u5185\u5B58/\u6587\u4EF6 dev store");
55989
56467
  }
55990
56468
  const registryActorNames = /* @__PURE__ */ new Map();
@@ -56018,7 +56496,7 @@ async function startServe(opts) {
56018
56496
  traceStore = await PostgresTraceStore.open(tracePool, pgSchema);
56019
56497
  console.log(`[serve] \u8FD0\u884C\u8F68\u8FF9\u4F53\u7CFB\uFF1APostgres schema=${pgSchema}${traceDsn === pgDsn ? "" : "\uFF08\u72EC\u7ACB trace \u5E93\uFF09"}`);
56020
56498
  } else {
56021
- traceStore = await FileTraceStore.open(path17.join(opts.dir, "trace"));
56499
+ traceStore = await FileTraceStore.open(path16.join(opts.dir, "trace"));
56022
56500
  console.log("[serve] \u8FD0\u884C\u8F68\u8FF9\u4F53\u7CFB\uFF1A\u672C\u5730 ndjson dev store");
56023
56501
  }
56024
56502
  traceStoreForActors = traceStore;
@@ -56039,7 +56517,7 @@ async function startServe(opts) {
56039
56517
  create: createSeededWorkorder,
56040
56518
  projectExists: async (id) => Boolean(await projectStateStore.getProject(id))
56041
56519
  });
56042
- const controlPlaneStore = pgDsn && pgPool ? await PostgresControlPlaneStore.open(pgPool, pgSchema) : await FileControlPlaneStore.open(path17.join(opts.dir, "control-plane.json"));
56520
+ const controlPlaneStore = pgDsn && pgPool ? await PostgresControlPlaneStore.open(pgPool, pgSchema) : await FileControlPlaneStore.open(path16.join(opts.dir, "control-plane.json"));
56043
56521
  if (pgDsn && pgPool) {
56044
56522
  const priceStore = await PostgresModelPriceStore.open(pgPool, pgSchema);
56045
56523
  setModelPriceCacheLoader(async () => {
@@ -56128,8 +56606,8 @@ async function startServe(opts) {
56128
56606
  }
56129
56607
  return auth.sessionAccountId(headers);
56130
56608
  };
56131
- const workorderDrafts = new WorkorderDraftStore(path17.join(opts.dir, "workorder-drafts.json"));
56132
- const journalFile = path17.join(opts.dir, "dispatch-journal.ndjson");
56609
+ const workorderDrafts = new WorkorderDraftStore(path16.join(opts.dir, "workorder-drafts.json"));
56610
+ const journalFile = path16.join(opts.dir, "dispatch-journal.ndjson");
56133
56611
  const journalRing = [];
56134
56612
  if (fs18.existsSync(journalFile)) {
56135
56613
  try {
@@ -56173,7 +56651,7 @@ async function startServe(opts) {
56173
56651
  defaultEngine: { kernel, oplog, blobs, registry: registryStore, assets },
56174
56652
  defaultCompanyId,
56175
56653
  buildEngine: async (companyId) => {
56176
- const engine2 = await openFileEngine(path17.join(opts.dir, "companies", companyEngineDirName(companyId)), { schema });
56654
+ const engine2 = await openFileEngine(path16.join(opts.dir, "companies", companyEngineDirName(companyId)), { schema });
56177
56655
  await ensureCompanyActors(controlPlaneStore, companyId, engine2.registry);
56178
56656
  await syncRegistryRolesToKernel(engine2.registry, engine2.kernel);
56179
56657
  return engine2;
@@ -56290,7 +56768,7 @@ async function startServe(opts) {
56290
56768
  }));
56291
56769
  }
56292
56770
  const limits = { wallClockMs: SESSION_WALL_CLOCK_MS.planner };
56293
- const artifactId = `artifact:planner:${(0, import_node_crypto25.randomUUID)()}`;
56771
+ const artifactId = `artifact:planner:${(0, import_node_crypto24.randomUUID)()}`;
56294
56772
  const handle = await chatRemoteAdapter.spawn({
56295
56773
  actor: planner.id,
56296
56774
  actorToken: issueSessionToken(planner.id, { artifactId, action: "plan-workorder", limits, binding }),
@@ -56318,7 +56796,7 @@ async function startServe(opts) {
56318
56796
  resolveCodeRepo: (ws) => resolveCodeRepoRef?.(ws) ?? null,
56319
56797
  // B3 auto-merge(§9.7):part propose 时把工作分支合进伞分支的兜底 mirror 根——与 GitSyncWorker/CiGate
56320
56798
  // 的 git-mirrors **分开**(各自 checkout 不同分支,共享 clone 会竞态)。
56321
- gitMirrorRoot: path17.join(opts.dir, "propose-mirrors"),
56799
+ gitMirrorRoot: path16.join(opts.dir, "propose-mirrors"),
56322
56800
  domains: [
56323
56801
  actors.register,
56324
56802
  companies.register,
@@ -56336,6 +56814,7 @@ async function startServe(opts) {
56336
56814
  collabDomain({
56337
56815
  kernel,
56338
56816
  oplog,
56817
+ trace: trace.service,
56339
56818
  registry: registryStore,
56340
56819
  artifacts: artifactStateStore,
56341
56820
  dispatchJournal: () => journalRing.slice(-200),
@@ -56401,8 +56880,8 @@ async function startServe(opts) {
56401
56880
  },
56402
56881
  retainedSession: (key) => key.annotationId && key.artifactId ? allDispatchers().map((d) => d.retainedProduceSessionFor(key.artifactId)).find(Boolean) : coordinatorRef?.retainedSessionFor(key),
56403
56882
  // 0030 D3/D7 落盘:discuss 按项表写文件,serve 重启后同一卡点续同一段、不再新开。
56404
- discussSessions: new FileSideMap(path17.join(opts.dir, "discuss-sessions.json")),
56405
- escalateDiscussRetain: new FileSideMap(path17.join(opts.dir, "escalate-discuss-retain.json")),
56883
+ discussSessions: new FileSideMap(path16.join(opts.dir, "discuss-sessions.json")),
56884
+ escalateDiscussRetain: new FileSideMap(path16.join(opts.dir, "escalate-discuss-retain.json")),
56406
56885
  // 切片 2b:代署 escalate 发起会话保留(落盘)
56407
56886
  dispatchChat: async ({ actorId, message, sessionId, chatSessionId, attachments, workspace, companyId }) => {
56408
56887
  const runCompanyId = companyId ?? defaultCompanyId;
@@ -56425,10 +56904,10 @@ async function startServe(opts) {
56425
56904
  if (!localUrl) {
56426
56905
  throw new ApiError(503, "SERVER_URL_NOT_READY", "server URL \u5C1A\u672A\u5C31\u7EEA");
56427
56906
  }
56428
- const runtimeSessionId = sessionId ?? (0, import_node_crypto25.randomUUID)();
56907
+ const runtimeSessionId = sessionId ?? (0, import_node_crypto24.randomUUID)();
56429
56908
  const resumeRuntimeSession = Boolean(sessionId);
56430
- const traceRunId = `chat-run:${(0, import_node_crypto25.randomUUID)()}`;
56431
- const artifactId = `artifact:chat:${(0, import_node_crypto25.randomUUID)()}`;
56909
+ const traceRunId = `chat-run:${(0, import_node_crypto24.randomUUID)()}`;
56910
+ const artifactId = `artifact:chat:${(0, import_node_crypto24.randomUUID)()}`;
56432
56911
  const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
56433
56912
  let traceSeq = 0;
56434
56913
  let lastProgressTouchMs = 0;
@@ -56707,7 +57186,7 @@ ${detail}` : genericLine));
56707
57186
  console.log(`[serve] \u8FDC\u7A0B agent API URL\uFF1A${remoteApiUrl ?? "\u672A\u914D\u7F6E\uFF08\u4EC5\u652F\u6301\u672C\u673A runtime\uFF09"}`);
56708
57187
  const projectsCfg = loadJson("projects.json");
56709
57188
  const seedProjects = (projectsCfg?.projects ?? []).map(
56710
- (p2) => p2.repos?.length ? { ...p2, repos: p2.repos.map((r) => r.repoDir ? { ...r, repoDir: path17.resolve(opts.dir, r.repoDir) } : r) } : p2
57189
+ (p2) => p2.repos?.length ? { ...p2, repos: p2.repos.map((r) => r.repoDir ? { ...r, repoDir: path16.resolve(opts.dir, r.repoDir) } : r) } : p2
56711
57190
  );
56712
57191
  for (const sp of seedProjects) {
56713
57192
  const existing = await projectStateStore.getProject(sp.id);
@@ -56837,8 +57316,8 @@ ${detail}` : genericLine));
56837
57316
  // 每 agent 并发产出上限默认 5(工单数 / 全局总量默认不限);--max-produce-per-agent N 覆盖。
56838
57317
  maxConcurrentProducePerActor: opts.maxConcurrentProducePerActor ?? 5,
56839
57318
  // 提案 uniform-annotation-resolve ③/④ 落盘:容错计数 + produce 会话号(重启不丢;丢了也只是降级/多容错一轮)
56840
- annotationRescue: new FileSideMap(path17.join(opts.dir, `annotation-rescue.${companyId}.json`)),
56841
- produceSessions: new FileSideMap(path17.join(opts.dir, `produce-sessions.${companyId}.json`))
57319
+ annotationRescue: new FileSideMap(path16.join(opts.dir, `annotation-rescue.${companyId}.json`)),
57320
+ produceSessions: new FileSideMap(path16.join(opts.dir, `produce-sessions.${companyId}.json`))
56842
57321
  });
56843
57322
  const slot = { dispatcher: d, logged: 0, ticking: false };
56844
57323
  dispatchers.set(companyId, slot);
@@ -57010,8 +57489,17 @@ ${detail}` : genericLine));
57010
57489
  if (f2.action !== "produce") continue;
57011
57490
  const cur = kernel.model.artifacts.get(f2.artifactId);
57012
57491
  if (!cur) continue;
57013
- if (now - Date.parse(f2.fusedAt) > agentMs) {
57014
- 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)}`);
57015
57503
  }
57016
57504
  }
57017
57505
  })().catch((err) => console.error(`[sla] \u626B\u63CF\u5931\u8D25: ${String(err)}`));
@@ -57024,7 +57512,7 @@ ${detail}` : genericLine));
57024
57512
  schema: schemaMap,
57025
57513
  mirrors: new Map(opts.mirror.connectors.map((c) => [c.platform, c])),
57026
57514
  identities: opts.mirror.identities ?? new MapMirrorIdentities(),
57027
- state: new FsMirrorStateStore(path17.join(opts.dir, "mirror-state.json")),
57515
+ state: new FsMirrorStateStore(path16.join(opts.dir, "mirror-state.json")),
57028
57516
  ...opts.mirror.debounceMs !== void 0 ? { debounceMs: opts.mirror.debounceMs } : {},
57029
57517
  log: (m2) => console.log(m2)
57030
57518
  });
@@ -57034,27 +57522,20 @@ ${detail}` : genericLine));
57034
57522
  }
57035
57523
  let gitTimer;
57036
57524
  if (opts.dispatch) {
57037
- if (ciGateEnabled) {
57038
- const ci = new GhCiProvider({
57039
- mirrorRoot: path17.join(opts.dir, "git-mirrors"),
57040
- failClosedWhenNoChecks: process.env["OASIS_CI_FAIL_CLOSED"] === "1",
57041
- log: (m2) => console.log(m2)
57042
- });
57043
- const ciGate = new CiGateWorker({ kernel, resolveProject, ci, statusSink: ciGateStatus, log: (m2) => console.log(m2) });
57044
- gitTimer = setInterval(() => {
57045
- void ciGate.tick().catch((err) => console.error(`[ci] tick \u5931\u8D25: ${String(err)}`));
57046
- }, 3e4);
57047
- } else {
57048
- const gitSync = new GitSyncWorker({
57049
- kernel,
57050
- resolveProject,
57051
- mirrorRoot: path17.join(opts.dir, "git-mirrors"),
57052
- log: (m2) => console.log(m2)
57053
- });
57054
- gitTimer = setInterval(() => {
57055
- void gitSync.tick().catch((err) => console.error(`[git] \u540C\u6B65\u5931\u8D25: ${String(err)}`));
57056
- }, 3e4);
57057
- }
57525
+ const gitMirrorRoot = path16.join(opts.dir, "git-mirrors");
57526
+ const ci = new GhCiProvider({
57527
+ mirrorRoot: gitMirrorRoot,
57528
+ failClosedWhenNoChecks: process.env["OASIS_CI_FAIL_CLOSED"] === "1",
57529
+ log: (m2) => console.log(m2)
57530
+ });
57531
+ const mergeWorker = new MergeWorker({ kernel, resolveProject, ci, mirrorRoot: gitMirrorRoot, statusSink: ciGateStatus, log: (m2) => console.log(m2) });
57532
+ const ciGate = ciGateEnabled ? new CiGateWorker({ kernel, resolveProject, ci, statusSink: ciGateStatus, log: (m2) => console.log(m2) }) : void 0;
57533
+ gitTimer = setInterval(() => {
57534
+ void (async () => {
57535
+ if (ciGate) await ciGate.tick().catch((err) => console.error(`[ci] tick \u5931\u8D25: ${String(err)}`));
57536
+ await mergeWorker.tick().catch((err) => console.error(`[merge] tick \u5931\u8D25: ${String(err)}`));
57537
+ })();
57538
+ }, 3e4);
57058
57539
  }
57059
57540
  let coordTimer;
57060
57541
  let livenessTimer;
@@ -57109,7 +57590,7 @@ ${detail}` : genericLine));
57109
57590
  // 病历里展示节点的"在跑会话情况"(runId + 跑了多久):从派发器在途会话按 artifactId 过滤。
57110
57591
  sessionsOf: (id) => allDispatchers().flatMap((d) => d.inFlightSessions()).filter((s2) => s2.artifactId === id).map((s2) => ({ runId: s2.sessionId, action: s2.action, startedAt: s2.startedAt })),
57111
57592
  log: (m2) => console.log(m2),
57112
- retention: new FileSideMap(path17.join(opts.dir, "retained-sessions.json"))
57593
+ retention: new FileSideMap(path16.join(opts.dir, "retained-sessions.json"))
57113
57594
  // 0030 D7 落盘
57114
57595
  });
57115
57596
  coordinatorRef = coordinator;
@@ -57653,7 +58134,7 @@ async function startNode(opts) {
57653
58134
  // ../cli/src/daemon/machine-id.ts
57654
58135
  var import_node_child_process14 = require("node:child_process");
57655
58136
  var import_node_fs6 = require("node:fs");
57656
- var import_node_crypto26 = require("node:crypto");
58137
+ var import_node_crypto25 = require("node:crypto");
57657
58138
  var import_node_os7 = require("node:os");
57658
58139
  function linuxMachineId() {
57659
58140
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -57719,7 +58200,7 @@ var defaultSources = {
57719
58200
  function resolveNodeId(sources = {}) {
57720
58201
  const s2 = { ...defaultSources, ...sources };
57721
58202
  const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
57722
- const digest = (0, import_node_crypto26.createHash)("sha256").update(material).digest("hex").slice(0, 12);
58203
+ const digest = (0, import_node_crypto25.createHash)("sha256").update(material).digest("hex").slice(0, 12);
57723
58204
  return `node-${digest}`;
57724
58205
  }
57725
58206
 
@@ -57889,8 +58370,8 @@ function makeClient(base, token) {
57889
58370
  if (!res.ok) await fail(res);
57890
58371
  return (await res.json()).data;
57891
58372
  },
57892
- async request(method, path20, body) {
57893
- const res = await fetch(`${base}${path20}`, {
58373
+ async request(method, path19, body) {
58374
+ const res = await fetch(`${base}${path19}`, {
57894
58375
  method,
57895
58376
  headers,
57896
58377
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
@@ -57898,8 +58379,8 @@ function makeClient(base, token) {
57898
58379
  if (!res.ok) await fail(res);
57899
58380
  return await res.json();
57900
58381
  },
57901
- async post(path20, body) {
57902
- const res = await fetch(`${base}${path20}`, { method: "POST", headers, body: JSON.stringify(body) });
58382
+ async post(path19, body) {
58383
+ const res = await fetch(`${base}${path19}`, { method: "POST", headers, body: JSON.stringify(body) });
57903
58384
  if (!res.ok) await fail(res);
57904
58385
  return await res.json();
57905
58386
  }
@@ -57912,14 +58393,14 @@ async function runCli(argv, println = console.log) {
57912
58393
  return;
57913
58394
  }
57914
58395
  const { positional, flags } = parseArgs(rest);
57915
- const dir = flags.get("dir") ?? process.env["OASIS_DIR"] ?? path18.resolve(".oasis");
58396
+ const dir = flags.get("dir") ?? process.env["OASIS_DIR"] ?? path17.resolve(".oasis");
57916
58397
  if (command === "node" && positional[0] !== "output") {
57917
58398
  const serverUrl = flags.get("server-ws") ?? process.env["OASIS_SERVER_WS"] ?? "ws://127.0.0.1:7320/node-gateway";
57918
58399
  const nodeName = flags.get("name") ?? process.env["OASIS_NODE_NAME"];
57919
- const nodeIdFile = path18.join(dir, "node-id");
57920
- const nodeTokenFile = path18.join(dir, "node-token");
57921
- const enrollTokenFile = path18.join(dir, "enroll-token");
57922
- const reportMarkerFile = path18.join(dir, "report-runtimes");
58400
+ const nodeIdFile = path17.join(dir, "node-id");
58401
+ const nodeTokenFile = path17.join(dir, "node-token");
58402
+ const enrollTokenFile = path17.join(dir, "enroll-token");
58403
+ const reportMarkerFile = path17.join(dir, "report-runtimes");
57923
58404
  const persistedNodeId = fs19.existsSync(nodeIdFile) ? fs19.readFileSync(nodeIdFile, "utf8").trim() : null;
57924
58405
  let nodeId = persistedNodeId || resolveNodeId();
57925
58406
  let token2 = flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? (fs19.existsSync(nodeTokenFile) ? fs19.readFileSync(nodeTokenFile, "utf8").trim() : void 0);
@@ -57930,7 +58411,7 @@ async function runCli(argv, println = console.log) {
57930
58411
  const res = await fetch(`${httpBase2}/api/nodes/exchange`, {
57931
58412
  method: "POST",
57932
58413
  headers: { "content-type": "application/json" },
57933
- body: JSON.stringify({ enrollToken, nodeId, hostname: os9.hostname() })
58414
+ body: JSON.stringify({ enrollToken, nodeId, hostname: os8.hostname() })
57934
58415
  });
57935
58416
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
57936
58417
  const body = await res.json();
@@ -57960,11 +58441,11 @@ async function runCli(argv, println = console.log) {
57960
58441
  println("\u2192 Installing oasis_test globally...");
57961
58442
  spawnSync("npm", ["install", "-g", "oasis_test@latest"], { stdio: "inherit" });
57962
58443
  const prefix = execSync2("npm prefix -g").toString().trim();
57963
- const binPath = path18.join(prefix, "bin", "oasis_test");
58444
+ const binPath = path17.join(prefix, "bin", "oasis_test");
57964
58445
  const svcArgs = `node --server-ws ${serverUrl} --dir ${dir}${nodeName ? ` --name ${nodeName}` : ""}`;
57965
58446
  if (process.platform === "linux") {
57966
- const svcFile = path18.join(os9.homedir(), ".config/systemd/user/oasis-node.service");
57967
- fs19.mkdirSync(path18.dirname(svcFile), { recursive: true });
58447
+ const svcFile = path17.join(os8.homedir(), ".config/systemd/user/oasis-node.service");
58448
+ fs19.mkdirSync(path17.dirname(svcFile), { recursive: true });
57968
58449
  if (spawnSync("systemctl", ["--user", "is-active", "--quiet", "oasis-node"], {}).status === 0)
57969
58450
  spawnSync("systemctl", ["--user", "stop", "oasis-node"], { stdio: "inherit" });
57970
58451
  fs19.writeFileSync(svcFile, [
@@ -57976,7 +58457,7 @@ async function runCli(argv, println = console.log) {
57976
58457
  `ExecStart=${binPath} ${svcArgs}`,
57977
58458
  // Carry the install-time PATH so the detached daemon detects CLIs in user dirs
57978
58459
  // (~/.npm-global/bin, ~/.local/bin) — systemd --user otherwise gives a minimal PATH.
57979
- `Environment=PATH=${process.env["PATH"] ?? ""}:${path18.join(os9.homedir(), ".npm-global/bin")}:${path18.join(os9.homedir(), ".local/bin")}:/usr/local/bin`,
58460
+ `Environment=PATH=${process.env["PATH"] ?? ""}:${path17.join(os8.homedir(), ".npm-global/bin")}:${path17.join(os8.homedir(), ".local/bin")}:/usr/local/bin`,
57980
58461
  "Restart=on-failure",
57981
58462
  "RestartSec=5",
57982
58463
  "StandardOutput=journal",
@@ -57988,23 +58469,23 @@ async function runCli(argv, println = console.log) {
57988
58469
  ].join("\n"));
57989
58470
  spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
57990
58471
  spawnSync("systemctl", ["--user", "enable", "--now", "oasis-node"], { stdio: "inherit" });
57991
- spawnSync("loginctl", ["enable-linger", os9.userInfo().username], { stdio: "ignore" });
58472
+ spawnSync("loginctl", ["enable-linger", os8.userInfo().username], { stdio: "ignore" });
57992
58473
  println("\u2713 Oasis node service started (systemd --user oasis-node)");
57993
58474
  println(" Status : systemctl --user status oasis-node");
57994
58475
  println(" Logs : journalctl --user -u oasis-node -f");
57995
58476
  println(" Stop : systemctl --user stop oasis-node");
57996
58477
  } else if (process.platform === "darwin") {
57997
- const plist = path18.join(os9.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
57998
- fs19.mkdirSync(path18.dirname(plist), { recursive: true });
58478
+ const plist = path17.join(os8.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
58479
+ fs19.mkdirSync(path17.dirname(plist), { recursive: true });
57999
58480
  spawnSync("launchctl", ["unload", plist], { stdio: "ignore" });
58000
58481
  const argXml = [binPath, "node", "--server-ws", serverUrl, "--dir", dir, ...nodeName ? ["--name", nodeName] : []].map((a) => `<string>${a}</string>`).join("");
58001
- fs19.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.oasis.node</string><key>ProgramArguments</key><array>${argXml}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>${os9.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os9.homedir()}/.oasis-node.err</string></dict></plist>`);
58482
+ fs19.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.oasis.node</string><key>ProgramArguments</key><array>${argXml}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>${os8.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os8.homedir()}/.oasis-node.err</string></dict></plist>`);
58002
58483
  spawnSync("launchctl", ["load", plist], { stdio: "inherit" });
58003
58484
  println("\u2713 Oasis node service started (macOS LaunchAgent com.oasis.node)");
58004
- println(` Logs : tail -f ${os9.homedir()}/.oasis-node.log`);
58485
+ println(` Logs : tail -f ${os8.homedir()}/.oasis-node.log`);
58005
58486
  println(` Stop : launchctl unload ${plist}`);
58006
58487
  } else {
58007
- spawnSync("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path18.join(os9.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
58488
+ spawnSync("sh", ["-c", `nohup ${binPath} ${svcArgs} > ${path17.join(os8.homedir(), ".oasis-node.log")} 2>&1 &`], { stdio: "inherit" });
58008
58489
  println(`\u2713 Oasis node started in background (nohup). Logs: ~/.oasis-node.log`);
58009
58490
  println(" Note: no automatic restart on reboot in this environment.");
58010
58491
  }
@@ -58020,14 +58501,14 @@ async function runCli(argv, println = console.log) {
58020
58501
  return;
58021
58502
  }
58022
58503
  if (command === "node-token") {
58023
- const store = createNodeTokenStore(path18.join(dir, "node-tokens.json"));
58504
+ const store = createNodeTokenStore(path17.join(dir, "node-tokens.json"));
58024
58505
  const sub = positional[0];
58025
58506
  if (sub === "issue") {
58026
- const id = flags.get("id") ?? `node-${(0, import_node_crypto27.randomUUID)()}`;
58507
+ const id = flags.get("id") ?? `node-${(0, import_node_crypto26.randomUUID)()}`;
58027
58508
  const token2 = store.issue(id);
58028
58509
  println(token2);
58029
58510
  process.stderr.write(
58030
- `\u5DF2\u4E3A ${id} \u7B7E\u53D1 node-token\uFF08\u5199\u5165 ${path18.join(dir, "node-tokens.json")}\uFF09\u3002
58511
+ `\u5DF2\u4E3A ${id} \u7B7E\u53D1 node-token\uFF08\u5199\u5165 ${path17.join(dir, "node-tokens.json")}\uFF09\u3002
58031
58512
  \u628A\u4E0A\u9762\u8FD9\u4E32\u653E\u5230\u8282\u70B9\u673A\uFF1A--token <t> / $OASIS_NODE_TOKEN / <\u8282\u70B9dir>/node-token
58032
58513
  `
58033
58514
  );
@@ -58048,7 +58529,7 @@ async function runCli(argv, println = console.log) {
58048
58529
  }
58049
58530
  if (command === "admin" && positional[0] === "grant-owner") {
58050
58531
  const email2 = need(flags, "email");
58051
- const store = await FileControlPlaneStore.open(path18.join(dir, "control-plane.json"));
58532
+ const store = await FileControlPlaneStore.open(path17.join(dir, "control-plane.json"));
58052
58533
  const activeCompanies = (await store.listCompanies()).filter((c) => c.status === "active");
58053
58534
  const companyId = flags.get("company") ?? activeCompanies[0]?.id ?? resolveDefaultCompanyConfig().id;
58054
58535
  const name = flags.get("name");
@@ -58061,7 +58542,7 @@ async function runCli(argv, println = console.log) {
58061
58542
  }
58062
58543
  if (command === "export-trajectories") {
58063
58544
  const { exportTrajectories: exportTrajectories2 } = await Promise.resolve().then(() => (init_trajectory(), trajectory_exports));
58064
- const samples = await exportTrajectories2(path18.resolve(dir));
58545
+ const samples = await exportTrajectories2(path17.resolve(dir));
58065
58546
  const outFile = flags.get("out");
58066
58547
  const lines = samples.map((sample) => JSON.stringify(sample)).join("\n");
58067
58548
  if (outFile !== void 0) {
@@ -58073,10 +58554,10 @@ async function runCli(argv, println = console.log) {
58073
58554
  return;
58074
58555
  }
58075
58556
  if (command === "serve" && positional[0] === "install") {
58076
- const absDir = path18.resolve(dir);
58077
- const name = `oasis-serve-${path18.basename(absDir)}`;
58078
- const unitDir = flags.get("unit-dir") ?? path18.join(os9.homedir(), ".config", "systemd", "user");
58079
- const bin = flags.get("bin") ?? path18.join(os9.homedir(), ".local", "bin", "oasis");
58557
+ const absDir = path17.resolve(dir);
58558
+ const name = `oasis-serve-${path17.basename(absDir)}`;
58559
+ const unitDir = flags.get("unit-dir") ?? path17.join(os8.homedir(), ".config", "systemd", "user");
58560
+ const bin = flags.get("bin") ?? path17.join(os8.homedir(), ".local", "bin", "oasis");
58080
58561
  const port = flags.get("port") ?? "7320";
58081
58562
  const extra = (flags.get("dispatch") === "true" ? ` --dispatch true` : "") + (flags.has("model") ? ` --model ${flags.get("model")}` : "");
58082
58563
  const unit = [
@@ -58094,7 +58575,7 @@ async function runCli(argv, println = console.log) {
58094
58575
  ``
58095
58576
  ].join("\n");
58096
58577
  fs19.mkdirSync(unitDir, { recursive: true });
58097
- const unitPath = path18.join(unitDir, `${name}.service`);
58578
+ const unitPath = path17.join(unitDir, `${name}.service`);
58098
58579
  fs19.writeFileSync(unitPath, unit);
58099
58580
  println(`\u5DF2\u5199\u5165 ${unitPath}`);
58100
58581
  println(`\u542F\u7528\uFF1Asystemctl --user daemon-reload && systemctl --user enable --now ${name}`);
@@ -58117,14 +58598,14 @@ async function runCli(argv, println = console.log) {
58117
58598
  });
58118
58599
  const port2 = new URL(handle.baseUrl).port;
58119
58600
  println(`oasis serve \u5C31\u7EEA\uFF1Ahttp://0.0.0.0:${port2}\uFF08\u672C\u673A\u8BBF\u95EE\u7528 http://127.0.0.1:${port2}\uFF09`);
58120
- println(`operator token \u5DF2\u5199\u5165 ${path18.join(dir, "operator-token")}\uFF08\u672C\u673A CLI \u81EA\u52A8\u8BFB\u53D6\uFF09`);
58601
+ println(`operator token \u5DF2\u5199\u5165 ${path17.join(dir, "operator-token")}\uFF08\u672C\u673A CLI \u81EA\u52A8\u8BFB\u53D6\uFF09`);
58121
58602
  if (flags.get("dispatch") === "true") println(`\u8C03\u5EA6\u5FAA\u73AF\u5DF2\u542F\u52A8\uFF08claude-code adapter\uFF09`);
58122
58603
  await new Promise(() => {
58123
58604
  });
58124
58605
  return;
58125
58606
  }
58126
58607
  const base = flags.get("server") ?? process.env["OASIS_SERVER"] ?? "http://127.0.0.1:7320";
58127
- const tokenFile = path18.join(dir, "operator-token");
58608
+ const tokenFile = path17.join(dir, "operator-token");
58128
58609
  const token = flags.get("token") ?? process.env["OASIS_TOKEN"] ?? (fs19.existsSync(tokenFile) ? fs19.readFileSync(tokenFile, "utf8").trim() : void 0);
58129
58610
  if (!token) {
58130
58611
  throw new Error(`\u65E0 token\uFF1A\u5148 oasis serve\uFF08\u4F1A\u751F\u6210 ${tokenFile}\uFF09\uFF0C\u6216\u663E\u5F0F --token / $OASIS_TOKEN`);
@@ -58149,7 +58630,7 @@ async function runCli(argv, println = console.log) {
58149
58630
  const out = [];
58150
58631
  const walk = (abs, rel) => {
58151
58632
  for (const name of fs19.readdirSync(abs)) {
58152
- const childAbs = path18.join(abs, name);
58633
+ const childAbs = path17.join(abs, name);
58153
58634
  const childRel = rel ? `${rel}/${name}` : name;
58154
58635
  if (fs19.statSync(childAbs).isDirectory()) walk(childAbs, childRel);
58155
58636
  else out.push({ path: childRel, contentBase64: fs19.readFileSync(childAbs).toString("base64") });
@@ -58501,7 +58982,7 @@ async function runCli(argv, println = console.log) {
58501
58982
  if (file !== void 0) {
58502
58983
  const bytes = fs19.readFileSync(file);
58503
58984
  const res = await api.request("POST", `/api/projects/${encodeURIComponent(projectId)}/files`, {
58504
- name: flags.get("name") ?? path18.basename(file),
58985
+ name: flags.get("name") ?? path17.basename(file),
58505
58986
  ...flags.has("path") ? { path: flags.get("path") } : {},
58506
58987
  content_base64: bytes.toString("base64"),
58507
58988
  ...flags.has("content-type") ? { content_type: flags.get("content-type") } : {}
@@ -58790,9 +59271,10 @@ ${res.warning}`);
58790
59271
  }
58791
59272
  case "escalate": {
58792
59273
  const { message } = await api.cmd("escalate", {
58793
- 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]"),
58794
59275
  reason: need(flags, "reason"),
58795
- ...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") } : {}
58796
59278
  });
58797
59279
  println(message);
58798
59280
  break;
@@ -58805,6 +59287,23 @@ ${res.warning}`);
58805
59287
  println(message);
58806
59288
  break;
58807
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
+ }
58808
59307
  case "add-revision": {
58809
59308
  const { message } = await api.cmd("addRevision", {
58810
59309
  artifactId: needPos(positional, 0, "oasis add-revision <artifactId> --desc <\u8981\u5B83\u5B8C\u6210\u4EC0\u4E48> [--role <\u8C01\u6765\u505A>]"),
@@ -59083,8 +59582,8 @@ ${res.warning}`);
59083
59582
  }
59084
59583
  case "content": {
59085
59584
  const id = needPos(positional, 0, "oasis content <artifactId> [path]");
59086
- const path20 = positional[1];
59087
- const c = await api.view("content", id, path20 ? { path: path20 } : void 0);
59585
+ const path19 = positional[1];
59586
+ const c = await api.view("content", id, path19 ? { path: path19 } : void 0);
59088
59587
  if (c.kind === "empty") println("\uFF08\u672C\u4EA7\u7269\u5C1A\u65E0\u5185\u5BB9\uFF09");
59089
59588
  else if (c.kind === "external-pin") println(`\u4EE3\u7801/\u5916\u90E8\u4EA4\u4ED8\u7269\uFF1A\u5185\u5BB9\u5728 git/\u5916\u90E8 \u2192 ${c.ref}\uFF08\u6309\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u91CC\u7684\u4ED3\u5E93\u6E05\u5355 clone \u53D6\uFF09`);
59090
59589
  else if (c.kind === "text") println(c.text);
@@ -59257,14 +59756,14 @@ ${res.warning}`);
59257
59756
  }
59258
59757
 
59259
59758
  // src/index.ts
59260
- var PKG_VERSION = true ? "0.1.52" : "dev";
59261
- var OASIS_DIR = path19.join(os10.homedir(), ".oasis");
59262
- var CONFIG_FILE = path19.join(OASIS_DIR, "node-config.json");
59263
- var PID_FILE = path19.join(OASIS_DIR, "node.pid");
59264
- var BIN_FILE = path19.join(OASIS_DIR, "bin", "oasis.js");
59265
- var LOCAL_BIN = path19.join(os10.homedir(), ".local", "bin", "oasis");
59266
- var LOG_FILE = path19.join(OASIS_DIR, "node.log");
59267
- var NPM_PREFIX = path19.join(OASIS_DIR, "npm-global");
59759
+ var PKG_VERSION = true ? "0.1.54" : "dev";
59760
+ var OASIS_DIR = path18.join(os9.homedir(), ".oasis");
59761
+ var CONFIG_FILE = path18.join(OASIS_DIR, "node-config.json");
59762
+ var PID_FILE = path18.join(OASIS_DIR, "node.pid");
59763
+ var BIN_FILE = path18.join(OASIS_DIR, "bin", "oasis.js");
59764
+ var LOCAL_BIN = path18.join(os9.homedir(), ".local", "bin", "oasis");
59765
+ var LOG_FILE = path18.join(OASIS_DIR, "node.log");
59766
+ var NPM_PREFIX = path18.join(OASIS_DIR, "npm-global");
59268
59767
  var PKG_NAME = "oasis_test";
59269
59768
  var parseFlags = (argv) => {
59270
59769
  const flags = /* @__PURE__ */ new Map();
@@ -59303,26 +59802,26 @@ var isRunning = (pid) => {
59303
59802
  };
59304
59803
  var httpBase = (wsUrl) => wsUrl.replace(/^ws(s?):\/\//, "http$1://").replace(/\/node-gateway.*$/, "");
59305
59804
  function installBinary() {
59306
- fs20.mkdirSync(path19.dirname(BIN_FILE), { recursive: true });
59805
+ fs20.mkdirSync(path18.dirname(BIN_FILE), { recursive: true });
59307
59806
  fs20.copyFileSync(process.argv[1], BIN_FILE);
59308
- fs20.mkdirSync(path19.dirname(LOCAL_BIN), { recursive: true });
59807
+ fs20.mkdirSync(path18.dirname(LOCAL_BIN), { recursive: true });
59309
59808
  fs20.writeFileSync(LOCAL_BIN, `#!/bin/sh
59310
59809
  exec node "${BIN_FILE}" "$@"
59311
59810
  `, { mode: 493 });
59312
59811
  }
59313
59812
  function setupAutostart() {
59314
59813
  const cmd = `${process.execPath} "${BIN_FILE}" --_daemon`;
59315
- const home = os10.homedir();
59814
+ const home = os9.homedir();
59316
59815
  const extraBins = [
59317
- path19.join(home, ".npm-global", "bin"),
59318
- path19.join(home, ".local", "bin"),
59816
+ path18.join(home, ".npm-global", "bin"),
59817
+ path18.join(home, ".local", "bin"),
59319
59818
  "/usr/local/bin"
59320
59819
  ];
59321
59820
  const daemonPath = [process.env["PATH"] ?? "", ...extraBins].filter(Boolean).join(":");
59322
59821
  if (process.platform === "linux") {
59323
- const unitDir = path19.join(os10.homedir(), ".config", "systemd", "user");
59822
+ const unitDir = path18.join(os9.homedir(), ".config", "systemd", "user");
59324
59823
  fs20.mkdirSync(unitDir, { recursive: true });
59325
- fs20.writeFileSync(path19.join(unitDir, "oasis-node.service"), [
59824
+ fs20.writeFileSync(path18.join(unitDir, "oasis-node.service"), [
59326
59825
  "[Unit]",
59327
59826
  "Description=Oasis Node Daemon",
59328
59827
  "After=network.target",
@@ -59344,8 +59843,8 @@ function setupAutostart() {
59344
59843
  } catch {
59345
59844
  }
59346
59845
  } else if (process.platform === "darwin") {
59347
- const plist = path19.join(os10.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
59348
- fs20.mkdirSync(path19.dirname(plist), { recursive: true });
59846
+ const plist = path18.join(os9.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
59847
+ fs20.mkdirSync(path18.dirname(plist), { recursive: true });
59349
59848
  fs20.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
59350
59849
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
59351
59850
  <plist version="1.0"><dict>
@@ -59398,7 +59897,7 @@ function selfUpdate() {
59398
59897
  const cfg = readConfig();
59399
59898
  if (!cfg?.serverUrl) throw new Error("no config \u2014 run `oasis start` first");
59400
59899
  const nameArg = cfg.name ? ` --name '${cfg.name.replace(/'/g, "'\\''")}'` : "";
59401
- const oasisBin = path19.join(NPM_PREFIX, "bin", "oasis");
59900
+ const oasisBin = path18.join(NPM_PREFIX, "bin", "oasis");
59402
59901
  const startCmd = `"${oasisBin}" start --server-ws '${cfg.serverUrl}'${nameArg}`;
59403
59902
  const inner = `{ echo "[oasis] self-update \u2192 npm install --prefix ${NPM_PREFIX} ${PKG_NAME}@latest"; sleep 2; npm install -g --prefer-online --prefix '${NPM_PREFIX}' '${PKG_NAME}@latest' && ${startCmd}; } >> '${LOG_FILE}' 2>&1`;
59404
59903
  const hasSystemd = process.platform === "linux" && (() => {
@@ -59498,7 +59997,7 @@ void (async () => {
59498
59997
  const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
59499
59998
  method: "POST",
59500
59999
  headers: { "content-type": "application/json" },
59501
- body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os10.hostname() })
60000
+ body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os9.hostname() })
59502
60001
  });
59503
60002
  if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
59504
60003
  const body = await res.json();
@@ -59521,7 +60020,7 @@ void (async () => {
59521
60020
  const pid = spawnDaemon();
59522
60021
  console.log(`\u2713 daemon started (PID ${pid})${nameChanged ? ` [name updated: ${prev.name} \u2192 ${name}]` : ""}`);
59523
60022
  console.log(` logs: tail -f ${LOG_FILE}`);
59524
- if (!process.env["PATH"]?.includes(path19.dirname(LOCAL_BIN)))
60023
+ if (!process.env["PATH"]?.includes(path18.dirname(LOCAL_BIN)))
59525
60024
  console.log(` add to PATH: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc`);
59526
60025
  return;
59527
60026
  }