oasis_test 0.1.13 → 0.1.14

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 +1776 -404
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -161,6 +161,7 @@ function fold(model, op) {
161
161
  author: op.actor,
162
162
  body: p2.body,
163
163
  state: "open",
164
+ createdAt: op.timestamp,
164
165
  thread: [],
165
166
  raisedFrom: p2.raisedFrom ?? null,
166
167
  raisedFromRev: p2.raisedFromRev ?? null,
@@ -1744,14 +1745,18 @@ var init_kernel = __esm({
1744
1745
  const override = await this.resolveReviewerOverride?.(artifact.owner, artifact);
1745
1746
  const overrideApplied = !!override && override.length > 0;
1746
1747
  const configured = overrideApplied ? override : gateSpec.reviewers.flatMap((role) => this.roleIndex.get(role) ?? []);
1747
- const eligible = [...new Set(configured)].filter((a) => a !== head.author);
1748
+ const configuredUnique = [...new Set(configured)];
1749
+ const eligible = configuredUnique.filter((a) => a !== head.author);
1748
1750
  const quorum = overrideApplied ? Math.min(gateSpec.quorum, Math.max(1, eligible.length)) : gateSpec.quorum;
1749
1751
  if (eligible.length < quorum) {
1750
- throw new KernelError(
1751
- `\u6536\u5C3E gate \u65E0\u6CD5\u6EE1\u8DB3\uFF1A\u5408\u683C reviewer ${eligible.length} \u4EBA < quorum ${quorum}\uFF08reviewers=${eligible.join(",") || "none"}\uFF1Broles=${gateSpec.reviewers.join(",")}\uFF0C\u5DF2\u5254\u9664 head \u4F5C\u8005 ${head.author}\uFF09\u2014\u2014\u914D\u7F6E\u95EE\u9898\u8981\u54CD\u4EAE\uFF0C\u4E0D\u9759\u9ED8\u653E\u884C`
1752
- );
1752
+ if (!(configuredUnique.includes(head.author) && configuredUnique.length >= quorum)) {
1753
+ throw new KernelError(
1754
+ `\u6536\u5C3E gate \u65E0\u6CD5\u6EE1\u8DB3\uFF1A\u5408\u683C reviewer ${eligible.length} \u4EBA < quorum ${quorum}\uFF08reviewers=${eligible.join(",") || "none"}\uFF1Broles=${gateSpec.reviewers.join(",")}\uFF0C\u5DF2\u5254\u9664 head \u4F5C\u8005 ${head.author}\uFF09\u2014\u2014\u914D\u7F6E\u95EE\u9898\u8981\u54CD\u4EAE\uFF0C\u4E0D\u9759\u9ED8\u653E\u884C`
1755
+ );
1756
+ }
1757
+ } else {
1758
+ gate = { quorum, eligible };
1753
1759
  }
1754
- gate = { quorum, eligible };
1755
1760
  }
1756
1761
  const payload = {
1757
1762
  ...args.note !== void 0 ? { note: args.note } : {},
@@ -3303,14 +3308,26 @@ var init_dispatcher = __esm({
3303
3308
  ...this.opts.resolveActorContext !== void 0 ? { resolveActorContext: this.opts.resolveActorContext } : {},
3304
3309
  ...materializeSkills ? { materializeSkills } : {}
3305
3310
  });
3311
+ const limits = { wallClockMs: this.wallClockFor(spec.artifactId) };
3312
+ const credentialCtx = {
3313
+ actor: spec.actor,
3314
+ artifactId: spec.artifactId,
3315
+ action: spec.action,
3316
+ ...spec.part !== void 0 ? { part: spec.part } : {},
3317
+ ...binding !== void 0 ? { binding } : {},
3318
+ limits
3319
+ };
3320
+ const serverUrl = this.opts.serverUrlFor ? await this.opts.serverUrlFor(credentialCtx) : this.opts.serverUrl;
3321
+ const tokenFor = this.opts.tokenFor ?? ((a, _ctx) => `token:${a}`);
3322
+ const actorToken = tokenFor(spec.actor, credentialCtx);
3306
3323
  const provisioned = this.opts.provision ? await this.opts.provision(spec.actor) : void 0;
3307
3324
  const job = {
3308
3325
  actor: spec.actor,
3309
- actorToken: (this.opts.tokenFor ?? ((a) => `token:${a}`))(spec.actor),
3326
+ actorToken,
3310
3327
  artifactId: spec.artifactId,
3311
3328
  bundle: { files: bundle.files },
3312
- server: { url: this.opts.serverUrl },
3313
- limits: { wallClockMs: this.wallClockFor(spec.artifactId) },
3329
+ server: { url: serverUrl },
3330
+ limits,
3314
3331
  ...binding !== void 0 ? { binding } : {},
3315
3332
  ...provisioned?.env && Object.keys(provisioned.env).length > 0 ? { env: provisioned.env } : {},
3316
3333
  ...provisioned?.wrapperPaths && provisioned.wrapperPaths.length > 0 ? { wrapperPaths: provisioned.wrapperPaths } : {}
@@ -3333,6 +3350,7 @@ var init_dispatcher = __esm({
3333
3350
  artifactId: spec.artifactId,
3334
3351
  actor: spec.actor,
3335
3352
  action: spec.action,
3353
+ ...binding?.runtimeKind ? { runtimeKind: binding.runtimeKind } : {},
3336
3354
  bundleManifest: manifest,
3337
3355
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3338
3356
  };
@@ -3481,7 +3499,7 @@ var init_dispatcher = __esm({
3481
3499
  if (!artifact) return false;
3482
3500
  if (!artifact.owner.startsWith("actor:agent:")) return false;
3483
3501
  if (lifecycleOf(model, spec.artifactId) !== "active") return false;
3484
- if (isConcluded(model, spec.artifactId)) return false;
3502
+ if (isConcluded(model, spec.artifactId) && concludedHead(model, spec.artifactId) === artifact.currentRev) return false;
3485
3503
  if (pendingConcludeAttempt(model, spec.artifactId)) return false;
3486
3504
  if (!artifact.currentRev) return false;
3487
3505
  if (queueOf(model, spec.artifactId).length > 0) return false;
@@ -16436,7 +16454,7 @@ var require_compile = __commonJS({
16436
16454
  }
16437
16455
  }
16438
16456
  exports2.compileSchema = compileSchema;
16439
- function resolveRef(root, baseId, ref) {
16457
+ function resolveRef2(root, baseId, ref) {
16440
16458
  var _a;
16441
16459
  ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
16442
16460
  const schOrFunc = root.refs[ref];
@@ -16453,7 +16471,7 @@ var require_compile = __commonJS({
16453
16471
  return;
16454
16472
  return root.refs[ref] = inlineOrCompile.call(this, _sch);
16455
16473
  }
16456
- exports2.resolveRef = resolveRef;
16474
+ exports2.resolveRef = resolveRef2;
16457
16475
  function inlineOrCompile(sch) {
16458
16476
  if ((0, resolve_1.inlineRef)(sch.schema, this.opts.inlineRefs))
16459
16477
  return sch.schema;
@@ -20334,12 +20352,12 @@ var require_dist = __commonJS({
20334
20352
  throw new Error(`Unknown format "${name}"`);
20335
20353
  return f2;
20336
20354
  };
20337
- function addFormats(ajv, list, fs16, exportName) {
20355
+ function addFormats(ajv, list, fs17, exportName) {
20338
20356
  var _a;
20339
20357
  var _b;
20340
20358
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
20341
20359
  for (const f2 of list)
20342
- ajv.addFormat(f2, fs16[f2]);
20360
+ ajv.addFormat(f2, fs17[f2]);
20343
20361
  }
20344
20362
  module2.exports = exports2 = formatsPlugin;
20345
20363
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -20527,16 +20545,35 @@ var init_drafts = __esm({
20527
20545
  });
20528
20546
 
20529
20547
  // ../server/src/governance/workorder-drafts.ts
20530
- var WorkorderDraftStore;
20548
+ var import_node_fs, WorkorderDraftStore;
20531
20549
  var init_workorder_drafts = __esm({
20532
20550
  "../server/src/governance/workorder-drafts.ts"() {
20533
20551
  "use strict";
20552
+ import_node_fs = __toESM(require("node:fs"), 1);
20534
20553
  WorkorderDraftStore = class {
20554
+ constructor(file) {
20555
+ this.file = file;
20556
+ if (file && import_node_fs.default.existsSync(file)) {
20557
+ try {
20558
+ const data = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
20559
+ this.seq = data.seq ?? 0;
20560
+ for (const d of data.drafts ?? []) this.drafts.set(d.id, d);
20561
+ } catch {
20562
+ }
20563
+ }
20564
+ }
20535
20565
  drafts = /* @__PURE__ */ new Map();
20536
20566
  seq = 0;
20567
+ persist() {
20568
+ if (!this.file) return;
20569
+ const tmp = `${this.file}.tmp`;
20570
+ import_node_fs.default.writeFileSync(tmp, JSON.stringify({ seq: this.seq, drafts: [...this.drafts.values()] }));
20571
+ import_node_fs.default.renameSync(tmp, this.file);
20572
+ }
20537
20573
  submit(d) {
20538
20574
  const draft = { ...d, id: `wodraft:${++this.seq}`, at: (/* @__PURE__ */ new Date()).toISOString() };
20539
20575
  this.drafts.set(draft.id, draft);
20576
+ this.persist();
20540
20577
  return draft;
20541
20578
  }
20542
20579
  list() {
@@ -20546,16 +20583,159 @@ var init_workorder_drafts = __esm({
20546
20583
  forWorkspace(workspace) {
20547
20584
  return [...this.drafts.values()].reverse().find((d) => d.workspace === workspace);
20548
20585
  }
20586
+ /** 按 chat 会话取最新一份:agent 不自带 ws(后端生成)时,friday 据本会话 id 取回它刚建的草案。 */
20587
+ forSession(chatSessionId) {
20588
+ return [...this.drafts.values()].reverse().find((d) => d.chatSessionId === chatSessionId);
20589
+ }
20590
+ /** 给草案打上 chat 会话标(建单经 chat 路时,/api/cmd 拿到 X-Chat-Session-Id 后调)。 */
20591
+ tagSession(draftId, chatSessionId) {
20592
+ const d = this.drafts.get(draftId);
20593
+ if (d) {
20594
+ d.chatSessionId = chatSessionId;
20595
+ this.persist();
20596
+ }
20597
+ }
20549
20598
  get(id) {
20550
20599
  return this.drafts.get(id);
20551
20600
  }
20552
20601
  remove(id) {
20553
- return this.drafts.delete(id);
20602
+ const ok = this.drafts.delete(id);
20603
+ if (ok) this.persist();
20604
+ return ok;
20554
20605
  }
20555
20606
  };
20556
20607
  }
20557
20608
  });
20558
20609
 
20610
+ // ../server/src/governance/draft-edit.ts
20611
+ function resolveRef(ref, draft) {
20612
+ const res = resolveNodeRef(ref, candidatesOf(draft));
20613
+ if (res.ok) return res.id;
20614
+ if (res.reason === "ambiguous")
20615
+ throw new Error(`\u300C${ref}\u300D\u5728\u672C\u8349\u6848\u5339\u914D\u5230\u591A\u4E2A\u8282\u70B9\uFF0C\u8BF7\u66F4\u5177\u4F53\uFF08type:title \u6216 id\uFF09\uFF1A${res.candidates.map((c) => `${c.type}:${c.title ?? "?"}`).join("\u3001")}`);
20616
+ throw new Error(`\u300C${ref}\u300D\u5728\u672C\u8349\u6848\u91CC\u627E\u4E0D\u5230\u5BF9\u5E94\u8282\u70B9\uFF08\u53EF\u7528 type / type:title / id\uFF09`);
20617
+ }
20618
+ function editDraftPlan(draft, op) {
20619
+ switch (op.action) {
20620
+ case "spawn": {
20621
+ const title = op.title?.trim();
20622
+ if (!title) throw new Error("\u7ED9\u8349\u6848\u52A0\u8282\u70B9\u8981 --title\uFF08\u636E (type,title) \u6D3E\u751F id\uFF09");
20623
+ if (candidatesOf(draft).some((c) => c.type === op.type && (c.title ?? "").toLowerCase() === title.toLowerCase()))
20624
+ throw new Error(`\u8282\u70B9 title \u649E\u540D\uFF1A${op.type}\u300C${title}\u300D\u8349\u6848\u91CC\u5DF2\u6709\uFF0C\u6362\u4E2A\u533A\u5206\u7684\u540D`);
20625
+ const used = new Set(spawnOps(draft).map((o) => o.id));
20626
+ const id = deriveArtifactId(op.type, draft.workspace, title, used);
20627
+ const inputs = op.inputs?.map((e) => ({ to: resolveRef(e.to, draft), ...e.required !== void 0 ? { required: e.required } : {} }));
20628
+ draft.plan.ops.push({
20629
+ action: "spawn",
20630
+ id,
20631
+ type: op.type,
20632
+ workspace: draft.workspace,
20633
+ title,
20634
+ ...op.description !== void 0 ? { description: op.description } : {},
20635
+ ...op.fields ? { fields: op.fields } : {},
20636
+ ...inputs && inputs.length > 0 ? { inputs } : {}
20637
+ });
20638
+ if (op.description) draft.plan.ops.push({ action: "annotate", artifactId: id, body: op.description, blocks: false });
20639
+ break;
20640
+ }
20641
+ case "edit": {
20642
+ const id = resolveRef(op.artifactId, draft);
20643
+ const target = spawnOps(draft).find((o) => o.id === id);
20644
+ if (!target) throw new Error(`\u8349\u6848\u91CC\u6CA1\u6709\u8282\u70B9 ${id} \u7684 spawn`);
20645
+ if (op.title !== void 0) target.title = op.title;
20646
+ if (op.fields) target.fields = { ...target.fields ?? {}, ...op.fields };
20647
+ if (op.description !== void 0) {
20648
+ target.description = op.description;
20649
+ const ann = draft.plan.ops.find((o) => o.action === "annotate" && o.artifactId === id);
20650
+ if (ann && ann.action === "annotate") ann.body = op.description;
20651
+ else draft.plan.ops.push({ action: "annotate", artifactId: id, body: op.description, blocks: false });
20652
+ }
20653
+ break;
20654
+ }
20655
+ case "link": {
20656
+ const consumer = resolveRef(op.artifactId, draft);
20657
+ const upstream = resolveRef(op.to, draft);
20658
+ draft.plan.ops.push({ action: "link", artifactId: consumer, to: upstream, ...op.required !== void 0 ? { required: op.required } : {} });
20659
+ break;
20660
+ }
20661
+ case "unlink": {
20662
+ const consumer = resolveRef(op.artifactId, draft);
20663
+ const upstream = resolveRef(op.to, draft);
20664
+ draft.plan.ops = draft.plan.ops.filter((o) => !(o.action === "link" && o.artifactId === consumer && o.to === upstream));
20665
+ for (const o of draft.plan.ops) if (o.action === "spawn" && o.id === consumer && o.inputs) o.inputs = o.inputs.filter((e) => e.to !== upstream);
20666
+ break;
20667
+ }
20668
+ case "seal": {
20669
+ if (op.reason !== "cancelled") throw new Error(`\u8349\u6848\u8282\u70B9\u53EA\u652F\u6301 cancel\uFF08\u5220\u9664\uFF09\uFF0C\u4E0D\u652F\u6301 ${op.reason}\u2014\u2014\u8282\u70B9\u8FD8\u6CA1\u843D\u5E93`);
20670
+ const id = resolveRef(op.artifactId, draft);
20671
+ draft.plan.ops = draft.plan.ops.filter((o) => {
20672
+ if (o.action === "spawn" && o.id === id) return false;
20673
+ if (o.action === "annotate" && o.artifactId === id) return false;
20674
+ if (o.action === "link" && (o.artifactId === id || o.to === id)) return false;
20675
+ return true;
20676
+ });
20677
+ for (const o of draft.plan.ops) if (o.action === "spawn" && o.inputs) o.inputs = o.inputs.filter((e) => e.to !== id);
20678
+ break;
20679
+ }
20680
+ default:
20681
+ throw new Error(`\u8349\u6848\u7F16\u8F91\u6682\u4E0D\u652F\u6301 ${op.action}\uFF08\u8349\u6848\u9636\u6BB5\u53EA\u652F\u6301 spawn / edit / link / unlink / cancel\uFF09`);
20682
+ }
20683
+ }
20684
+ var spawnOps, candidatesOf;
20685
+ var init_draft_edit = __esm({
20686
+ "../server/src/governance/draft-edit.ts"() {
20687
+ "use strict";
20688
+ init_src2();
20689
+ spawnOps = (draft) => draft.plan.ops.filter((o) => o.action === "spawn" && !!o.id);
20690
+ candidatesOf = (draft) => spawnOps(draft).map((o) => ({ id: o.id, type: o.type, ...o.title ? { title: o.title } : {} }));
20691
+ }
20692
+ });
20693
+
20694
+ // ../server/src/coordinator/identity.ts
20695
+ function isCoordinatorAgentName(name) {
20696
+ return COORDINATOR_NAME_RE.test(name);
20697
+ }
20698
+ function isActiveCoordinatorAgent(actor) {
20699
+ return actor.kind === "agent" && actor.status === "active" && isCoordinatorAgentName(actor.name);
20700
+ }
20701
+ async function listActiveCoordinatorAgents(registry2) {
20702
+ if (!registry2) return [];
20703
+ return (await registry2.listActors({ kind: "agent", status: "active" })).filter(isActiveCoordinatorAgent);
20704
+ }
20705
+ async function resolveCoordinatorRequirement(registry2) {
20706
+ const candidates = await listActiveCoordinatorAgents(registry2);
20707
+ const actor = candidates[0];
20708
+ if (!actor) {
20709
+ return {
20710
+ ok: false,
20711
+ code: "coordinator_missing",
20712
+ message: "\u7CFB\u7EDF\u7F3A\u5C11\u534F\u8C03\u8005\u667A\u80FD\u4F53\u3002\u8BF7\u5148\u521B\u5EFA\u4E00\u4E2A\u540D\u79F0\u5305\u542B\u201C\u534F\u8C03\u8005\u201D\u6216\u201Ccoordinator\u201D\u7684\u6D3B\u8DC3\u667A\u80FD\u4F53\uFF0C\u5E76\u7ED1\u5B9A\u53EF\u7528\u8FD0\u884C\u65F6\u3002"
20713
+ };
20714
+ }
20715
+ const binding = await registry2.getBinding(actor.id);
20716
+ if (!binding || binding.status !== "active") {
20717
+ return {
20718
+ ok: false,
20719
+ actor,
20720
+ code: "coordinator_binding_missing",
20721
+ message: `${actor.name}\uFF08${actor.id}\uFF09\u7F3A\u5C11 active runtime \u7ED1\u5B9A\u3002\u534F\u8C03\u8005\u65E0\u6CD5\u5904\u7406\u540E\u7EED gap / \u6B7B\u9501 / \u6539\u56FE\u6D41\u7A0B\u3002`
20722
+ };
20723
+ }
20724
+ return { ok: true, actor, binding };
20725
+ }
20726
+ async function resolveCoordinator(registry2) {
20727
+ const resolved = await resolveCoordinatorRequirement(registry2);
20728
+ return resolved.ok && resolved.actor && resolved.binding ? { actor: resolved.actor, binding: resolved.binding } : null;
20729
+ }
20730
+ var LEGACY_COORDINATOR_ACTOR, COORDINATOR_NAME_RE;
20731
+ var init_identity = __esm({
20732
+ "../server/src/coordinator/identity.ts"() {
20733
+ "use strict";
20734
+ LEGACY_COORDINATOR_ACTOR = "actor:agent:coordinator";
20735
+ COORDINATOR_NAME_RE = /协调者|coordinator/i;
20736
+ }
20737
+ });
20738
+
20559
20739
  // ../server/src/domains/collab/planner.ts
20560
20740
  function ensureDefaultWorkorderTypes(schema) {
20561
20741
  const out = [...schema];
@@ -20576,6 +20756,46 @@ function upgradeDefaultFieldDefs(schema) {
20576
20756
  }
20577
20757
  return changed;
20578
20758
  }
20759
+ function isPlannerCoordinator(actor) {
20760
+ return actor.kind === "agent" && (actor.roles.includes("coordinator") || isCoordinatorAgentName(actor.name));
20761
+ }
20762
+ async function plannerAvailableActors(registry2) {
20763
+ if (!registry2) return [];
20764
+ const active = await registry2.listActors({ status: "active" });
20765
+ const out = [];
20766
+ for (const actor of active) {
20767
+ if (isPlannerCoordinator(actor)) continue;
20768
+ const roles = actor.roles.filter((role) => role !== "coordinator");
20769
+ if (roles.length === 0) continue;
20770
+ if (actor.kind === "agent") {
20771
+ const binding = await registry2.getBinding(actor.id);
20772
+ if (!binding || binding.status !== "active") continue;
20773
+ }
20774
+ out.push({
20775
+ id: actor.id,
20776
+ name: actor.name,
20777
+ roles,
20778
+ kind: actor.kind
20779
+ });
20780
+ }
20781
+ return out;
20782
+ }
20783
+ async function plannerArtifactTypes(schema, registry2) {
20784
+ if (!registry2) {
20785
+ return schema.filter((def) => def.name !== "brief").map((def) => ({
20786
+ name: def.name,
20787
+ ownerRole: def.ownerRole,
20788
+ contentType: def.contentType
20789
+ }));
20790
+ }
20791
+ const actors = await plannerAvailableActors(registry2);
20792
+ const staffableRoles = new Set(actors.flatMap((actor) => actor.roles));
20793
+ return schema.filter((def) => def.name !== "brief" && staffableRoles.has(def.ownerRole)).map((def) => ({
20794
+ name: def.name,
20795
+ ownerRole: def.ownerRole,
20796
+ contentType: def.contentType
20797
+ }));
20798
+ }
20579
20799
  function parseWorkorderSpec(body) {
20580
20800
  const first = body.split("\n", 1)[0] ?? "";
20581
20801
  if (!first.startsWith(WORKORDER_SPEC_PREFIX)) return null;
@@ -20602,6 +20822,8 @@ async function buildDynamicWorkorderPlan(input) {
20602
20822
  const primaryType = schemaByName.has(input.requestedType) && input.requestedType !== "brief" ? input.requestedType : "prd";
20603
20823
  const primaryId = input.requestedArtifactId && input.requestedType === primaryType ? input.requestedArtifactId : `artifact:${primaryType}:${slug4}`;
20604
20824
  const issues = [...input.agentIssues ?? []];
20825
+ const enforceStaffing = Boolean(input.registry);
20826
+ const staffableTypes = new Set((await plannerArtifactTypes(schema, input.registry)).map((def) => def.name));
20605
20827
  const roleCache = /* @__PURE__ */ new Map();
20606
20828
  const resolveRole = async (role, artifactType) => {
20607
20829
  const hit = roleCache.get(role);
@@ -20653,6 +20875,7 @@ async function buildDynamicWorkorderPlan(input) {
20653
20875
  continue;
20654
20876
  }
20655
20877
  const owner = await resolveRole(def.ownerRole, node.type);
20878
+ if (enforceStaffing && (!owner.actor || !staffableTypes.has(node.type))) continue;
20656
20879
  const ownerActor = owner.actor ?? `pending:role:${def.ownerRole}`;
20657
20880
  availableNodes.push({
20658
20881
  type: node.type,
@@ -20674,6 +20897,7 @@ async function buildDynamicWorkorderPlan(input) {
20674
20897
  }
20675
20898
  const owner = await resolveRole(def.ownerRole, type);
20676
20899
  const ownerActor = owner.actor ?? (type === primaryType ? input.actor : `pending:role:${def.ownerRole}`);
20900
+ if (enforceStaffing && !owner.actor && type !== primaryType) continue;
20677
20901
  const id = type === primaryType ? primaryId : `artifact:${type}:${slug4}`;
20678
20902
  availableNodes.push({ type, id, role: def.ownerRole, owner: ownerActor });
20679
20903
  }
@@ -20980,6 +21204,7 @@ var init_planner = __esm({
20980
21204
  "../server/src/domains/collab/planner.ts"() {
20981
21205
  "use strict";
20982
21206
  init_src2();
21207
+ init_identity();
20983
21208
  DEFAULT_TYPES = [
20984
21209
  // brief 的 goal/acceptanceCriteria 现在是【自定义字段】(Phase 3,替代旧 OASIS_WORKORDER_SPEC 批注,铲 #1 死锁根)
20985
21210
  { name: "brief", contentType: "text", ownerRole: "pm", fieldDefs: [
@@ -21265,8 +21490,19 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
21265
21490
  if (createdViaArg !== void 0 && createdViaArg !== "seed" && createdViaArg !== "manual-spawn") {
21266
21491
  throw new Error(`createdVia \u4EC5\u63A5\u53D7 seed\uFF08\u53D1\u8D77\u5DE5\u5355\uFF09\u6216 manual-spawn\uFF1Bauto-trigger/prereq \u7531\u5185\u6838\u5185\u90E8\u8BBE\u7F6E`);
21267
21492
  }
21268
- const workspace = optStr(args, "workspace") ?? "ws:default";
21493
+ let workspace = optStr(args, "workspace");
21494
+ if (workspace === void 0) {
21495
+ workspace = createdViaArg === "seed" ? `ws:wo-${(await import("node:crypto")).randomUUID().slice(0, 8)}` : "ws:default";
21496
+ }
21269
21497
  const projectId = optStr(args, "project_id") ?? optStr(args, "projectId");
21498
+ const schemaForSeed = (createdViaArg ?? "manual-spawn") === "seed" ? ctx.schema : void 0;
21499
+ let coordinatorRequirement;
21500
+ if (schemaForSeed) {
21501
+ coordinatorRequirement = await resolveCoordinatorRequirement(ctx.registry);
21502
+ if (!coordinatorRequirement.ok && args["allowMissingCoordinator"] !== true) {
21503
+ throw new CoordinatorRequiredError(coordinatorRequirement);
21504
+ }
21505
+ }
21270
21506
  if (projectId !== void 0) {
21271
21507
  if (!ctx.projectState || !ctx.artifactState) {
21272
21508
  throw new Error("spawn \u5E26 project_id \u9700\u8981\u670D\u52A1\u7AEF\u6CE8\u5165\u9879\u76EE\u5B58\u50A8\u4E0E\u5DE5\u5355\u7ED1\u5B9A\u5B58\u50A8");
@@ -21275,7 +21511,7 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
21275
21511
  if (!project) throw new Error(`project not found: ${projectId}`);
21276
21512
  await ctx.artifactState.upsertWorkspaceBinding({ workOrderId: workspace, projectId });
21277
21513
  }
21278
- if ((createdViaArg ?? "manual-spawn") === "seed" && ctx.schema) {
21514
+ if (schemaForSeed) {
21279
21515
  const title = optStr(args, "title") ?? optStr(args, "description") ?? workspace;
21280
21516
  const brief = optStr(args, "brief") ?? optStr(args, "body") ?? optStr(args, "description") ?? workspace;
21281
21517
  const goal = optStr(args, "goal");
@@ -21286,6 +21522,14 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
21286
21522
  \u9A8C\u6536\u6807\u51C6\uFF1A
21287
21523
  ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
21288
21524
  const agentIssues = [];
21525
+ if (coordinatorRequirement && !coordinatorRequirement.ok) {
21526
+ agentIssues.push({
21527
+ code: "missing_coordinator",
21528
+ severity: "warning",
21529
+ role: "coordinator",
21530
+ message: `${coordinatorRequirement.message} \u7528\u6237\u5DF2\u786E\u8BA4\u7EE7\u7EED\u521B\u5EFA\uFF1B\u540E\u7EED gap / \u6B7B\u9501 / \u6539\u56FE\u6D41\u7A0B\u53EF\u80FD\u65E0\u6CD5\u81EA\u52A8\u5904\u7406\u3002`
21531
+ });
21532
+ }
21289
21533
  let agentPlan = null;
21290
21534
  const inlinePlan = args["plan"];
21291
21535
  if (inlinePlan !== void 0 && inlinePlan !== null) {
@@ -21296,22 +21540,14 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
21296
21540
  }
21297
21541
  if (agentPlan === null && ctx.planWorkorderWithAgent) {
21298
21542
  try {
21299
- const availableActors = ctx.registry ? (await ctx.registry.listActors({ status: "active" })).map((a) => ({
21300
- id: a.id,
21301
- name: a.name,
21302
- roles: a.roles,
21303
- kind: a.kind
21304
- })) : [];
21543
+ const availableActors = await plannerAvailableActors(ctx.registry);
21544
+ const artifactTypes = await plannerArtifactTypes(schemaForSeed, ctx.registry);
21305
21545
  const attempt = await ctx.planWorkorderWithAgent({
21306
21546
  requestedType: type,
21307
21547
  workspace,
21308
21548
  title,
21309
21549
  brief: briefForPlanner,
21310
- artifactTypes: ctx.schema.map((def) => ({
21311
- name: def.name,
21312
- ownerRole: def.ownerRole,
21313
- contentType: def.contentType
21314
- })),
21550
+ artifactTypes,
21315
21551
  availableActors
21316
21552
  });
21317
21553
  if (attempt) {
@@ -21342,7 +21578,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
21342
21578
  ...goal !== void 0 ? { goal } : {},
21343
21579
  ...acceptanceCriteria && acceptanceCriteria.length > 0 ? { acceptanceCriteria } : {},
21344
21580
  actor,
21345
- schema: ctx.schema,
21581
+ schema: schemaForSeed,
21346
21582
  ...optStr(args, "id") !== void 0 ? { requestedArtifactId: optStr(args, "id") } : {},
21347
21583
  ...ctx.registry ? { registry: ctx.registry } : {},
21348
21584
  ...agentPlan ? { agentPlan } : {},
@@ -21739,8 +21975,9 @@ async function runView(kernel, oplog, blobs, name, artifactId, params) {
21739
21975
  case "ls":
21740
21976
  return [...model.artifacts.values()].map((a) => {
21741
21977
  const lifecycle = lifecycleOf(model, a.id);
21742
- const mark = lifecycle !== "active" ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : isConcluded(model, a.id) ? "concluded" : a.currentRev ? "active" : "embryo";
21743
- return { ...a, queue: queueOf(model, a.id).length, mark };
21978
+ const queue = queueOf(model, a.id).length;
21979
+ const mark = lifecycle !== "active" ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : isConcluded(model, a.id) ? "concluded" : a.currentRev || queue > 0 ? "active" : "embryo";
21980
+ return { ...a, queue, mark };
21744
21981
  });
21745
21982
  case "show": {
21746
21983
  const id = need2();
@@ -21842,6 +22079,7 @@ async function runView(kernel, oplog, blobs, name, artifactId, params) {
21842
22079
  async function startOasisServer(opts) {
21843
22080
  const resolveActor = opts.resolveActor ?? defaultResolveActor;
21844
22081
  const stagingBuffers = /* @__PURE__ */ new Map();
22082
+ const stagingWorkspace = /* @__PURE__ */ new Map();
21845
22083
  const resolveEngine = opts.resolveEngine ?? (async () => ({ kernel: opts.kernel, oplog: opts.oplog, blobs: opts.blobs, ...opts.registry ? { registry: opts.registry } : {} }));
21846
22084
  const domainRouter = new Router();
21847
22085
  for (const register of opts.domains ?? []) register(domainRouter);
@@ -21924,6 +22162,10 @@ async function startOasisServer(opts) {
21924
22162
  }));
21925
22163
  return;
21926
22164
  }
22165
+ if (url.pathname === "/api/whoami" && req.method === "GET") {
22166
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ actor }));
22167
+ return;
22168
+ }
21927
22169
  const sessionKey = (req.headers.authorization ?? "").startsWith("Bearer ") ? req.headers.authorization.slice("Bearer ".length) : actor;
21928
22170
  const chunks = [];
21929
22171
  for await (const chunk of req) chunks.push(chunk);
@@ -21945,8 +22187,38 @@ async function startOasisServer(opts) {
21945
22187
  ...opts.workorderDrafts ? { workorderDrafts: opts.workorderDrafts } : {},
21946
22188
  ...opts.gitEnv ? { gitEnv: opts.gitEnv } : {}
21947
22189
  });
22190
+ if (body.command === "spawn" && opts.chatSession) {
22191
+ const workspace = body.args?.["workspace"];
22192
+ if (workspace && workspace !== "ws:default") {
22193
+ const chatSid = req.headers["x-chat-session-id"];
22194
+ if (typeof chatSid === "string") {
22195
+ await opts.chatSession.linkWorkOrder(chatSid, workspace).catch((err) => {
22196
+ console.error(`[chat-session] Failed to link work order ${workspace} to session ${chatSid}:`, err);
22197
+ });
22198
+ }
22199
+ }
22200
+ }
22201
+ if (body.command === "spawn" && opts.workorderDrafts) {
22202
+ const draftId = result?.data?.draftId;
22203
+ const chatSid = req.headers["x-chat-session-id"];
22204
+ if (draftId && typeof chatSid === "string") opts.workorderDrafts.tagSession(draftId, chatSid);
22205
+ }
21948
22206
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
21949
22207
  } catch (err) {
22208
+ if (err instanceof CoordinatorRequiredError) {
22209
+ res.writeHead(err.status, { "content-type": "application/json" }).end(JSON.stringify({
22210
+ error: {
22211
+ code: err.code,
22212
+ message: err.message,
22213
+ canContinueWithOverride: true,
22214
+ overrideField: "allowMissingCoordinator",
22215
+ overrideValue: true,
22216
+ reason: err.requirement.code,
22217
+ coordinatorActorId: err.requirement.actor?.id
22218
+ }
22219
+ }));
22220
+ return;
22221
+ }
21950
22222
  const status = err instanceof FrozenError ? 409 : 400;
21951
22223
  res.writeHead(status, { "content-type": "application/json" }).end(
21952
22224
  JSON.stringify({
@@ -21983,7 +22255,8 @@ async function startOasisServer(opts) {
21983
22255
  }
21984
22256
  if (url.pathname === "/api/workorder/draft" && req.method === "GET" && opts.workorderDrafts) {
21985
22257
  const workspace = url.searchParams.get("workspace");
21986
- const draft = workspace ? opts.workorderDrafts.forWorkspace(workspace) : void 0;
22258
+ const session = url.searchParams.get("session");
22259
+ const draft = workspace ? opts.workorderDrafts.forWorkspace(workspace) : session ? opts.workorderDrafts.forSession(session) : void 0;
21987
22260
  if (!draft) {
21988
22261
  res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "no staged workorder draft" }));
21989
22262
  return;
@@ -22005,6 +22278,11 @@ async function startOasisServer(opts) {
22005
22278
  if (body.dispatch === false && brief?.workspace) {
22006
22279
  await opts.artifactState?.setWorkspaceDispatchHold(brief.workspace, true);
22007
22280
  }
22281
+ if (opts.chatSession && body.chatSessionId && brief?.workspace && brief.workspace !== "ws:default") {
22282
+ await opts.chatSession.linkWorkOrder(body.chatSessionId, brief.workspace).catch((err) => {
22283
+ console.error(`[chat-session] Failed to link work order ${brief.workspace} to session ${body.chatSessionId}:`, err);
22284
+ });
22285
+ }
22008
22286
  res.writeHead(200, { "content-type": "application/json" }).end(
22009
22287
  JSON.stringify({ ...result, briefArtifactId: body.briefArtifactId, workspace: brief?.workspace ?? null })
22010
22288
  );
@@ -22089,11 +22367,29 @@ async function startOasisServer(opts) {
22089
22367
  }
22090
22368
  if (url.pathname === "/api/stage" && req.method === "POST") {
22091
22369
  try {
22092
- const { op } = JSON.parse(rawBody);
22370
+ const { op, workspace } = JSON.parse(rawBody);
22093
22371
  if (!op || typeof op.action !== "string") throw new Error("\u7F3A\u5C11 op\uFF08{action,...}\uFF09");
22372
+ if (opts.workorderDrafts) {
22373
+ let ws = typeof workspace === "string" && workspace.trim() ? workspace.trim() : void 0;
22374
+ if (!ws) {
22375
+ const ref = op.artifactId;
22376
+ if (typeof ref === "string" && ref.startsWith("artifact:"))
22377
+ ws = opts.workorderDrafts.list().find((d) => d.plan.ops.some((o) => o.action === "spawn" && o.id === ref))?.workspace;
22378
+ }
22379
+ const draft = ws ? opts.workorderDrafts.forWorkspace(ws) : void 0;
22380
+ if (draft) {
22381
+ editDraftPlan(draft, op);
22382
+ draft.at = (/* @__PURE__ */ new Date()).toISOString();
22383
+ res.writeHead(200, { "content-type": "application/json" }).end(
22384
+ JSON.stringify({ draft: true, workspace: draft.workspace, nodes: draft.plan.ops.filter((o) => o.action === "spawn").length, staged: op })
22385
+ );
22386
+ return;
22387
+ }
22388
+ }
22094
22389
  const buf = stagingBuffers.get(sessionKey) ?? [];
22095
22390
  buf.push(op);
22096
22391
  stagingBuffers.set(sessionKey, buf);
22392
+ if (typeof workspace === "string" && workspace.trim()) stagingWorkspace.set(sessionKey, workspace.trim());
22097
22393
  const cascade = op.action === "seal" && op.reason === "cancelled" ? engine.kernel.cancelCascadeEdges(op.artifactId) : void 0;
22098
22394
  res.writeHead(200, { "content-type": "application/json" }).end(
22099
22395
  JSON.stringify({ count: buf.length, staged: op, ...cascade && cascade.length > 0 ? { cascade } : {} })
@@ -22107,10 +22403,11 @@ async function startOasisServer(opts) {
22107
22403
  }
22108
22404
  if (url.pathname === "/api/stage" && req.method === "GET") {
22109
22405
  const ops = stagingBuffers.get(sessionKey) ?? [];
22406
+ const ws = stagingWorkspace.get(sessionKey);
22110
22407
  let preview2 = null;
22111
22408
  if (ops.length > 0) {
22112
22409
  try {
22113
- preview2 = await engine.kernel.previewIntervention({ reason: "(staged)", ops });
22410
+ preview2 = await engine.kernel.previewIntervention({ reason: "(staged)", ops, ...ws ? { workspace: ws } : {} });
22114
22411
  } catch (err) {
22115
22412
  preview2 = { error: err instanceof Error ? err.message : String(err) };
22116
22413
  }
@@ -22120,6 +22417,7 @@ async function startOasisServer(opts) {
22120
22417
  }
22121
22418
  if (url.pathname === "/api/stage" && req.method === "DELETE") {
22122
22419
  stagingBuffers.delete(sessionKey);
22420
+ stagingWorkspace.delete(sessionKey);
22123
22421
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ cleared: true }));
22124
22422
  return;
22125
22423
  }
@@ -22131,7 +22429,8 @@ async function startOasisServer(opts) {
22131
22429
  return;
22132
22430
  }
22133
22431
  const { reason } = JSON.parse(rawBody || "{}");
22134
- const plan = { reason: reason?.trim() || "(\u534F\u8C03\u8005 staged \u6539\u56FE)", ops };
22432
+ const ws = stagingWorkspace.get(sessionKey);
22433
+ const plan = { reason: reason?.trim() || "(\u534F\u8C03\u8005 staged \u6539\u56FE)", ops, ...ws ? { workspace: ws } : {} };
22135
22434
  const preview2 = await engine.kernel.previewIntervention(plan);
22136
22435
  if (!preview2.endState.ok) {
22137
22436
  throw new Error(`\u7EC8\u6001\u6821\u9A8C\u4E0D\u8FC7\uFF08\u673A\u68B0\u786C\u62E6\uFF09\uFF1A${preview2.endState.violations.join("\uFF1B")}`);
@@ -22139,6 +22438,7 @@ async function startOasisServer(opts) {
22139
22438
  if (preview2.changeClass === "B") {
22140
22439
  const result = await engine.kernel.applyIntervention(plan, preview2.baseSeqs, actor);
22141
22440
  stagingBuffers.delete(sessionKey);
22441
+ stagingWorkspace.delete(sessionKey);
22142
22442
  res.writeHead(200, { "content-type": "application/json" }).end(
22143
22443
  JSON.stringify({ outcome: "applied", applied: result.applied, interventionId: result.interventionId })
22144
22444
  );
@@ -22154,6 +22454,7 @@ async function startOasisServer(opts) {
22154
22454
  downstreamClosure: preview2.blastRadius.downstreamClosure
22155
22455
  });
22156
22456
  stagingBuffers.delete(sessionKey);
22457
+ stagingWorkspace.delete(sessionKey);
22157
22458
  res.writeHead(200, { "content-type": "application/json" }).end(
22158
22459
  JSON.stringify({ outcome: "drafted", draftId: draft.id, changeClass: draft.changeClass, nonMonotonicOps: draft.nonMonotonicOps })
22159
22460
  );
@@ -22249,10 +22550,33 @@ async function startOasisServer(opts) {
22249
22550
  }
22250
22551
  return;
22251
22552
  }
22553
+ if (url.pathname === "/api/chat/attachments" && req.method === "POST" && opts.uploadChatAttachment) {
22554
+ try {
22555
+ const body = JSON.parse(rawBody);
22556
+ if (typeof body.contentBase64 !== "string" || !body.contentBase64) throw new Error("\u7F3A\u5C11 contentBase64");
22557
+ const out = await opts.uploadChatAttachment({
22558
+ contentBase64: body.contentBase64,
22559
+ ...typeof body.contentType === "string" ? { contentType: body.contentType } : {},
22560
+ ...typeof body.name === "string" ? { name: body.name } : {}
22561
+ });
22562
+ res.writeHead(201, { "content-type": "application/json" });
22563
+ res.end(JSON.stringify(out));
22564
+ } catch (err) {
22565
+ res.writeHead(400, { "content-type": "application/json" });
22566
+ res.end(JSON.stringify({ error: { code: "ATTACHMENT_UPLOAD_FAILED", message: err instanceof Error ? err.message : String(err) } }));
22567
+ }
22568
+ return;
22569
+ }
22252
22570
  if (url.pathname === "/api/chat" && req.method === "POST") {
22253
22571
  try {
22254
22572
  const body = JSON.parse(rawBody);
22255
22573
  if (!body.message) throw new Error("\u7F3A\u5C11 message");
22574
+ const attachments = Array.isArray(body.attachments) ? body.attachments.filter((a) => typeof a?.name === "string" && (typeof a?.text === "string" || typeof a?.blobRef === "string")).map((a) => ({
22575
+ name: a.name,
22576
+ ...typeof a.text === "string" ? { text: a.text } : {},
22577
+ ...typeof a.blobRef === "string" ? { blobRef: a.blobRef } : {},
22578
+ ...typeof a.contentType === "string" ? { contentType: a.contentType } : {}
22579
+ })) : [];
22256
22580
  const typedParts = wantsChatParts(req);
22257
22581
  if (opts.dispatchChat) {
22258
22582
  if (!body.actorId) {
@@ -22261,7 +22585,9 @@ async function startOasisServer(opts) {
22261
22585
  const session = await opts.dispatchChat({
22262
22586
  actorId: body.actorId,
22263
22587
  message: body.message,
22264
- ...body.sessionId ? { sessionId: body.sessionId } : {}
22588
+ ...body.sessionId ? { sessionId: body.sessionId } : {},
22589
+ ...body.chatSessionId ? { chatSessionId: body.chatSessionId } : {},
22590
+ ...attachments.length ? { attachments } : {}
22265
22591
  });
22266
22592
  let finished = false;
22267
22593
  res.on("close", () => {
@@ -22277,8 +22603,9 @@ async function startOasisServer(opts) {
22277
22603
  res.writeHead(200, {
22278
22604
  "content-type": "application/x-ndjson; charset=utf-8",
22279
22605
  "transfer-encoding": "chunked",
22280
- "access-control-expose-headers": "X-Session-Id",
22281
- "x-session-id": session.id
22606
+ "access-control-expose-headers": "X-Session-Id, X-Run-Id",
22607
+ "x-session-id": session.id,
22608
+ ...session.runId ? { "x-run-id": session.runId } : {}
22282
22609
  });
22283
22610
  let sawTextOutput = false;
22284
22611
  session.onOutput((chunk) => {
@@ -22382,7 +22709,7 @@ async function startOasisServer(opts) {
22382
22709
  ];
22383
22710
  const child = spawn6(opts.claudeBin ?? "claude", args, {
22384
22711
  ...spawnCwd ? { cwd: spawnCwd } : {},
22385
- env: { ...process.env, ...env },
22712
+ env: { ...process.env, ...env, ...body.chatSessionId ? { OASIS_CHAT_SESSION_ID: body.chatSessionId } : {} },
22386
22713
  stdio: ["ignore", "pipe", "pipe"]
22387
22714
  });
22388
22715
  if (typedParts) {
@@ -22663,7 +22990,7 @@ async function startOasisServer(opts) {
22663
22990
  })
22664
22991
  };
22665
22992
  }
22666
- var http, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, GOVERNANCE_COMMANDS, isAgent;
22993
+ var http, CoordinatorRequiredError, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, GOVERNANCE_COMMANDS, isAgent;
22667
22994
  var init_server3 = __esm({
22668
22995
  "../server/src/server.ts"() {
22669
22996
  "use strict";
@@ -22675,7 +23002,17 @@ var init_server3 = __esm({
22675
23002
  init_router();
22676
23003
  init_drafts();
22677
23004
  init_workorder_drafts();
23005
+ init_draft_edit();
22678
23006
  init_planner();
23007
+ init_identity();
23008
+ CoordinatorRequiredError = class extends Error {
23009
+ constructor(requirement) {
23010
+ super(requirement.message ?? "\u7CFB\u7EDF\u7F3A\u5C11\u534F\u8C03\u8005\u667A\u80FD\u4F53\u3002");
23011
+ this.requirement = requirement;
23012
+ }
23013
+ status = 409;
23014
+ code = "COORDINATOR_REQUIRED";
23015
+ };
22679
23016
  defaultResolveActor = (token) => token.startsWith("token:") ? token.slice("token:".length) : null;
22680
23017
  enc = (s2) => new TextEncoder().encode(s2);
22681
23018
  CHAT_OUTPUT_LIMIT = 16e3;
@@ -22687,29 +23024,53 @@ var init_server3 = __esm({
22687
23024
  // ../server/src/tokens.ts
22688
23025
  function createTokenIssuer() {
22689
23026
  const table = /* @__PURE__ */ new Map();
23027
+ const prune = () => {
23028
+ const now = Date.now();
23029
+ for (const [token, entry] of table) {
23030
+ if (entry.expiresAt !== void 0 && entry.expiresAt <= now) table.delete(token);
23031
+ }
23032
+ };
22690
23033
  return {
22691
- issue(actor) {
23034
+ issue(actor, opts = {}) {
23035
+ prune();
22692
23036
  const token = `oat_${(0, import_node_crypto3.randomBytes)(24).toString("base64url")}`;
22693
- table.set(token, actor);
23037
+ const issuedAt = Date.now();
23038
+ table.set(token, {
23039
+ actor,
23040
+ issuedAt,
23041
+ ...opts.ttlMs !== void 0 ? { expiresAt: issuedAt + opts.ttlMs } : {},
23042
+ ...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
23043
+ ...opts.artifactId !== void 0 ? { artifactId: opts.artifactId } : {},
23044
+ ...opts.action !== void 0 ? { action: opts.action } : {},
23045
+ ...opts.nodeId !== void 0 ? { nodeId: opts.nodeId } : {},
23046
+ ...opts.runtimeKind !== void 0 ? { runtimeKind: opts.runtimeKind } : {},
23047
+ ...opts.scopes !== void 0 ? { scopes: opts.scopes } : {}
23048
+ });
22694
23049
  return token;
22695
23050
  },
22696
23051
  revoke(token) {
22697
23052
  table.delete(token);
22698
23053
  },
22699
23054
  resolve(token) {
22700
- return table.get(token) ?? null;
23055
+ const entry = table.get(token);
23056
+ if (!entry) return null;
23057
+ if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
23058
+ table.delete(token);
23059
+ return null;
23060
+ }
23061
+ return entry.actor;
22701
23062
  }
22702
23063
  };
22703
23064
  }
22704
23065
  function createNodeTokenStore(file) {
22705
23066
  const read = () => {
22706
23067
  try {
22707
- return JSON.parse(fs.readFileSync(file, "utf8"));
23068
+ return JSON.parse(fs2.readFileSync(file, "utf8"));
22708
23069
  } catch {
22709
23070
  return {};
22710
23071
  }
22711
23072
  };
22712
- const write = (table) => fs.writeFileSync(file, JSON.stringify(table, null, 2), { mode: 384 });
23073
+ const write = (table) => fs2.writeFileSync(file, JSON.stringify(table, null, 2), { mode: 384 });
22713
23074
  return {
22714
23075
  issue(nodeId) {
22715
23076
  const table = read();
@@ -22735,14 +23096,14 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
22735
23096
  const read = () => {
22736
23097
  if (!file) return table;
22737
23098
  try {
22738
- return new Map(Object.entries(JSON.parse(fs.readFileSync(file, "utf8"))));
23099
+ return new Map(Object.entries(JSON.parse(fs2.readFileSync(file, "utf8"))));
22739
23100
  } catch {
22740
23101
  return /* @__PURE__ */ new Map();
22741
23102
  }
22742
23103
  };
22743
23104
  const write = (m2) => {
22744
23105
  if (!file) return;
22745
- fs.writeFileSync(file, JSON.stringify(Object.fromEntries(m2), null, 2), { mode: 384 });
23106
+ fs2.writeFileSync(file, JSON.stringify(Object.fromEntries(m2), null, 2), { mode: 384 });
22746
23107
  };
22747
23108
  const table = /* @__PURE__ */ new Map();
22748
23109
  const prune = () => {
@@ -22785,12 +23146,12 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
22785
23146
  }
22786
23147
  };
22787
23148
  }
22788
- var import_node_crypto3, fs;
23149
+ var import_node_crypto3, fs2;
22789
23150
  var init_tokens = __esm({
22790
23151
  "../server/src/tokens.ts"() {
22791
23152
  "use strict";
22792
23153
  import_node_crypto3 = require("node:crypto");
22793
- fs = __toESM(require("node:fs"), 1);
23154
+ fs2 = __toESM(require("node:fs"), 1);
22794
23155
  }
22795
23156
  });
22796
23157
 
@@ -23904,17 +24265,25 @@ function nodeInfo(model, a) {
23904
24265
  const lifecycle = lifecycleOf(model, a.id);
23905
24266
  const sealed = lifecycle !== "active";
23906
24267
  const concluded = isConcluded(model, a.id);
23907
- const mark = sealed ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : concluded ? "concluded" : a.currentRev ? "active" : "embryo";
23908
- const hasWorking = revisionsOf(model, a.id).some((r) => r.state === "working");
23909
- return { mark, queueLen: queueOf(model, a.id).length, concluded, sealed, hasWorking };
24268
+ const queueLen = queueOf(model, a.id).length;
24269
+ const mark = sealed ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : concluded ? "concluded" : (
24270
+ // embryo 只判"还没动工":无定稿版**且**队列里也没有待审版。智能体已交首版排队待审
24271
+ // (queueLen>0)即已产出,按 active 走,由 deriveNodeStage 落到 review,别误判成 embryo。
24272
+ a.currentRev || queueLen > 0 ? "active" : "embryo"
24273
+ );
24274
+ const revisions = revisionsOf(model, a.id);
24275
+ const hasWorking = revisions.some((r) => r.state === "working");
24276
+ const producedParts = new Set(revisions.filter((r) => r.part !== void 0 && r.state !== "rejected").map((r) => r.part));
24277
+ const hasUnproducedParts = (a.parts ?? []).some((p2) => !producedParts.has(p2.name));
24278
+ return { mark, queueLen, concluded, sealed, hasWorking, hasUnproducedParts };
23910
24279
  }
23911
24280
  function deriveStage(members) {
23912
24281
  if (members.length > 0 && members.every((m2) => m2.sealed)) return "sealed";
23913
24282
  const live = members.filter((m2) => !m2.sealed);
23914
24283
  if (live.length > 0 && live.every((m2) => m2.concluded)) return "concluded";
23915
- const reviewing = live.some((m2) => m2.mark === "gate-pending" || m2.mark === "active" && m2.queueLen > 0);
24284
+ const reviewing = live.some((m2) => m2.mark === "gate-pending" || m2.mark === "active" && m2.queueLen > 0 && !m2.hasUnproducedParts);
23916
24285
  if (reviewing) return "review";
23917
- const progressing = live.some((m2) => m2.mark === "active" || m2.mark === "embryo");
24286
+ const progressing = live.some((m2) => m2.mark === "active" || m2.mark === "embryo" || m2.hasUnproducedParts);
23918
24287
  const blocked = live.some((m2) => m2.mark === "blocked");
23919
24288
  if (blocked && !progressing) return "blocked";
23920
24289
  return "executing";
@@ -23931,7 +24300,7 @@ function deriveNodeStage(info) {
23931
24300
  case "embryo":
23932
24301
  return "embryo";
23933
24302
  default:
23934
- if (info.hasWorking) return "editing";
24303
+ if (info.hasWorking || info.hasUnproducedParts) return "editing";
23935
24304
  if (info.queueLen > 0) return "review";
23936
24305
  return "merged";
23937
24306
  }
@@ -23969,6 +24338,8 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
23969
24338
  const lastOps = arts.map((a) => model.lastOpAt.get(a.id)).filter((t) => Boolean(t)).sort();
23970
24339
  const participantIds = [...new Set(arts.map((a) => a.owner))];
23971
24340
  const woTitle = seed.title ?? seed.description ?? workspace;
24341
+ const stage = deriveStage(members);
24342
+ const dispatchPaused = dispatchPausedOf?.(workspace) ?? false;
23972
24343
  out.push({
23973
24344
  id: workspace,
23974
24345
  title: woTitle,
@@ -23976,7 +24347,7 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
23976
24347
  ...seed.description && seed.description !== woTitle ? { description: seed.description } : {},
23977
24348
  // 一句话目标:优先取结构化 spec.goal;缺省时前端回退 description 首段(投影不替前端做回退,只给真值)
23978
24349
  ...spec?.goal ? { goal: spec.goal } : {},
23979
- stage: deriveStage(members),
24350
+ stage,
23980
24351
  owner: ref(seed.owner),
23981
24352
  participants: participantIds.map(ref),
23982
24353
  artifactCount: arts.length,
@@ -23984,7 +24355,7 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
23984
24355
  updatedAt: lastOps[lastOps.length - 1] ?? "",
23985
24356
  projectId: resolveProject?.(workspace) ?? null,
23986
24357
  planning: planningStatusOf(model, arts),
23987
- ...dispatchPausedOf?.(workspace) ? { dispatchPaused: true } : {}
24358
+ ...dispatchPaused ? { dispatchPaused: true } : {}
23988
24359
  });
23989
24360
  }
23990
24361
  return out.sort((a, b2) => a.updatedAt < b2.updatedAt ? 1 : a.updatedAt > b2.updatedAt ? -1 : 0);
@@ -24858,9 +25229,9 @@ var init_service = __esm({
24858
25229
  reason: input.revision.summary ?? input.revision.version
24859
25230
  });
24860
25231
  await this.syncProjection();
24861
- const record3 = await this.artifacts.getRevision(kernelRevision.id);
24862
- if (!record3) throw new Error(`revision projection missing after register: ${kernelRevision.id}`);
24863
- return record3;
25232
+ const record4 = await this.artifacts.getRevision(kernelRevision.id);
25233
+ if (!record4) throw new Error(`revision projection missing after register: ${kernelRevision.id}`);
25234
+ return record4;
24864
25235
  }
24865
25236
  async approveArtifactRevision(input) {
24866
25237
  if (input.actorKind !== "human") throw namedError("AgentReviewForbidden", "agents cannot approve artifact revisions");
@@ -24955,9 +25326,9 @@ var init_service = __esm({
24955
25326
  ...input.revisionId ? { revisionId: input.revisionId } : {}
24956
25327
  });
24957
25328
  await this.syncProjection();
24958
- const record3 = await this.artifacts.getAnnotation(kernelAnnotation.id);
24959
- if (!record3) throw new Error(`annotation projection missing after create: ${kernelAnnotation.id}`);
24960
- return record3;
25329
+ const record4 = await this.artifacts.getAnnotation(kernelAnnotation.id);
25330
+ if (!record4) throw new Error(`annotation projection missing after create: ${kernelAnnotation.id}`);
25331
+ return record4;
24961
25332
  }
24962
25333
  async resolveArtifactAnnotation(input) {
24963
25334
  const artifact = await this.artifacts.getArtifact(input.artifactId);
@@ -24973,9 +25344,9 @@ var init_service = __esm({
24973
25344
  ...input.comment?.trim() ? { note: input.comment.trim() } : {}
24974
25345
  });
24975
25346
  await this.syncProjection();
24976
- const record3 = await this.artifacts.getAnnotation(input.annotationId);
24977
- if (!record3) throw new Error(`annotation projection missing after resolve: ${input.annotationId}`);
24978
- return record3;
25347
+ const record4 = await this.artifacts.getAnnotation(input.annotationId);
25348
+ if (!record4) throw new Error(`annotation projection missing after resolve: ${input.annotationId}`);
25349
+ return record4;
24979
25350
  }
24980
25351
  async registerNodeOutput(input) {
24981
25352
  await this.assertProjectExecutable(input.projectId);
@@ -25204,12 +25575,12 @@ var init_service = __esm({
25204
25575
  });
25205
25576
 
25206
25577
  // ../server/src/dev-store.ts
25207
- var import_node_crypto4, fs2, path, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRegistryStore, FileProjectStateStore, FileProjectDocumentStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore;
25578
+ var import_node_crypto4, fs3, path, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRegistryStore, FileProjectStateStore, FileProjectDocumentStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore;
25208
25579
  var init_dev_store = __esm({
25209
25580
  "../server/src/dev-store.ts"() {
25210
25581
  "use strict";
25211
25582
  import_node_crypto4 = require("node:crypto");
25212
- fs2 = __toESM(require("node:fs"), 1);
25583
+ fs3 = __toESM(require("node:fs"), 1);
25213
25584
  path = __toESM(require("node:path"), 1);
25214
25585
  init_src();
25215
25586
  init_src3();
@@ -25222,8 +25593,8 @@ var init_dev_store = __esm({
25222
25593
  }
25223
25594
  static async open(file) {
25224
25595
  const memory = new MemoryOplogStore();
25225
- if (fs2.existsSync(file)) {
25226
- for (const line of fs2.readFileSync(file, "utf8").split("\n")) {
25596
+ if (fs3.existsSync(file)) {
25597
+ for (const line of fs3.readFileSync(file, "utf8").split("\n")) {
25227
25598
  if (!line.trim()) continue;
25228
25599
  const stored = JSON.parse(line);
25229
25600
  const { artifactId, seq, ...op } = stored;
@@ -25231,15 +25602,15 @@ var init_dev_store = __esm({
25231
25602
  if (assigned !== seq) throw new Error(`corrupt ndjson log: seq mismatch at ${line}`);
25232
25603
  }
25233
25604
  } else {
25234
- fs2.mkdirSync(path.dirname(file), { recursive: true });
25235
- fs2.writeFileSync(file, "");
25605
+ fs3.mkdirSync(path.dirname(file), { recursive: true });
25606
+ fs3.writeFileSync(file, "");
25236
25607
  }
25237
25608
  return new _NdjsonOplogStore(file, memory);
25238
25609
  }
25239
25610
  async append(artifactId, op, expectedSeq) {
25240
25611
  const seq = await this.memory.append(artifactId, op, expectedSeq);
25241
25612
  const stored = { ...op, artifactId, seq };
25242
- fs2.appendFileSync(this.file, JSON.stringify(stored) + "\n");
25613
+ fs3.appendFileSync(this.file, JSON.stringify(stored) + "\n");
25243
25614
  return seq;
25244
25615
  }
25245
25616
  read(artifactId, fromSeq) {
@@ -25255,7 +25626,7 @@ var init_dev_store = __esm({
25255
25626
  DirBlobStore = class {
25256
25627
  constructor(dir) {
25257
25628
  this.dir = dir;
25258
- fs2.mkdirSync(dir, { recursive: true });
25629
+ fs3.mkdirSync(dir, { recursive: true });
25259
25630
  }
25260
25631
  fileOf(hash) {
25261
25632
  return path.join(this.dir, hash);
@@ -25263,31 +25634,31 @@ var init_dev_store = __esm({
25263
25634
  async put(bytes) {
25264
25635
  const hash = (0, import_node_crypto4.createHash)("sha256").update(bytes).digest("hex");
25265
25636
  const file = this.fileOf(hash);
25266
- if (!fs2.existsSync(file)) fs2.writeFileSync(file, bytes);
25637
+ if (!fs3.existsSync(file)) fs3.writeFileSync(file, bytes);
25267
25638
  return hash;
25268
25639
  }
25269
25640
  async meta(hash) {
25270
25641
  const file = this.fileOf(hash);
25271
- if (!fs2.existsSync(file)) return null;
25272
- const size = fs2.statSync(file).size;
25273
- const fd = fs2.openSync(file, "r");
25642
+ if (!fs3.existsSync(file)) return null;
25643
+ const size = fs3.statSync(file).size;
25644
+ const fd = fs3.openSync(file, "r");
25274
25645
  const head = Buffer.alloc(16);
25275
- fs2.readSync(fd, head, 0, 16, 0);
25276
- fs2.closeSync(fd);
25646
+ fs3.readSync(fd, head, 0, 16, 0);
25647
+ fs3.closeSync(fd);
25277
25648
  const contentType = sniffContentType(new Uint8Array(head));
25278
25649
  return { size, ...contentType ? { contentType } : {} };
25279
25650
  }
25280
25651
  async get(hash) {
25281
25652
  const file = this.fileOf(hash);
25282
- if (!fs2.existsSync(file)) throw new BlobNotFoundError(hash);
25283
- return new Uint8Array(fs2.readFileSync(file));
25653
+ if (!fs3.existsSync(file)) throw new BlobNotFoundError(hash);
25654
+ return new Uint8Array(fs3.readFileSync(file));
25284
25655
  }
25285
25656
  async has(hash) {
25286
- return fs2.existsSync(this.fileOf(hash));
25657
+ return fs3.existsSync(this.fileOf(hash));
25287
25658
  }
25288
25659
  async sweep(reachable) {
25289
- for (const name of fs2.readdirSync(this.dir)) {
25290
- if (!reachable.has(name)) fs2.unlinkSync(this.fileOf(name));
25660
+ for (const name of fs3.readdirSync(this.dir)) {
25661
+ if (!reachable.has(name)) fs3.unlinkSync(this.fileOf(name));
25291
25662
  }
25292
25663
  }
25293
25664
  };
@@ -25313,7 +25684,7 @@ var init_dev_store = __esm({
25313
25684
  this.file = file;
25314
25685
  }
25315
25686
  static async open(file) {
25316
- const seed = fs2.existsSync(file) ? JSON.parse(fs2.readFileSync(file, "utf8")) : [];
25687
+ const seed = fs3.existsSync(file) ? JSON.parse(fs3.readFileSync(file, "utf8")) : [];
25317
25688
  return new _FileTypeRegistryStore(file, seed);
25318
25689
  }
25319
25690
  async put(def) {
@@ -25326,8 +25697,8 @@ var init_dev_store = __esm({
25326
25697
  }
25327
25698
  async save() {
25328
25699
  const tmp = `${this.file}.tmp`;
25329
- fs2.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
25330
- fs2.renameSync(tmp, this.file);
25700
+ fs3.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
25701
+ fs3.renameSync(tmp, this.file);
25331
25702
  }
25332
25703
  };
25333
25704
  FileRegistryStore = class _FileRegistryStore extends MemoryRegistryStore {
@@ -25345,8 +25716,8 @@ var init_dev_store = __esm({
25345
25716
  }
25346
25717
  static async open(file) {
25347
25718
  const store = new _FileRegistryStore(file);
25348
- if (fs2.existsSync(file)) {
25349
- const snap = JSON.parse(fs2.readFileSync(file, "utf8"));
25719
+ if (fs3.existsSync(file)) {
25720
+ const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
25350
25721
  for (const a of snap.actors) await MemoryRegistryStore.prototype.upsertActor.call(store, a);
25351
25722
  for (const c of snap.configs) await MemoryRegistryStore.prototype.appendConfig.call(store, c);
25352
25723
  for (const t of snap.teams) await MemoryRegistryStore.prototype.upsertTeam.call(store, t);
@@ -25383,7 +25754,7 @@ var init_dev_store = __esm({
25383
25754
  installedSkills,
25384
25755
  skillFiles: skillFilesEntries
25385
25756
  };
25386
- fs2.writeFileSync(this.file, JSON.stringify(snap, null, 2));
25757
+ fs3.writeFileSync(this.file, JSON.stringify(snap, null, 2));
25387
25758
  }
25388
25759
  };
25389
25760
  FileProjectStateStore = class _FileProjectStateStore extends MemoryProjectStateStore {
@@ -25393,8 +25764,8 @@ var init_dev_store = __esm({
25393
25764
  }
25394
25765
  static async open(file) {
25395
25766
  const store = new _FileProjectStateStore(file);
25396
- if (fs2.existsSync(file)) {
25397
- const snap = JSON.parse(fs2.readFileSync(file, "utf8"));
25767
+ if (fs3.existsSync(file)) {
25768
+ const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
25398
25769
  for (const project of snap.projects ?? []) await MemoryProjectStateStore.prototype.upsertProject.call(store, project);
25399
25770
  const membersByProject = /* @__PURE__ */ new Map();
25400
25771
  for (const member of snap.members ?? []) {
@@ -25406,8 +25777,8 @@ var init_dev_store = __esm({
25406
25777
  await MemoryProjectStateStore.prototype.replaceProjectMembers.call(store, projectId, members);
25407
25778
  }
25408
25779
  } else {
25409
- fs2.mkdirSync(path.dirname(file), { recursive: true });
25410
- fs2.writeFileSync(file, JSON.stringify({ projects: [], members: [] }, null, 2));
25780
+ fs3.mkdirSync(path.dirname(file), { recursive: true });
25781
+ fs3.writeFileSync(file, JSON.stringify({ projects: [], members: [] }, null, 2));
25411
25782
  }
25412
25783
  return store;
25413
25784
  }
@@ -25426,7 +25797,7 @@ var init_dev_store = __esm({
25426
25797
  async save() {
25427
25798
  const projects = await this.listProjects();
25428
25799
  const members = (await Promise.all(projects.map((project) => this.listProjectMembers(project.id)))).flat();
25429
- fs2.writeFileSync(this.file, JSON.stringify({ projects, members }, null, 2));
25800
+ fs3.writeFileSync(this.file, JSON.stringify({ projects, members }, null, 2));
25430
25801
  }
25431
25802
  };
25432
25803
  FileProjectDocumentStore = class _FileProjectDocumentStore extends MemoryProjectDocumentStore {
@@ -25436,8 +25807,8 @@ var init_dev_store = __esm({
25436
25807
  }
25437
25808
  static async open(file) {
25438
25809
  const store = new _FileProjectDocumentStore(file);
25439
- if (fs2.existsSync(file)) {
25440
- const snap = JSON.parse(fs2.readFileSync(file, "utf8"));
25810
+ if (fs3.existsSync(file)) {
25811
+ const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
25441
25812
  for (const [projectId, space] of Object.entries(snap.spaces ?? {})) {
25442
25813
  store.spaces.set(projectId, {
25443
25814
  rootRef: space.rootRef,
@@ -25451,8 +25822,8 @@ var init_dev_store = __esm({
25451
25822
  });
25452
25823
  }
25453
25824
  } else {
25454
- fs2.mkdirSync(path.dirname(file), { recursive: true });
25455
- fs2.writeFileSync(file, JSON.stringify({ spaces: {} }, null, 2));
25825
+ fs3.mkdirSync(path.dirname(file), { recursive: true });
25826
+ fs3.writeFileSync(file, JSON.stringify({ spaces: {} }, null, 2));
25456
25827
  }
25457
25828
  return store;
25458
25829
  }
@@ -25485,7 +25856,7 @@ var init_dev_store = __esm({
25485
25856
  runStates: [...space.runStates.values()]
25486
25857
  };
25487
25858
  }
25488
- fs2.writeFileSync(this.file, JSON.stringify({ spaces }, null, 2));
25859
+ fs3.writeFileSync(this.file, JSON.stringify({ spaces }, null, 2));
25489
25860
  }
25490
25861
  };
25491
25862
  FileArtifactStateStore = class _FileArtifactStateStore extends MemoryArtifactStateStore {
@@ -25495,8 +25866,8 @@ var init_dev_store = __esm({
25495
25866
  }
25496
25867
  static async open(file) {
25497
25868
  const store = new _FileArtifactStateStore(file);
25498
- if (fs2.existsSync(file)) {
25499
- const snap = JSON.parse(fs2.readFileSync(file, "utf8"));
25869
+ if (fs3.existsSync(file)) {
25870
+ const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
25500
25871
  for (const artifact of snap.artifacts ?? []) await MemoryArtifactStateStore.prototype.upsertArtifact.call(store, artifact);
25501
25872
  for (const revision of snap.revisions ?? []) await MemoryArtifactStateStore.prototype.upsertRevision.call(store, revision);
25502
25873
  for (const event of snap.events ?? []) await MemoryArtifactStateStore.prototype.appendEvent.call(store, event);
@@ -25505,8 +25876,8 @@ var init_dev_store = __esm({
25505
25876
  for (const binding of snap.workspaceBindings ?? []) await MemoryArtifactStateStore.prototype.upsertWorkspaceBinding.call(store, binding);
25506
25877
  for (const ws of snap.workspaceDispatchHeld ?? []) await MemoryArtifactStateStore.prototype.setWorkspaceDispatchHold.call(store, ws, true);
25507
25878
  } else {
25508
- fs2.mkdirSync(path.dirname(file), { recursive: true });
25509
- fs2.writeFileSync(file, JSON.stringify({ artifacts: [], revisions: [], events: [], annotations: [], nodeOutputs: [], workspaceBindings: [], workspaceDispatchHeld: [] }, null, 2));
25879
+ fs3.mkdirSync(path.dirname(file), { recursive: true });
25880
+ fs3.writeFileSync(file, JSON.stringify({ artifacts: [], revisions: [], events: [], annotations: [], nodeOutputs: [], workspaceBindings: [], workspaceDispatchHeld: [] }, null, 2));
25510
25881
  }
25511
25882
  return store;
25512
25883
  }
@@ -25539,7 +25910,7 @@ var init_dev_store = __esm({
25539
25910
  await this.save();
25540
25911
  }
25541
25912
  async save() {
25542
- fs2.writeFileSync(this.file, JSON.stringify(await this.listAll(), null, 2));
25913
+ fs3.writeFileSync(this.file, JSON.stringify(await this.listAll(), null, 2));
25543
25914
  }
25544
25915
  };
25545
25916
  FileTraceStore = class _FileTraceStore extends MemoryTraceStore {
@@ -25566,20 +25937,20 @@ var init_dev_store = __esm({
25566
25937
  return path.join(this.eventsDir, `${encodeURIComponent(runId)}.ndjson`);
25567
25938
  }
25568
25939
  appendLine(file, obj) {
25569
- fs2.appendFileSync(file, JSON.stringify(obj) + "\n");
25940
+ fs3.appendFileSync(file, JSON.stringify(obj) + "\n");
25570
25941
  }
25571
25942
  static replay(file, fn) {
25572
- if (!fs2.existsSync(file)) return;
25573
- for (const line of fs2.readFileSync(file, "utf8").split("\n")) {
25943
+ if (!fs3.existsSync(file)) return;
25944
+ for (const line of fs3.readFileSync(file, "utf8").split("\n")) {
25574
25945
  if (!line.trim()) continue;
25575
25946
  fn(JSON.parse(line));
25576
25947
  }
25577
25948
  }
25578
25949
  static async open(dir) {
25579
25950
  const store = new _FileTraceStore(dir);
25580
- fs2.mkdirSync(store.eventsDir, { recursive: true });
25951
+ fs3.mkdirSync(store.eventsDir, { recursive: true });
25581
25952
  _FileTraceStore.replay(store.runsFile, (r) => store.restoreRun(r));
25582
- for (const name of fs2.readdirSync(store.eventsDir)) {
25953
+ for (const name of fs3.readdirSync(store.eventsDir)) {
25583
25954
  _FileTraceStore.replay(path.join(store.eventsDir, name), (e) => store.restoreEvent(e));
25584
25955
  }
25585
25956
  _FileTraceStore.replay(store.toolCallsFile, (c) => store.restoreToolCall(c));
@@ -25624,6 +25995,7 @@ var init_dev_store = __esm({
25624
25995
  MemoryChatSessionStore = class {
25625
25996
  sessions = /* @__PURE__ */ new Map();
25626
25997
  messages = /* @__PURE__ */ new Map();
25998
+ sessionWorkOrders = /* @__PURE__ */ new Map();
25627
25999
  async createSession(s2) {
25628
26000
  this.sessions.set(s2.id, s2);
25629
26001
  }
@@ -25640,6 +26012,7 @@ var init_dev_store = __esm({
25640
26012
  async deleteSession(id) {
25641
26013
  this.sessions.delete(id);
25642
26014
  this.messages.delete(id);
26015
+ this.sessionWorkOrders.delete(id);
25643
26016
  }
25644
26017
  async appendMessage(m2) {
25645
26018
  const list = this.messages.get(m2.sessionId) ?? [];
@@ -25650,16 +26023,24 @@ var init_dev_store = __esm({
25650
26023
  const list = (this.messages.get(sessionId) ?? []).sort((a, b2) => a.seq - b2.seq);
25651
26024
  return limit ? list.slice(-limit) : list;
25652
26025
  }
26026
+ async linkWorkOrder(sessionId, workOrderId) {
26027
+ const set = this.sessionWorkOrders.get(sessionId) ?? /* @__PURE__ */ new Set();
26028
+ set.add(workOrderId);
26029
+ this.sessionWorkOrders.set(sessionId, set);
26030
+ }
26031
+ async listWorkOrdersForSession(sessionId) {
26032
+ return [...this.sessionWorkOrders.get(sessionId) ?? []];
26033
+ }
25653
26034
  };
25654
26035
  }
25655
26036
  });
25656
26037
 
25657
26038
  // ../server/src/control-plane-file-store.ts
25658
- var fs3, path2, MUTATORS2, FileControlPlaneStore;
26039
+ var fs4, path2, MUTATORS2, FileControlPlaneStore;
25659
26040
  var init_control_plane_file_store = __esm({
25660
26041
  "../server/src/control-plane-file-store.ts"() {
25661
26042
  "use strict";
25662
- fs3 = __toESM(require("node:fs"), 1);
26043
+ fs4 = __toESM(require("node:fs"), 1);
25663
26044
  path2 = __toESM(require("node:path"), 1);
25664
26045
  init_src3();
25665
26046
  MUTATORS2 = [
@@ -25688,15 +26069,15 @@ var init_control_plane_file_store = __esm({
25688
26069
  }
25689
26070
  static async open(file) {
25690
26071
  const store = new _FileControlPlaneStore(file);
25691
- if (fs3.existsSync(file)) {
25692
- const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
26072
+ if (fs4.existsSync(file)) {
26073
+ const snap = JSON.parse(fs4.readFileSync(file, "utf8"));
25693
26074
  for (const c of snap.companies ?? []) await MemoryControlPlaneStore.prototype.createCompany.call(store, c);
25694
26075
  for (const m2 of snap.members ?? []) await MemoryControlPlaneStore.prototype.upsertMember.call(store, m2);
25695
26076
  for (const inv of snap.invitations ?? []) await MemoryControlPlaneStore.prototype.createInvitation.call(store, inv);
25696
26077
  for (const a of snap.accounts ?? []) await MemoryControlPlaneStore.prototype.upsertAccount.call(store, a);
25697
26078
  for (const e of snap.accountSecrets ?? []) store.accountSecrets.set(e.accountId, { ...e.secret });
25698
26079
  } else {
25699
- fs3.mkdirSync(path2.dirname(file), { recursive: true });
26080
+ fs4.mkdirSync(path2.dirname(file), { recursive: true });
25700
26081
  }
25701
26082
  return store;
25702
26083
  }
@@ -25717,7 +26098,7 @@ var init_control_plane_file_store = __esm({
25717
26098
  accounts,
25718
26099
  accountSecrets
25719
26100
  };
25720
- fs3.writeFileSync(this.file, JSON.stringify(snap, null, 2));
26101
+ fs4.writeFileSync(this.file, JSON.stringify(snap, null, 2));
25721
26102
  }
25722
26103
  };
25723
26104
  }
@@ -25991,7 +26372,7 @@ var init_liveness = __esm({
25991
26372
 
25992
26373
  // ../server/src/file-engine.ts
25993
26374
  async function openFileEngine(dir, seed = {}) {
25994
- fs4.mkdirSync(dir, { recursive: true });
26375
+ fs5.mkdirSync(dir, { recursive: true });
25995
26376
  const oplog = await NdjsonOplogStore.open(path3.join(dir, "oplog.ndjson"));
25996
26377
  const blobs = new DirBlobStore(path3.join(dir, "blobs"));
25997
26378
  const assets = new DirBlobStore(path3.join(dir, "assets"));
@@ -26012,11 +26393,11 @@ async function openFileEngine(dir, seed = {}) {
26012
26393
  function companyEngineDirName(companyId) {
26013
26394
  return companyId.replace(/[^A-Za-z0-9._-]/g, "_");
26014
26395
  }
26015
- var fs4, path3;
26396
+ var fs5, path3;
26016
26397
  var init_file_engine = __esm({
26017
26398
  "../server/src/file-engine.ts"() {
26018
26399
  "use strict";
26019
- fs4 = __toESM(require("node:fs"), 1);
26400
+ fs5 = __toESM(require("node:fs"), 1);
26020
26401
  path3 = __toESM(require("node:path"), 1);
26021
26402
  init_src2();
26022
26403
  init_dev_store();
@@ -30069,6 +30450,7 @@ var init_service2 = __esm({
30069
30450
  "use strict";
30070
30451
  import_node_crypto5 = require("node:crypto");
30071
30452
  init_skill_fetcher();
30453
+ init_identity();
30072
30454
  REDACTED_MARKER = "[REDACTED]";
30073
30455
  ActorsService = class {
30074
30456
  constructor(opts) {
@@ -30084,11 +30466,19 @@ var init_service2 = __esm({
30084
30466
  async upsertActor(a, by) {
30085
30467
  const existing = await this.opts.store.getActor(a.id);
30086
30468
  const roles = a.roles ?? existing?.roles ?? [];
30087
- const record3 = { ...a, roles, createdAt: existing?.createdAt ?? a.createdAt ?? this.now() };
30088
- await this.opts.store.upsertActor(record3);
30089
- this.opts.onRolesChanged?.(record3.id, record3.status === "active" ? roles : []);
30469
+ const record4 = { ...a, roles, createdAt: existing?.createdAt ?? a.createdAt ?? this.now() };
30470
+ if (isActiveCoordinatorAgent(record4)) {
30471
+ const existingCoordinator = (await this.opts.store.listActors({ kind: "agent", status: "active" })).find((actor) => actor.id !== record4.id && isActiveCoordinatorAgent(actor));
30472
+ if (existingCoordinator) {
30473
+ throw new Error(
30474
+ `\u7CFB\u7EDF\u53EA\u80FD\u6709\u4E00\u4E2A\u534F\u8C03\u8005\u667A\u80FD\u4F53\uFF1A\u5DF2\u5B58\u5728 ${existingCoordinator.name}\uFF08${existingCoordinator.id}\uFF09\uFF0C\u4E0D\u80FD\u518D\u521B\u5EFA\u6216\u542F\u7528 ${record4.name}\uFF08${record4.id}\uFF09\u3002`
30475
+ );
30476
+ }
30477
+ }
30478
+ await this.opts.store.upsertActor(record4);
30479
+ this.opts.onRolesChanged?.(record4.id, record4.status === "active" ? roles : []);
30090
30480
  await this.audit({ kind: "actor_upsert", actorId: a.id, by, at: this.now(), detail: { status: a.status, roles } });
30091
- return record3;
30481
+ return record4;
30092
30482
  }
30093
30483
  /** 删除即停用(2.1 完成标准) */
30094
30484
  async disableActor(id, by) {
@@ -30104,10 +30494,10 @@ var init_service2 = __esm({
30104
30494
  const a = await this.opts.store.getActor(id);
30105
30495
  if (!a) throw new Error(`actor not found: ${id}`);
30106
30496
  const { avatar: _drop, ...rest } = a;
30107
- const record3 = ref === null ? rest : { ...rest, avatar: ref };
30108
- await this.opts.store.upsertActor(record3);
30497
+ const record4 = ref === null ? rest : { ...rest, avatar: ref };
30498
+ await this.opts.store.upsertActor(record4);
30109
30499
  await this.audit({ kind: "actor_upsert", actorId: id, by, at: this.now(), detail: { avatar: ref ?? "cleared" } });
30110
- return record3;
30500
+ return record4;
30111
30501
  }
30112
30502
  /** 设置/清除团队图标引用(同头像约定;不接受 emoji,只接受 blob 引用) */
30113
30503
  async setTeamIcon(teamId, ref) {
@@ -31881,8 +32271,8 @@ var DEFAULT_COMPANY_ID, DEFAULT_COMPANY_SLUG, DEFAULT_COMPANY_NAME;
31881
32271
  var init_migrate = __esm({
31882
32272
  "../server/src/domains/companies/migrate.ts"() {
31883
32273
  "use strict";
31884
- DEFAULT_COMPANY_ID = "company:hq";
31885
- DEFAULT_COMPANY_SLUG = "hq";
32274
+ DEFAULT_COMPANY_ID = "company:oasis";
32275
+ DEFAULT_COMPANY_SLUG = "oasis";
31886
32276
  DEFAULT_COMPANY_NAME = "Oasis \u603B\u90E8";
31887
32277
  }
31888
32278
  });
@@ -31908,9 +32298,13 @@ var init_service3 = __esm({
31908
32298
  CompaniesService = class {
31909
32299
  store;
31910
32300
  now;
32301
+ sender;
32302
+ consoleUrl;
31911
32303
  constructor(opts) {
31912
32304
  this.store = opts.store;
31913
32305
  this.now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
32306
+ this.sender = opts.sender;
32307
+ this.consoleUrl = opts.consoleUrl ?? "/";
31914
32308
  }
31915
32309
  /** 当前用户所属的公司(按 membership 聚合)。 */
31916
32310
  async listForUser(accountId) {
@@ -32001,6 +32395,16 @@ var init_service3 = __esm({
32001
32395
  await this.requireCompany(companyId);
32002
32396
  return this.store.listMembers(companyId);
32003
32397
  }
32398
+ /** 成员视图:每条成员关系附上其账号的 email/name(无账号成员如 agent 为 null)。 */
32399
+ async listMemberViews(companyId) {
32400
+ const members = await this.listMembers(companyId);
32401
+ const out = [];
32402
+ for (const m2 of members) {
32403
+ const acc = await this.store.getAccount(m2.accountId);
32404
+ out.push({ ...m2, email: acc?.email ?? null, name: acc?.name ?? null });
32405
+ }
32406
+ return out;
32407
+ }
32004
32408
  /**
32005
32409
  * 改成员角色。不变量「至少留 1 个 owner」:把唯一的 owner 降级会被拒。
32006
32410
  * 公司/成员不存在则抛 404。
@@ -32036,7 +32440,7 @@ var init_service3 = __esm({
32036
32440
  * 公司不存在则抛 404。
32037
32441
  */
32038
32442
  async createInvitation(companyId, email2, role) {
32039
- const mail = email2?.trim() ?? "";
32443
+ const mail = email2?.trim().toLowerCase() ?? "";
32040
32444
  if (!mail) throw new CompanyError(400, "BAD_REQUEST", "\u7F3A\u5C11 email");
32041
32445
  if (!INVITABLE_ROLES.includes(role)) throw new CompanyError(400, "BAD_ROLE", `role \u987B\u4E3A ${INVITABLE_ROLES.join(" | ")}\uFF08owner \u4E0D\u7ECF\u9080\u8BF7\u4EA7\u751F\uFF09`);
32042
32446
  await this.requireCompany(companyId);
@@ -32054,11 +32458,16 @@ var init_service3 = __esm({
32054
32458
  expiresAt: new Date(nowMs + INVITATION_TTL_MS).toISOString()
32055
32459
  };
32056
32460
  await this.store.createInvitation(invitation);
32461
+ if (this.sender) {
32462
+ const company = await this.store.getCompany(companyId);
32463
+ const companyName = company?.name ?? companyId;
32464
+ void this.sender.sendInvitation(mail, { companyName, consoleUrl: this.consoleUrl }).catch((e) => console.error(`[companies] \u9080\u8BF7\u90AE\u4EF6\u53D1\u9001\u5931\u8D25 ${mail}: ${e instanceof Error ? e.message : String(e)}`));
32465
+ }
32057
32466
  return invitation;
32058
32467
  }
32059
32468
  /** 列出发给某邮箱的有效(pending 未过期)邀请;顺带把过期的 pending 落为 expired。 */
32060
32469
  async listInvitationsForEmail(email2) {
32061
- const all = await this.store.listInvitations({ email: email2 });
32470
+ const all = await this.store.listInvitations({ email: email2.trim().toLowerCase() });
32062
32471
  const out = [];
32063
32472
  for (const inv of all) {
32064
32473
  if (inv.status !== "pending") continue;
@@ -32152,7 +32561,7 @@ function companiesDomain(opts) {
32152
32561
  return { status: 200, body: { ok: true } };
32153
32562
  });
32154
32563
  router.get("/api/companies/:id/members", async (req) => {
32155
- const items = await service.listMembers(req.params.id).catch(mapErr);
32564
+ const items = await service.listMemberViews(req.params.id).catch(mapErr);
32156
32565
  return { status: 200, body: { items } };
32157
32566
  });
32158
32567
  router.patch("/api/companies/:id/members/:accountId", async (req) => {
@@ -32199,7 +32608,12 @@ var init_routes3 = __esm({
32199
32608
 
32200
32609
  // ../server/src/domains/companies/index.ts
32201
32610
  function createCompaniesDomain(opts) {
32202
- const service = new CompaniesService({ store: opts.store, ...opts.now !== void 0 ? { now: opts.now } : {} });
32611
+ const service = new CompaniesService({
32612
+ store: opts.store,
32613
+ ...opts.now !== void 0 ? { now: opts.now } : {},
32614
+ ...opts.sender !== void 0 ? { sender: opts.sender } : {},
32615
+ ...opts.consoleUrl !== void 0 ? { consoleUrl: opts.consoleUrl } : {}
32616
+ });
32203
32617
  const currentUser = opts.currentUser ?? makeCurrentUser(opts.store);
32204
32618
  return { service, register: companiesDomain({ service, currentUser, ...opts.blobs !== void 0 ? { blobs: opts.blobs } : {} }), currentUser };
32205
32619
  }
@@ -32302,6 +32716,14 @@ function parseCookies(header) {
32302
32716
  }
32303
32717
  return out;
32304
32718
  }
32719
+ function looksLikeEmail(s2) {
32720
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s2);
32721
+ }
32722
+ function maskEmail(email2) {
32723
+ const at = email2.indexOf("@");
32724
+ if (at <= 0) return "***";
32725
+ return `${email2[0]}***${email2.slice(at)}`;
32726
+ }
32305
32727
  function createAuth(opts) {
32306
32728
  const now = opts.now ?? (() => Date.now());
32307
32729
  const cookieName = opts.cookieName ?? "oasis_auth";
@@ -32310,7 +32732,6 @@ function createAuth(opts) {
32310
32732
  const maxAttempts = opts.maxCodeAttempts ?? 5;
32311
32733
  const resendThrottleMs = opts.resendThrottleMs ?? 30 * 1e3;
32312
32734
  const { store, sender, jwtSecret } = opts;
32313
- const hasPendingInvite = async (email2) => (await store.listInvitations({ email: email2, status: "pending" })).some((inv) => Date.parse(inv.expiresAt) > now());
32314
32735
  const sessionCookie = (accountId, tokenVersion) => {
32315
32736
  const t = now();
32316
32737
  const jwt = signSession({ sub: accountId, tokenVersion, iat: t, exp: t + sessionTtlMs }, jwtSecret);
@@ -32367,26 +32788,23 @@ function createAuth(opts) {
32367
32788
  if (method !== "POST") return { status: 405, body: { error: { code: "METHOD_NOT_ALLOWED", message: method } } };
32368
32789
  if (route === "request-code") {
32369
32790
  const email2 = typeof parsed.email === "string" ? parsed.email.trim().toLowerCase() : "";
32370
- if (email2) {
32371
- const account = await store.getAccountByEmail(email2);
32372
- const eligible = !!account || !!opts.provisionAccount && await hasPendingInvite(email2);
32373
- if (eligible) {
32374
- const existing = await store.getVerificationCode(email2);
32375
- const fresh = existing && Date.parse(existing.createdAt) > now() - resendThrottleMs;
32376
- if (!fresh) {
32377
- const code = generateCode();
32378
- const t = now();
32379
- await store.putVerificationCode({
32380
- email: email2,
32381
- codeHash: hashCode(code, jwtSecret),
32382
- createdAt: new Date(t).toISOString(),
32383
- expiresAt: new Date(t + codeTtlMs).toISOString(),
32384
- attempts: 0
32385
- });
32386
- try {
32387
- await sender.sendCode(email2, code);
32388
- } catch {
32389
- }
32791
+ if (looksLikeEmail(email2)) {
32792
+ const existing = await store.getVerificationCode(email2);
32793
+ const fresh = existing && Date.parse(existing.createdAt) > now() - resendThrottleMs;
32794
+ if (!fresh) {
32795
+ const code = generateCode();
32796
+ const t = now();
32797
+ await store.putVerificationCode({
32798
+ email: email2,
32799
+ codeHash: hashCode(code, jwtSecret),
32800
+ createdAt: new Date(t).toISOString(),
32801
+ expiresAt: new Date(t + codeTtlMs).toISOString(),
32802
+ attempts: 0
32803
+ });
32804
+ try {
32805
+ await sender.sendCode(email2, code);
32806
+ } catch (e) {
32807
+ console.error(`[auth] \u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25 ${maskEmail(email2)}: ${e instanceof Error ? e.message : String(e)}`);
32390
32808
  }
32391
32809
  }
32392
32810
  }
@@ -32409,7 +32827,7 @@ function createAuth(opts) {
32409
32827
  }
32410
32828
  await store.deleteVerificationCode(email2);
32411
32829
  let account = await store.getAccountByEmail(email2);
32412
- if (!account && opts.provisionAccount && await hasPendingInvite(email2)) {
32830
+ if (!account && opts.provisionAccount) {
32413
32831
  account = await opts.provisionAccount(email2);
32414
32832
  }
32415
32833
  if (!account) return fail();
@@ -32456,6 +32874,9 @@ function consoleSender() {
32456
32874
  return {
32457
32875
  async sendCode(email2, code) {
32458
32876
  console.log(`[auth] \u9A8C\u8BC1\u7801 \u2192 ${email2}: ${code}`);
32877
+ },
32878
+ async sendInvitation(email2, opts) {
32879
+ console.log(`[auth] \u9080\u8BF7 \u2192 ${email2}: \u52A0\u5165\u300C${opts.companyName}\u300D\uFF0C\u767B\u5F55 ${opts.consoleUrl}`);
32459
32880
  }
32460
32881
  };
32461
32882
  }
@@ -32477,6 +32898,22 @@ function resendSender(opts) {
32477
32898
  const detail = await res.text().catch(() => "");
32478
32899
  throw new Error(`Resend \u53D1\u9001\u5931\u8D25 ${res.status}: ${detail.slice(0, 200)}`);
32479
32900
  }
32901
+ },
32902
+ async sendInvitation(email2, { companyName, consoleUrl }) {
32903
+ const res = await doFetch("https://api.resend.com/emails", {
32904
+ method: "POST",
32905
+ headers: { authorization: `Bearer ${opts.apiKey}`, "content-type": "application/json" },
32906
+ body: JSON.stringify({
32907
+ from: opts.from,
32908
+ to: [email2],
32909
+ subject: `\u4F60\u88AB\u9080\u8BF7\u52A0\u5165\u300C${companyName}\u300D`,
32910
+ html: `<div style="font-family:sans-serif;max-width:440px;margin:0 auto"><h2>\u52A0\u5165\u300C${companyName}\u300D</h2><p>\u4F60\u88AB\u9080\u8BF7\u52A0\u5165 Oasis \u4E0A\u7684\u300C${companyName}\u300D\u3002</p><p style="margin:24px 0"><a href="${consoleUrl}" style="background:#111;color:#fff;padding:10px 20px;border-radius:8px;text-decoration:none">\u767B\u5F55\u5E76\u63A5\u53D7\u9080\u8BF7</a></p><p style="color:#666;font-size:14px">\u7528<strong>\u672C\u90AE\u7BB1</strong>\u767B\u5F55\uFF08\u90AE\u7BB1\u9A8C\u8BC1\u7801\uFF09\uFF0C\u767B\u5F55\u540E\u5728\u516C\u53F8\u5207\u6362\u5904\u5373\u53EF\u770B\u5230\u5E76\u63A5\u53D7\u8FD9\u6761\u9080\u8BF7\u3002\u9080\u8BF7 7 \u5929\u5185\u6709\u6548\u3002</p><p style="color:#666;font-size:13px">\u94FE\u63A5\u6253\u4E0D\u5F00\u5C31\u76F4\u63A5\u8BBF\u95EE\uFF1A${consoleUrl}</p></div>`
32911
+ })
32912
+ });
32913
+ if (!res.ok) {
32914
+ const detail = await res.text().catch(() => "");
32915
+ throw new Error(`Resend \u9080\u8BF7\u53D1\u9001\u5931\u8D25 ${res.status}: ${detail.slice(0, 200)}`);
32916
+ }
32480
32917
  }
32481
32918
  };
32482
32919
  }
@@ -32952,11 +33389,11 @@ var init_types3 = __esm({
32952
33389
  });
32953
33390
 
32954
33391
  // ../server/src/node-store.ts
32955
- var fs5, MemoryNodeStore, FileNodeStore;
33392
+ var fs6, MemoryNodeStore, FileNodeStore;
32956
33393
  var init_node_store = __esm({
32957
33394
  "../server/src/node-store.ts"() {
32958
33395
  "use strict";
32959
- fs5 = __toESM(require("node:fs"), 1);
33396
+ fs6 = __toESM(require("node:fs"), 1);
32960
33397
  MemoryNodeStore = class {
32961
33398
  nodes = /* @__PURE__ */ new Map();
32962
33399
  runtimes = /* @__PURE__ */ new Map();
@@ -33037,16 +33474,16 @@ var init_node_store = __esm({
33037
33474
  static async open(file) {
33038
33475
  const s2 = new _FileNodeStore(file);
33039
33476
  try {
33040
- const snap = JSON.parse(fs5.readFileSync(file, "utf8"));
33477
+ const snap = JSON.parse(fs6.readFileSync(file, "utf8"));
33041
33478
  for (const n of snap.nodes ?? []) s2.nodes.set(n.id, n);
33042
33479
  for (const r of snap.runtimes ?? []) s2.runtimes.set(r.id, r);
33043
33480
  } catch {
33044
- fs5.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
33481
+ fs6.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
33045
33482
  }
33046
33483
  return s2;
33047
33484
  }
33048
33485
  flush() {
33049
- fs5.writeFileSync(this.file, JSON.stringify({
33486
+ fs6.writeFileSync(this.file, JSON.stringify({
33050
33487
  nodes: [...this.nodes.values()],
33051
33488
  runtimes: [...this.runtimes.values()]
33052
33489
  }, null, 2));
@@ -33094,7 +33531,7 @@ var init_node_store = __esm({
33094
33531
  });
33095
33532
 
33096
33533
  // ../server/src/domains/collab/workorder-detail.ts
33097
- function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject, dispatchJournal = [], dispatchPausedOf) {
33534
+ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject, dispatchJournal = [], dispatchPausedOf, ops = []) {
33098
33535
  const arts = [...model.artifacts.values()].filter((a) => a.workspace === workspaceId);
33099
33536
  if (arts.length === 0) return null;
33100
33537
  const summary = buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPausedOf).find((w2) => w2.id === workspaceId);
@@ -33111,13 +33548,16 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
33111
33548
  const runtimeByArtifact = buildRuntimeByArtifact(dispatchJournal, ref);
33112
33549
  const nodes = arts.map((a) => {
33113
33550
  const info = nodeInfo(model, a);
33114
- const runtime = runtimeByArtifact.get(a.id);
33551
+ const observedRuntime = runtimeByArtifact.get(a.id);
33552
+ const runtime = observedRuntime && shouldExposeRuntime(model, a.id, info, observedRuntime) ? observedRuntime : void 0;
33553
+ const baseStage = deriveNodeStage(info);
33554
+ const stage = runtime?.status === "running" && (runtime.action === "produce" || runtime.action === "integrate") && (baseStage === "embryo" || baseStage === "merged" || baseStage === "review") ? "editing" : baseStage;
33115
33555
  const lifecycle = lifecycleOf(model, a.id);
33116
33556
  return {
33117
33557
  id: a.id,
33118
33558
  type: a.type,
33119
33559
  label: nodeLabel(a),
33120
- stage: deriveNodeStage(info),
33560
+ stage,
33121
33561
  currentRev: a.currentRev,
33122
33562
  queue: info.queueLen,
33123
33563
  blocked: blockedOf(model, a.id).blocked,
@@ -33131,6 +33571,7 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
33131
33571
  (a) => a.inputs.map((e) => ({ from: a.id, to: e.to, pinned: e.pinned, required: e.required }))
33132
33572
  );
33133
33573
  const activity = buildActivity(model, arts, ref);
33574
+ const coordinatorActivity = buildCoordinatorActivity(arts, ops, ref);
33134
33575
  const acceptance = buildAcceptance(model, arts);
33135
33576
  const planning = planningStatusOf(model, arts);
33136
33577
  const spec = workorderSpecOf(model, arts);
@@ -33138,11 +33579,72 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
33138
33579
  summary,
33139
33580
  graph: { nodes, edges },
33140
33581
  activity,
33582
+ coordinatorActivity,
33141
33583
  acceptance,
33142
33584
  ...spec?.acceptanceCriteria ? { acceptanceCriteria: spec.acceptanceCriteria } : {},
33143
33585
  planning
33144
33586
  };
33145
33587
  }
33588
+ function buildWorkorderDraftDetail(draft, resolveActor) {
33589
+ const ref = (id) => {
33590
+ const extra = resolveActor?.(id);
33591
+ return { id, ...extra?.name ? { name: extra.name } : {}, ...extra?.role ? { role: extra.role } : {}, ...extra?.avatar ? { avatar: extra.avatar } : {} };
33592
+ };
33593
+ const spawns = draft.plan.ops.filter((op) => op.action === "spawn" && !!op.id);
33594
+ const nodes = spawns.map((op) => ({
33595
+ id: op.id,
33596
+ type: op.type,
33597
+ label: op.title ?? op.description ?? op.id,
33598
+ stage: "embryo",
33599
+ currentRev: null,
33600
+ queue: 0,
33601
+ blocked: false,
33602
+ owner: ref(op.owner ?? draft.draftedBy)
33603
+ }));
33604
+ const edges = [
33605
+ ...spawns.flatMap((op) => (op.inputs ?? []).map((inp) => ({ from: op.id, to: inp.to, pinned: null, required: inp.required ?? true }))),
33606
+ ...draft.plan.ops.filter((op) => op.action === "link").map((op) => ({ from: op.artifactId, to: op.to, pinned: null, required: op.required ?? true }))
33607
+ ];
33608
+ const rootSpawn = spawns.find((op) => op.id === draft.rootArtifactId);
33609
+ const title = rootSpawn?.title ?? rootSpawn?.description ?? draft.workspace;
33610
+ const participantIds = [...new Set(nodes.map((n) => n.owner.id))];
33611
+ const summary = {
33612
+ id: draft.workspace,
33613
+ title,
33614
+ ...draft.seed.brief && draft.seed.brief !== title ? { description: draft.seed.brief } : {},
33615
+ ...draft.seed.goal ? { goal: draft.seed.goal } : {},
33616
+ stage: "draft",
33617
+ owner: ref(draft.draftedBy),
33618
+ participants: participantIds.map((id) => ref(id)),
33619
+ artifactCount: nodes.length,
33620
+ progress: { concluded: 0, total: nodes.length },
33621
+ updatedAt: draft.at,
33622
+ projectId: null,
33623
+ ...draft.seed.dispatch === false ? { dispatchPaused: true } : {}
33624
+ };
33625
+ return {
33626
+ summary,
33627
+ graph: { nodes, edges },
33628
+ activity: [],
33629
+ coordinatorActivity: [],
33630
+ acceptance: [],
33631
+ ...draft.seed.acceptanceCriteria ? { acceptanceCriteria: draft.seed.acceptanceCriteria } : {}
33632
+ };
33633
+ }
33634
+ function isRuntimeFailure(runtime) {
33635
+ return runtime.status === "failed" || runtime.status === "fused" || runtime.status === "escalated";
33636
+ }
33637
+ function shouldExposeRuntime(model, artifactId, info, runtime) {
33638
+ if (!isRuntimeFailure(runtime)) return true;
33639
+ if (info.concluded) return false;
33640
+ const lastOpAt = model.lastOpAt.get(artifactId);
33641
+ const runtimeAt = runtime.finishedAt ?? runtime.startedAt;
33642
+ if (!lastOpAt || !runtimeAt) return true;
33643
+ const lastOpTime = Date.parse(lastOpAt);
33644
+ const runtimeTime = Date.parse(runtimeAt);
33645
+ if (!Number.isFinite(lastOpTime) || !Number.isFinite(runtimeTime)) return true;
33646
+ return lastOpTime <= runtimeTime;
33647
+ }
33146
33648
  function buildRuntimeByArtifact(entries, ref) {
33147
33649
  const byJob = /* @__PURE__ */ new Map();
33148
33650
  for (const entry of entries) {
@@ -33191,7 +33693,16 @@ function buildRuntimeByArtifact(entries, ref) {
33191
33693
  const byArtifact = /* @__PURE__ */ new Map();
33192
33694
  for (const value of byJob.values()) {
33193
33695
  const old = byArtifact.get(value.artifactId);
33194
- if (!old || old.at < value.at) byArtifact.set(value.artifactId, value);
33696
+ if (!old) {
33697
+ byArtifact.set(value.artifactId, value);
33698
+ continue;
33699
+ }
33700
+ if (old.status === "running" && value.status !== "running") continue;
33701
+ if (old.status !== "running" && value.status === "running") {
33702
+ byArtifact.set(value.artifactId, value);
33703
+ continue;
33704
+ }
33705
+ if (old.at < value.at) byArtifact.set(value.artifactId, value);
33195
33706
  }
33196
33707
  return new Map([...byArtifact].map(([id, value]) => {
33197
33708
  const { at: _at, artifactId: _artifactId, ...runtime } = value;
@@ -33249,6 +33760,65 @@ function buildActivity(model, arts, ref) {
33249
33760
  }
33250
33761
  return events.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
33251
33762
  }
33763
+ function buildCoordinatorActivity(arts, ops, ref) {
33764
+ const inWorkspace = new Set(arts.map((a) => a.id));
33765
+ const firstRevisionAt = ops.filter((op) => op.kind === "propose_revision" && inWorkspace.has(op.artifactId)).map((op) => op.timestamp).sort()[0];
33766
+ const edits = [];
33767
+ for (const op of ops) {
33768
+ if (!inWorkspace.has(op.artifactId)) continue;
33769
+ if (op.actor !== COORDINATOR_ACTOR) continue;
33770
+ if (!COORDINATOR_EDIT_KINDS.has(op.kind)) continue;
33771
+ const kind = op.kind;
33772
+ if ((kind === "spawn_artifact" || kind === "link_input") && (!firstRevisionAt || op.timestamp < firstRevisionAt)) {
33773
+ continue;
33774
+ }
33775
+ const detail = coordinatorEditDetail(kind, op.payload, ref);
33776
+ edits.push({
33777
+ seq: op.seq,
33778
+ at: op.timestamp,
33779
+ actor: ref(op.actor),
33780
+ kind,
33781
+ artifactId: op.artifactId,
33782
+ label: COORDINATOR_EDIT_LABEL[kind],
33783
+ ...detail ? { detail } : {}
33784
+ });
33785
+ }
33786
+ return edits.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
33787
+ }
33788
+ function coordinatorEditDetail(kind, payload, ref) {
33789
+ const str2 = (v2) => typeof v2 === "string" && v2.trim() ? v2 : void 0;
33790
+ switch (kind) {
33791
+ case "link_input":
33792
+ case "unlink_input":
33793
+ return str2(payload.to);
33794
+ case "spawn_artifact":
33795
+ return str2(payload.title) ?? str2(payload.type);
33796
+ case "assign_owner": {
33797
+ const owner = str2(payload.owner);
33798
+ return owner ? ref(owner).name ?? owner : void 0;
33799
+ }
33800
+ case "bump_pin": {
33801
+ const to = str2(payload.to);
33802
+ const pinned = str2(payload.pinned);
33803
+ return to && pinned ? `${to} \u2192 ${pinned}` : to ?? pinned;
33804
+ }
33805
+ case "report_gap":
33806
+ return str2(payload.description);
33807
+ case "edit_artifact": {
33808
+ const changed = ["title", "description", "docType", "fields"].filter((k2) => payload[k2] !== void 0);
33809
+ return changed.length ? changed.join("\u3001") : void 0;
33810
+ }
33811
+ case "seal": {
33812
+ const reason = str2(payload.reason);
33813
+ const note = str2(payload.note);
33814
+ return [reason, note].filter(Boolean).join("\uFF1A") || void 0;
33815
+ }
33816
+ case "reopen":
33817
+ return str2(payload.note);
33818
+ default:
33819
+ return void 0;
33820
+ }
33821
+ }
33252
33822
  function buildAcceptance(model, arts) {
33253
33823
  return arts.map((a) => ({
33254
33824
  artifactId: a.id,
@@ -33256,12 +33826,36 @@ function buildAcceptance(model, arts) {
33256
33826
  state: isConcluded(model, a.id) ? "done" : a.currentRev ? "active" : "pending"
33257
33827
  }));
33258
33828
  }
33829
+ var COORDINATOR_EDIT_KINDS, COORDINATOR_EDIT_LABEL, COORDINATOR_ACTOR;
33259
33830
  var init_workorder_detail = __esm({
33260
33831
  "../server/src/domains/collab/workorder-detail.ts"() {
33261
33832
  "use strict";
33262
33833
  init_src2();
33263
33834
  init_node_state();
33264
33835
  init_workorders();
33836
+ COORDINATOR_EDIT_KINDS = /* @__PURE__ */ new Set([
33837
+ "link_input",
33838
+ "unlink_input",
33839
+ "spawn_artifact",
33840
+ "assign_owner",
33841
+ "bump_pin",
33842
+ "report_gap",
33843
+ "edit_artifact",
33844
+ "seal",
33845
+ "reopen"
33846
+ ]);
33847
+ COORDINATOR_EDIT_LABEL = {
33848
+ link_input: "\u63A5\u5165\u4F9D\u8D56",
33849
+ unlink_input: "\u79FB\u9664\u4F9D\u8D56",
33850
+ spawn_artifact: "\u65B0\u5EFA\u4EA4\u4ED8\u7269",
33851
+ assign_owner: "\u6539\u6D3E",
33852
+ bump_pin: "\u89E6\u53D1\u8FD4\u5DE5",
33853
+ report_gap: "\u7559\u7F3A\u53E3",
33854
+ edit_artifact: "\u6539\u4EA4\u4ED8\u7269\u4FE1\u606F",
33855
+ seal: "\u5C01\u5B58",
33856
+ reopen: "\u91CD\u542F"
33857
+ };
33858
+ COORDINATOR_ACTOR = "actor:agent:coordinator";
33265
33859
  }
33266
33860
  });
33267
33861
 
@@ -33311,7 +33905,7 @@ function buildInbox(model, me) {
33311
33905
  if (gate?.gate && gate.gate.eligible.includes(me)) {
33312
33906
  const voted = (model.reviews.get(gate.head) ?? []).some((v2) => v2.author === me);
33313
33907
  if (!voted) {
33314
- out.push({ kind: "review-request", artifactId: a.id, workspace: ws, revisionId: gate.head, since: gate.at, actionRequired: true, summary: `\u5F85\u4F60\u5BA1\u6279\uFF1A${label}\uFF08quorum=${gate.gate.quorum}\uFF09` });
33908
+ out.push({ kind: "review-request", artifactId: a.id, workspace: ws, since: gate.at, actionRequired: true, summary: `\u5F85\u4F60\u5BA1\u6279\uFF1A${label}\uFF08quorum=${gate.gate.quorum}\uFF09` });
33315
33909
  continue;
33316
33910
  }
33317
33911
  }
@@ -33518,15 +34112,17 @@ function collabDomain(opts) {
33518
34112
  return { status: 200, body: { items: buildWorkorderSummaries(kernel.model, resolve3, resolveProject, dispatchPausedOf) } };
33519
34113
  });
33520
34114
  router.get("/api/workorders/:id", async (req) => {
33521
- const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
34115
+ const { kernel, oplog, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
33522
34116
  const resolve3 = await buildResolver(registry2);
33523
34117
  const resolveProject = await buildProjectResolver(artifacts);
33524
34118
  const dispatchPausedOf = await buildDispatchPausedResolver(artifacts);
33525
- const detail = buildWorkorderDetail(kernel.model, req.params.id, resolve3, resolveProject, opts.dispatchJournal?.() ?? [], dispatchPausedOf);
33526
- if (!detail) {
33527
- return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${req.params.id}` } } };
33528
- }
33529
- return { status: 200, body: detail };
34119
+ const wsArts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === req.params.id);
34120
+ const ops = (await Promise.all(wsArts.map((a) => oplog.read(a.id)))).flat();
34121
+ const detail = buildWorkorderDetail(kernel.model, req.params.id, resolve3, resolveProject, opts.dispatchJournal?.() ?? [], dispatchPausedOf, ops);
34122
+ if (detail) return { status: 200, body: detail };
34123
+ const draft = opts.workorderDrafts?.forWorkspace(req.params.id);
34124
+ if (draft) return { status: 200, body: buildWorkorderDraftDetail(draft, resolve3) };
34125
+ return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${req.params.id}` } } };
33530
34126
  });
33531
34127
  router.post("/api/workorders/:id/dispatch", async (req) => {
33532
34128
  const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
@@ -33549,7 +34145,7 @@ function collabDomain(opts) {
33549
34145
  const q = req.query;
33550
34146
  const audit = {};
33551
34147
  const actor = q.get("actor");
33552
- if (actor) audit.actor = actor;
34148
+ audit.actor = actor && actor !== "me" ? actor : req.auth.actor;
33553
34149
  const kind = q.get("kind");
33554
34150
  if (kind) audit.kind = kind;
33555
34151
  const from = q.get("from");
@@ -33613,12 +34209,75 @@ var init_collab = __esm({
33613
34209
  });
33614
34210
 
33615
34211
  // ../server/src/domains/trace/service.ts
33616
- var EVENT_PAGE_LIMIT, TraceService;
34212
+ function rawObj(event) {
34213
+ const payload = event.payload;
34214
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
34215
+ const raw = payload["raw"];
34216
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
34217
+ }
34218
+ function payloadObj(event) {
34219
+ const payload = event.payload;
34220
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
34221
+ }
34222
+ function modelChunkKind(event) {
34223
+ if (event.stream !== "model") return null;
34224
+ const payload = payloadObj(event);
34225
+ const kind = payload?.["kind"] ?? rawObj(event)?.["kind"];
34226
+ if (kind === "message" || kind === "thought") return kind;
34227
+ return null;
34228
+ }
34229
+ function modelText(event) {
34230
+ const raw = rawObj(event);
34231
+ const text = raw?.["text"];
34232
+ if (typeof text === "string") return text;
34233
+ return typeof event.message === "string" ? event.message : null;
34234
+ }
34235
+ function eventTimeMs(event) {
34236
+ const t = Date.parse(event.startedAt ?? event.createdAt);
34237
+ return Number.isFinite(t) ? t : null;
34238
+ }
34239
+ function eventEndTimeMs(event) {
34240
+ const t = Date.parse(event.endedAt ?? event.startedAt ?? event.createdAt);
34241
+ return Number.isFinite(t) ? t : null;
34242
+ }
34243
+ function canCompactModelChunk(prev, next) {
34244
+ const kind = modelChunkKind(prev);
34245
+ if (!kind || kind !== modelChunkKind(next)) return false;
34246
+ const a = eventEndTimeMs(prev);
34247
+ const b2 = eventTimeMs(next);
34248
+ if (a === null || b2 === null) return false;
34249
+ return Math.abs(b2 - a) <= MODEL_CHUNK_COMPACT_WINDOW_MS;
34250
+ }
34251
+ function compactPair(prev, next) {
34252
+ const text = `${modelText(prev) ?? ""}${modelText(next) ?? ""}`;
34253
+ const prevPayload = payloadObj(prev) ?? {};
34254
+ const prevRaw = rawObj(prev) ?? {};
34255
+ return {
34256
+ ...prev,
34257
+ id: next.id,
34258
+ seq: next.seq,
34259
+ message: text.slice(0, 500),
34260
+ payload: { ...prevPayload, raw: { ...prevRaw, text } },
34261
+ endedAt: next.endedAt ?? next.startedAt ?? prev.endedAt
34262
+ };
34263
+ }
34264
+ function compactModelChunks(rows) {
34265
+ const out = [];
34266
+ for (const row of rows) {
34267
+ const prev = out[out.length - 1];
34268
+ if (prev && canCompactModelChunk(prev, row)) out[out.length - 1] = compactPair(prev, row);
34269
+ else out.push(row);
34270
+ }
34271
+ return out;
34272
+ }
34273
+ var EVENT_PAGE_LIMIT, MODEL_CHUNK_COMPACT_WINDOW_MS, MODEL_CHUNK_RAW_MULTIPLIER, TraceService;
33617
34274
  var init_service4 = __esm({
33618
34275
  "../server/src/domains/trace/service.ts"() {
33619
34276
  "use strict";
33620
34277
  init_src();
33621
34278
  EVENT_PAGE_LIMIT = 200;
34279
+ MODEL_CHUNK_COMPACT_WINDOW_MS = 1500;
34280
+ MODEL_CHUNK_RAW_MULTIPLIER = 20;
33622
34281
  TraceService = class {
33623
34282
  store;
33624
34283
  constructor(opts) {
@@ -33695,12 +34354,14 @@ var init_service4 = __esm({
33695
34354
  }
33696
34355
  async listEvents(runId, opts) {
33697
34356
  const limit = Math.min(opts.limit ?? EVENT_PAGE_LIMIT, EVENT_PAGE_LIMIT);
34357
+ const rawLimit = Math.min(limit * MODEL_CHUNK_RAW_MULTIPLIER + 1, 5e3);
33698
34358
  const rows = await this.store.listEvents(runId, {
33699
34359
  ...opts.afterSeq !== void 0 ? { afterSeq: opts.afterSeq } : {},
33700
- limit: limit + 1
34360
+ limit: rawLimit
33701
34361
  });
33702
- const hasMore = rows.length > limit;
33703
- const items = hasMore ? rows.slice(0, limit) : rows;
34362
+ const compacted = compactModelChunks(rows);
34363
+ const hasMore = rows.length > rawLimit - 1 || compacted.length > limit;
34364
+ const items = compacted.length > limit ? compacted.slice(0, limit) : compacted;
33704
34365
  const nextCursor = hasMore ? String(items[items.length - 1].seq) : null;
33705
34366
  return { items, nextCursor };
33706
34367
  }
@@ -34266,6 +34927,18 @@ var init_sink = __esm({
34266
34927
  this.onError = opts.onError ?? (() => {
34267
34928
  });
34268
34929
  }
34930
+ runtimeKindFor(session) {
34931
+ if (this.runtimeKind !== "auto") return this.runtimeKind;
34932
+ const runtimeKind = session.runtimeKind?.toLowerCase();
34933
+ if (runtimeKind === "codex") return "codex";
34934
+ if (runtimeKind === "hermes") return "hermes";
34935
+ if (runtimeKind === "claude" || runtimeKind === "claude-code") return "claude-code";
34936
+ const id = session.sessionId.toLowerCase();
34937
+ if (id.startsWith("codex-")) return "codex";
34938
+ if (id.startsWith("hermes-")) return "hermes";
34939
+ if (id.startsWith("claude-") || id.startsWith("claudecode-")) return "claude-code";
34940
+ return "custom";
34941
+ }
34269
34942
  enqueue(sessionId, step) {
34270
34943
  const st = this.sessions.get(sessionId);
34271
34944
  if (!st) return;
@@ -34278,7 +34951,7 @@ var init_sink = __esm({
34278
34951
  await this.store.createRun({
34279
34952
  id: session.sessionId,
34280
34953
  actorId: session.actor,
34281
- runtimeKind: this.runtimeKind,
34954
+ runtimeKind: this.runtimeKindFor(session),
34282
34955
  triggerKind: "dispatch",
34283
34956
  triggerRef: session.jobKey,
34284
34957
  jobKey: session.jobKey,
@@ -34395,6 +35068,69 @@ var init_sink = __esm({
34395
35068
  }
34396
35069
  });
34397
35070
 
35071
+ // ../server/src/domains/trace/chat-parts.ts
35072
+ function asObj2(v2) {
35073
+ return v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : void 0;
35074
+ }
35075
+ function textOf2(v2) {
35076
+ if (typeof v2 === "string") return v2;
35077
+ if (Array.isArray(v2)) return v2.map(textOf2).filter(Boolean).join("\n") || void 0;
35078
+ const o = asObj2(v2);
35079
+ if (o) {
35080
+ if (typeof o.text === "string") return o.text;
35081
+ if (typeof o.content === "string") return o.content;
35082
+ if (typeof o.thinking === "string") return o.thinking;
35083
+ }
35084
+ return void 0;
35085
+ }
35086
+ function truncate(s2, limit = OUTPUT_PREVIEW_LIMIT) {
35087
+ return s2.length > limit ? `${s2.slice(0, limit)}\u2026` : s2;
35088
+ }
35089
+ function deriveChatPartsFromEvents(events) {
35090
+ const parts = [];
35091
+ for (const ev of [...events].sort((a, b2) => a.seq - b2.seq)) {
35092
+ const p2 = asObj2(ev.payload);
35093
+ const kind = p2 && typeof p2.kind === "string" ? p2.kind : void 0;
35094
+ const raw = p2?.raw;
35095
+ if (kind === "thought") {
35096
+ const text = textOf2(raw);
35097
+ if (text) parts.push({ type: "thinking", seq: ev.seq, text });
35098
+ continue;
35099
+ }
35100
+ if (kind === "tool_use") {
35101
+ const ro = asObj2(raw);
35102
+ parts.push({
35103
+ type: "tool_use",
35104
+ seq: ev.seq,
35105
+ toolName: ro && typeof ro.name === "string" ? ro.name : "tool",
35106
+ ...ro && ro.input !== void 0 ? { input: ro.input } : {},
35107
+ ...ro && typeof ro.id === "string" ? { toolCallId: ro.id } : {}
35108
+ });
35109
+ continue;
35110
+ }
35111
+ if (kind === "tool_result") {
35112
+ const ro = asObj2(raw);
35113
+ const output = textOf2(raw) ?? "";
35114
+ parts.push({
35115
+ type: "tool_result",
35116
+ seq: ev.seq,
35117
+ output: truncate(output),
35118
+ isError: ro?.is_error === true || ro?.isError === true,
35119
+ ...ro && typeof ro.tool_use_id === "string" ? { toolCallId: ro.tool_use_id } : {}
35120
+ });
35121
+ continue;
35122
+ }
35123
+ }
35124
+ return parts;
35125
+ }
35126
+ var OUTPUT_PREVIEW_LIMIT;
35127
+ var init_chat_parts = __esm({
35128
+ "../server/src/domains/trace/chat-parts.ts"() {
35129
+ "use strict";
35130
+ OUTPUT_PREVIEW_LIMIT = 4e3;
35131
+ }
35132
+ });
35133
+
34398
35134
  // ../server/src/domains/trace/index.ts
34399
35135
  function createTraceDomain(opts) {
34400
35136
  const service = new TraceService({ store: opts.store });
@@ -34408,6 +35144,7 @@ var init_trace2 = __esm({
34408
35144
  init_service4();
34409
35145
  init_routes5();
34410
35146
  init_sink();
35147
+ init_chat_parts();
34411
35148
  }
34412
35149
  });
34413
35150
 
@@ -34418,6 +35155,15 @@ function createChatSessionsDomain(opts) {
34418
35155
  const binding = await registry2.getBinding(aiActorId).catch(() => null);
34419
35156
  return binding?.nodeId ?? null;
34420
35157
  }
35158
+ async function enrichMessages(messages) {
35159
+ if (!opts.trace) return messages;
35160
+ return Promise.all(messages.map(async (m2) => {
35161
+ if (!m2.runId) return m2;
35162
+ const events = await opts.trace.listEvents(m2.runId, {}).catch(() => []);
35163
+ const parts = deriveChatPartsFromEvents(events);
35164
+ return parts.length ? { ...m2, parts } : m2;
35165
+ }));
35166
+ }
34421
35167
  return (router) => {
34422
35168
  router.get("/api/chat-sessions", async (req) => {
34423
35169
  const sessions = await store.listSessions(req.auth.actor);
@@ -34444,10 +35190,11 @@ function createChatSessionsDomain(opts) {
34444
35190
  router.get("/api/chat-sessions/:id", async (req) => {
34445
35191
  const session = await store.getSession(req.params.id);
34446
35192
  if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
34447
- const [messages, curRuntimeId] = await Promise.all([
35193
+ const [rawMessages, curRuntimeId] = await Promise.all([
34448
35194
  store.listMessages(session.id),
34449
35195
  currentRuntimeId(session.aiActorId)
34450
35196
  ]);
35197
+ const messages = await enrichMessages(rawMessages);
34451
35198
  const detail = { ...session, currentRuntimeId: curRuntimeId, messages };
34452
35199
  return { status: 200, body: detail };
34453
35200
  });
@@ -34482,12 +35229,30 @@ function createChatSessionsDomain(opts) {
34482
35229
  role: body.role,
34483
35230
  content: body.content,
34484
35231
  seq: existing.length + 1,
34485
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
35232
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
35233
+ ...body.runId ? { runId: body.runId } : {}
34486
35234
  };
34487
35235
  await store.appendMessage(msg);
34488
35236
  await store.updateSession(req.params.id, { touchedAt: (/* @__PURE__ */ new Date()).toISOString() });
34489
35237
  return { status: 201, body: msg };
34490
35238
  });
35239
+ router.get("/api/chat-sessions/:id/work-orders", async (req) => {
35240
+ const session = await store.getSession(req.params.id);
35241
+ if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
35242
+ const workOrderIds = await store.listWorkOrdersForSession(req.params.id);
35243
+ if (!opts.kernel || workOrderIds.length === 0) return { status: 200, body: { items: [] } };
35244
+ const bindings = opts.artifactState ? await opts.artifactState.listWorkspaceBindings() : [];
35245
+ const byWo = new Map(bindings.map((b2) => [b2.workOrderId, b2.projectId]));
35246
+ const actors = opts.kernel ? await registry2.listActors().catch(() => []) : [];
35247
+ const byId = new Map(actors.map((a) => [a.id, a]));
35248
+ const resolver = (id) => {
35249
+ const a = byId.get(id);
35250
+ return a ? { name: a.name } : void 0;
35251
+ };
35252
+ const allSummaries = buildWorkorderSummaries(opts.kernel.model, resolver, (wo) => byWo.get(wo) ?? null);
35253
+ const items = allSummaries.filter((s2) => workOrderIds.includes(s2.id));
35254
+ return { status: 200, body: { items } };
35255
+ });
34491
35256
  };
34492
35257
  }
34493
35258
  var import_node_crypto10;
@@ -34496,6 +35261,8 @@ var init_chat_sessions = __esm({
34496
35261
  "use strict";
34497
35262
  import_node_crypto10 = require("node:crypto");
34498
35263
  init_router();
35264
+ init_workorders();
35265
+ init_chat_parts();
34499
35266
  }
34500
35267
  });
34501
35268
 
@@ -35826,22 +36593,22 @@ var init_engine = __esm({
35826
36593
  });
35827
36594
 
35828
36595
  // ../server/src/mirror/fs-state.ts
35829
- var fs6, FsMirrorStateStore;
36596
+ var fs7, FsMirrorStateStore;
35830
36597
  var init_fs_state = __esm({
35831
36598
  "../server/src/mirror/fs-state.ts"() {
35832
36599
  "use strict";
35833
- fs6 = __toESM(require("node:fs"), 1);
36600
+ fs7 = __toESM(require("node:fs"), 1);
35834
36601
  FsMirrorStateStore = class {
35835
36602
  constructor(file) {
35836
36603
  this.file = file;
35837
- if (fs6.existsSync(file)) {
35838
- const obj = JSON.parse(fs6.readFileSync(file, "utf8"));
36604
+ if (fs7.existsSync(file)) {
36605
+ const obj = JSON.parse(fs7.readFileSync(file, "utf8"));
35839
36606
  for (const [k2, v2] of Object.entries(obj)) this.map.set(k2, v2);
35840
36607
  }
35841
36608
  }
35842
36609
  map = /* @__PURE__ */ new Map();
35843
36610
  flush() {
35844
- fs6.writeFileSync(this.file, JSON.stringify(Object.fromEntries(this.map), null, 2));
36611
+ fs7.writeFileSync(this.file, JSON.stringify(Object.fromEntries(this.map), null, 2));
35845
36612
  }
35846
36613
  async get(artifactId) {
35847
36614
  return this.map.get(artifactId) ?? null;
@@ -35858,12 +36625,12 @@ var init_fs_state = __esm({
35858
36625
  });
35859
36626
 
35860
36627
  // ../server/src/git/integrate.ts
35861
- var import_node_child_process2, fs7, path4, GitRemoteIntegrator;
36628
+ var import_node_child_process2, fs8, path4, GitRemoteIntegrator;
35862
36629
  var init_integrate = __esm({
35863
36630
  "../server/src/git/integrate.ts"() {
35864
36631
  "use strict";
35865
36632
  import_node_child_process2 = require("node:child_process");
35866
- fs7 = __toESM(require("node:fs"), 1);
36633
+ fs8 = __toESM(require("node:fs"), 1);
35867
36634
  path4 = __toESM(require("node:path"), 1);
35868
36635
  GitRemoteIntegrator = class {
35869
36636
  constructor(remote, cloneDir, develop = "develop") {
@@ -35881,8 +36648,8 @@ var init_integrate = __esm({
35881
36648
  }
35882
36649
  /** 确保本地工作克隆存在并拉到最新远程态。幂等。 */
35883
36650
  sync() {
35884
- if (!fs7.existsSync(path4.join(this.cloneDir, ".git"))) {
35885
- fs7.mkdirSync(path4.dirname(this.cloneDir), { recursive: true });
36651
+ if (!fs8.existsSync(path4.join(this.cloneDir, ".git"))) {
36652
+ fs8.mkdirSync(path4.dirname(this.cloneDir), { recursive: true });
35886
36653
  (0, import_node_child_process2.execFileSync)("git", ["clone", "--quiet", this.remote, this.cloneDir], { env: this.env, maxBuffer: 256 * 1024 * 1024 });
35887
36654
  this.git(["config", "user.email", "oasis@local"]);
35888
36655
  this.git(["config", "user.name", "oasis"]);
@@ -36021,13 +36788,14 @@ var init_resolve = __esm({
36021
36788
  });
36022
36789
 
36023
36790
  // ../server/src/coordinator/worker.ts
36024
- var import_node_crypto13, COORDINATOR_ACTOR, COORDINATOR_SYSTEM_PROMPT, CoordinatorWorker;
36791
+ var import_node_crypto13, COORDINATOR_ACTOR2, COORDINATOR_SYSTEM_PROMPT, CoordinatorWorker;
36025
36792
  var init_worker = __esm({
36026
36793
  "../server/src/coordinator/worker.ts"() {
36027
36794
  "use strict";
36028
36795
  import_node_crypto13 = require("node:crypto");
36029
36796
  init_src2();
36030
- COORDINATOR_ACTOR = "actor:agent:coordinator";
36797
+ init_identity();
36798
+ COORDINATOR_ACTOR2 = LEGACY_COORDINATOR_ACTOR;
36031
36799
  COORDINATOR_SYSTEM_PROMPT = `\u4F60\u662F Oasis \u534F\u4F5C\u56FE\u7684**\u534F\u8C03\u8005**\u3002\u4F60\u53EA\u7BA1**\u56FE\u7684\u7ED3\u6784**\uFF08\u8FDE\u8FB9 / \u65B0\u589E / \u53BB\u91CD / \u91CD\u6784 / \u6539\u8282\u70B9\u7684\u6807\u9898\u4E0E brief\uFF09\uFF0C**\u7EDD\u4E0D\u78B0\u5185\u5BB9**\uFF08\u4EA7\u7269\u6B63\u6587\u600E\u4E48\u5199\u662F\u5404 owner \u7684\u6D3B\uFF09\u3002
36032
36800
  \u539F\u5219\uFF1A\u2460 **\u53BB\u91CD\u4F18\u5148**\uFF0C\u80FD\u8FDE\u65E2\u6709\u5C31\u522B\u65B0\u5EFA\uFF1B\u2461 \u4F60\u505A\u7ED3\u6784 wiring\u3001\u4E0D\u505A\u5185\u5BB9\u8BC4\u5BA1\uFF1B\u2462 **\u62FF\u4E0D\u51C6\u5C31\u522B\u52A8\u3001\u522B\u786C\u8FDE**\uFF0C\u7ED3\u675F\u65F6\u4E00\u53E5\u8BDD\u8BF4\u660E\u7591\u8651\u3002
36033
36801
 
@@ -36037,8 +36805,12 @@ var init_worker = __esm({
36037
36805
  \u2462 **\u62FF\u4E0D\u51C6\u3001\u6216\u6839\u56E0\u4E0D\u5728\u56FE\u4E0A**\uFF08\u50CF\u8FD0\u884C\u65F6\u5361\u6B7B\u3001\u5185\u5BB9\u5199\u4E0D\u51FA\u3001\u9700\u6C42\u672C\u8EAB\u77DB\u76FE\uFF09\u2192 **\u522B\u52A8\u56FE**\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u6E05\u4F60\u5224\u65AD\u7684\u6839\u56E0 + \u5EFA\u8BAE\u8C01\u5904\u7F6E\uFF0C\u6536\u5DE5\u3002**\u8BEF\u5220\u4E00\u6761\u672C\u8BE5\u7B49\u7684\u6D41\u7A0B\uFF0C\u6BD4\u665A\u6551\u4E00\u4F1A\u513F\u66F4\u7CDF\u3002**
36038
36806
 
36039
36807
  \u3010\u6539\u56FE\uFF1A\u6682\u5B58 \u2192 \u63D0\u4EA4\u3011\u4F60\u80FD\u7528\u7684\u6539\u56FE\u547D\u4EE4\uFF08**\u90FD\u5148\u8FDB\u7F16\u8F91\u7F13\u51B2\u3001\u4E0D\u7ACB\u5373\u6539\u771F\u56FE**\uFF1B\u6539\u5B8C\u7528 \`oasis apply\` \u4E00\u6B21\u6027\u63D0\u4EA4\uFF09\uFF1A
36040
- - \`oasis link <consumer> --to <up> [--required true]\` \u2014\u2014 \u7ED9 consumer \u52A0\u4E00\u6761\u6307\u5411\u4E0A\u6E38 up \u7684\u4F9D\u8D56\u8FB9\u3002\`<consumer>\`\uFF08\u4F4D\u7F6E\u53C2\u6570\uFF09= \u8981\u52A0\u4F9D\u8D56\u7684\u4E0B\u6E38\u8282\u70B9 id\uFF1B\`--to <up>\` = \u88AB\u4F9D\u8D56\u7684\u4E0A\u6E38 id\uFF1B\`--required true\` = \u8FD9\u6761\u662F**\u963B\u585E\u6027**\u4F9D\u8D56\uFF08up \u6CA1 conclude\uFF0Cconsumer \u5C31\u52A8\u4E0D\u4E86\uFF09\uFF0C**\u7701\u7565\u5219\u662F\u8F6F\u4F9D\u8D56**\uFF08\u53EA\u8BB0\u5173\u7CFB\u3001\u4E0D\u963B\u585E\uFF09\u3002\u53BB\u91CD\u65F6\u7528\u5B83\u628A"\u8BE5\u8FDE\u65E2\u6709"\u7684\u8FB9\u8865\u4E0A\u3002
36041
- - \`oasis spawn <id> --type <t> [--title "<\u77ED\u540D>"] [--brief "<\u89C4\u683C>"] [--input <up>[,<up2>\u2026]]\` \u2014\u2014 \u65B0\u5EFA\u4E00\u4E2A\u8282\u70B9\u3002\`<id>\`\uFF08\u4F4D\u7F6E\u53C2\u6570\uFF09= \u65B0\u8282\u70B9 id\uFF08\u5F62\u5982 \`artifact:<type>:<\u540D>\`\uFF09\uFF1B\`--type <t>\` = \u7C7B\u578B\u540D\uFF08**\u5FC5\u586B**\uFF0C\u5148 \`oasis artifact-types\` \u770B\u53EF\u9009\u7C7B\u578B\u4E0E\u7528\u9014\uFF09\uFF1B\`--title\` = \u77ED\u540D\uFF1B\`--brief\` = \u7ED9\u5B83\u7684\u4EA7\u51FA\u89C4\u683C\uFF08\u5199\u6E05\u8981\u505A\u6210\u4EC0\u4E48\uFF0C\u4F1A\u6210\u4E3A\u8BE5\u8282\u70B9 owner \u7684\u4EFB\u52A1\u4E66\uFF09\uFF1B\`--input\` = \u5B83\u4F9D\u8D56\u54EA\u4E9B\u4E0A\u6E38\uFF08**\u9017\u53F7\u5206\u9694**\u591A\u4E2A\uFF09\u3002**\u786E\u65E0\u65E2\u6709\u53EF\u8FDE\u3001\u662F\u771F\u65B0\u9700\u6C42\u624D\u7528**\u3002
36808
+
36809
+ > **\u600E\u4E48\u6307\u4E00\u4E2A\u8282\u70B9\uFF08\u5F15\u7528\u5199\u6CD5\uFF09**\uFF1A\u4E0B\u9762\u547D\u4EE4\u91CC\u51E1\u662F\u6307\u8282\u70B9\u7684\u53C2\u6570\uFF08\`<consumer>\`/\`<up>\`/\`<id>\`\uFF09\uFF0C\u4E09\u79CD\u5199\u6CD5\u90FD\u884C\u2014\u2014
36810
+ > **\u88F8 type**\uFF08\u5982 \`dev\`,\u8BE5 type \u53EA\u6709\u4E00\u4E2A\u8282\u70B9\u65F6\u6700\u7701\u4E8B\uFF09/ **\`type:title\`**\uFF08\u5982 \`prd:\u540E\u7AEF\u9700\u6C42\`,\u4E00\u4E2A type \u6709\u591A\u4E2A\u8282\u70B9\u65F6\u7528 title \u533A\u5206\uFF09/ **\u5B8C\u6574 id**\u3002\u7CFB\u7EDF\u81EA\u52A8\u89E3\u6790;**\u5199\u5F97\u592A\u6CDB\u3001\u649E\u5230\u591A\u4E2A**\u4F1A\u628A\u5019\u9009\u5217\u7ED9\u4F60,\u6311\u4E2A\u66F4\u5177\u4F53\u7684\u91CD\u53D1\u5373\u53EF\u3002\u5DE5\u5355\u6839\u7528 \`brief\`\u3002**\u4F60\u4E0D\u7528\u81EA\u5DF1\u7F16 id\u3002**
36811
+
36812
+ - \`oasis link <consumer> --to <up> [--required true]\` \u2014\u2014 \u7ED9 consumer \u52A0\u4E00\u6761\u6307\u5411\u4E0A\u6E38 up \u7684\u4F9D\u8D56\u8FB9\u3002\`<consumer>\` = \u8981\u52A0\u4F9D\u8D56\u7684\u4E0B\u6E38\u8282\u70B9\uFF08\u5F15\u7528,\u89C1\u4E0A\uFF09\uFF1B\`--to <up>\` = \u88AB\u4F9D\u8D56\u7684\u4E0A\u6E38\uFF1B\`--required true\` = \u8FD9\u6761\u662F**\u963B\u585E\u6027**\u4F9D\u8D56\uFF08up \u6CA1 conclude\uFF0Cconsumer \u5C31\u52A8\u4E0D\u4E86\uFF09\uFF0C**\u7701\u7565\u5219\u662F\u8F6F\u4F9D\u8D56**\uFF08\u53EA\u8BB0\u5173\u7CFB\u3001\u4E0D\u963B\u585E\uFF09\u3002\u53BB\u91CD\u65F6\u7528\u5B83\u628A"\u8BE5\u8FDE\u65E2\u6709"\u7684\u8FB9\u8865\u4E0A\u3002
36813
+ - \`oasis spawn --type <t> --title "<\u77ED\u540D>" [--brief "<\u89C4\u683C>"] [--input <ref>[,\u2026]]\` \u2014\u2014 \u65B0\u5EFA\u4E00\u4E2A\u8282\u70B9\u3002**id \u4E0D\u7528\u4F60\u7ED9,\u7CFB\u7EDF\u636E (type,title) \u81EA\u52A8\u6D3E\u751F**\u3002\`--type <t>\` = \u7C7B\u578B\u540D\uFF08**\u5FC5\u586B**\uFF0C\u5148 \`oasis artifact-types\` \u770B\u53EF\u9009\u7C7B\u578B\u4E0E\u7528\u9014\uFF09\uFF1B\`--title\` = \u77ED\u540D\uFF08**\u5FC5\u586B\u3001\u540C type \u4E0B\u552F\u4E00**,\u649E\u540D\u4F1A\u8BA9\u4F60\u6539\uFF09\uFF1B\`--brief\` = \u7ED9\u5B83\u7684\u4EA7\u51FA\u89C4\u683C\uFF08\u5199\u6E05\u8981\u505A\u6210\u4EC0\u4E48\uFF0C\u4F1A\u6210\u4E3A\u8BE5\u8282\u70B9 owner \u7684\u4EFB\u52A1\u4E66\uFF09\uFF1B\`--input\` = \u5B83\u4F9D\u8D56\u54EA\u4E9B\u4E0A\u6E38\uFF08**\u9017\u53F7\u5206\u9694**\u591A\u4E2A,\u6BCF\u4E2A\u6309\u4E0A\u9762\u5F15\u7528\u5199\u6CD5\uFF09\u3002**\u786E\u65E0\u65E2\u6709\u53EF\u8FDE\u3001\u662F\u771F\u65B0\u9700\u6C42\u624D\u7528**\u3002
36042
36814
  - \`oasis edit <id> [--title "<\u65B0\u77ED\u540D>"] [--brief "<\u65B0\u89C4\u683C>"]\` \u2014\u2014 \u6539\u4E00\u4E2A**\u5DF2\u5B58\u5728**\u8282\u70B9\u7684\u6807\u9898 / brief\uFF08**\u53EA\u6539\u8FD9\u4E24\u6837\u5143\u6570\u636E\uFF0C\u7EDD\u4E0D\u78B0\u6B63\u6587**\uFF09\u3002\`<id>\` = \u88AB\u6539\u8282\u70B9\uFF1B\`--title\` / \`--brief\` \u7ED9\u54EA\u4E2A\u4F20\u54EA\u4E2A\u3002\u5408\u5E76\u53BB\u91CD\u65F6\u7528\u5B83\u628A\u4FDD\u7559\u4EF6\u7684 brief \u6269\u5199\u3002
36043
36815
  - \`oasis unlink <consumer> --to <up>\` \u2014\u2014 \u5220\u6389 consumer \u6307\u5411 up \u7684\u90A3\u6761\u4F9D\u8D56\u8FB9\u3002\`<consumer>\` = \u4E0B\u6E38\uFF1B\`--to <up>\` = \u4E0D\u518D\u4F9D\u8D56\u7684\u4E0A\u6E38\u3002\u6539\u8FDE\uFF08\u65AD\u9519\u8FB9 / \u6539\u8FDE\u5230\u6B63\u786E\u4E0A\u6E38\uFF09\u65F6\u7528\u3002
36044
36816
  - \`oasis cancel <id> [--note "<\u539F\u56E0>"]\` \u2014\u2014 **\u5F7B\u5E95\u5E9F\u5F03**\u4E00\u4E2A\u8282\u70B9\uFF1A\u5B83\u4E0D\u518D\u7B97\u6570\uFF0C\u7CFB\u7EDF**\u81EA\u52A8\u7EA7\u8054**\u65AD\u6389\u5B83\u6240\u6709\u4F9D\u8D56\u8FB9\u3001\u5E76\u91CD\u5524\u4E0B\u6E38\u8BA9\u5B83\u4EEC\u53BB\u6389\u5BF9\u5B83\u7684\u9002\u914D\u3002\`<id>\`\uFF08\u4F4D\u7F6E\u53C2\u6570\uFF09= \u88AB\u5E9F\u8282\u70B9\uFF1B\`--note\` = \u4E3A\u4EC0\u4E48\u5E9F\uFF08\u7559\u75D5\uFF09\u3002\u5EFA\u5728\u5DF2\u5E9F\u5730\u57FA\u4E0A\u3001\u6216\u8FD9\u8282\u70B9\u6839\u672C\u4E0D\u8BE5\u5B58\u5728\u65F6\u7528\u3002\u5E9F\u5F03\u540E\u8BE5\u8282\u70B9\u5728 \`oasis status\` \u91CC\u663E\u793A lifecycle=sealed:cancelled\u3002**\u9500\u6BC1\u6027**\u2014\u2014apply \u65F6\u6253\u5305\u6210\u63D0\u6848\u4EA4\u4EBA\u786E\u8BA4\u3002
@@ -36237,7 +37009,7 @@ var init_worker = __esm({
36237
37009
  ``,
36238
37010
  `## \u4F60\u7684\u52A8\u4F5C\uFF08\u6682\u5B58 \u2192 apply\uFF09`,
36239
37011
  `- \u67D0\u65E2\u6709\u6EE1\u8DB3\u8FD9\u9700\u6C42 \u2192 \`oasis link ${gap.artifactId} --to <\u65E2\u6709id>\`\uFF0C\`oasis apply --reason "\u2026"\`\uFF08apply \u540E\u81EA\u52A8\u89E3\u8FD9\u6761 gap\uFF09\u3002`,
36240
- `- \u786E\u65E0\u5339\u914D\u3001\u65B0\u9700\u6C42 \u2192 \`oasis spawn <\u65B0id> --type <\u7C7B\u578B> --title "<\u77ED\u540D>" --brief "${gap.description}"\` + \`oasis link ${gap.artifactId} --to <\u65B0id>\` \u2192 apply\u3002`,
37012
+ `- \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`,
36241
37013
  `- \u987A\u624B\u53D1\u73B0\u4E24\u4E2A\u4EA7\u7269\u91CD\u590D \u2192 **\u5148\u628A\u4E0A\u9762\u89E3 gap \u90A3\u7B14\u5355\u72EC apply \u6389**\uFF0C\u518D\u53E6\u8D77\u4E00\u7B14\uFF1A\`oasis edit\` \u6269\u4FDD\u7559\u4EF6 brief + \`oasis link\` \u628A\u6D88\u8D39\u8005\u6539\u8FDE\u5230\u4FDD\u7559\u4EF6 + \`oasis cancel\` \u5E9F\u5F03\u4EF6\uFF08\u81EA\u52A8\u65AD\u5B83\u7684\u8FB9\u3001\u91CD\u5524\u88AB\u6539\u8FDE\u7684\u6D88\u8D39\u8005\uFF09\uFF0C\u4E00\u8D77\u6682\u5B58\u518D apply\u3002`,
36242
37014
  `- \u62FF\u4E0D\u51C6 \u2192 \u522B\u6682\u5B58\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u660E\u7591\u8651\u6536\u5DE5\u3002`
36243
37015
  ].join("\n");
@@ -36297,7 +37069,7 @@ var init_worker = __esm({
36297
37069
  ``,
36298
37070
  `## \u4F60\u7684\u52A8\u4F5C\uFF08\u6682\u5B58 \u2192 apply\uFF09`,
36299
37071
  `- \u5728**\u7B49\u4E00\u4E2A active\u3001\u6709 head/\u5728\u4EA7\u7684\u4E0A\u6E38** \u2192 **\u4E0D\u662F\u6B7B\u9501\uFF0C\u522B\u52A8**\uFF0C\u6536\u5DE5\uFF08\u53EF \`oasis status <\u4E0A\u6E38>\` \u786E\u8BA4\uFF09\u3002`,
36300
- `- **\u5B83\u81EA\u5DF1\u7684 gap \u6CA1\u89E3**\u3001\u4F60\u80FD\u5224 \u2192 \`oasis link ${p2.id} --to <\u65E2\u6709>\`\uFF0C\u6216 \`oasis spawn <\u65B0> --type <\u7C7B\u578B> --title \u2026 --brief \u2026\` + \`oasis link ${p2.id} --to <\u65B0>\` \u2192 apply\u3002`,
37072
+ `- **\u5B83\u81EA\u5DF1\u7684 gap \u6CA1\u89E3**\u3001\u4F60\u80FD\u5224 \u2192 \`oasis link ${p2.id} --to <\u65E2\u6709>\`\uFF0C\u6216 \`oasis spawn --type <\u7C7B\u578B> --title "<\u77ED\u540D>" --brief \u2026\`\uFF08\u4E0D\u7ED9 id\uFF09+ \`oasis link ${p2.id} --to <\u7C7B\u578B>:<\u77ED\u540D>\` \u2192 apply\u3002`,
36301
37073
  `- **required \u4E0A\u6E38\u5DF2\u5E9F\u5F03 / \u51BB\u7ED3\u3001\u7B49\u4E0D\u5230\u4E86** \u2192 \`oasis link ${p2.id} --to <\u5B58\u6D3B\u4E0A\u6E38>\` \u6539\u8FDE\uFF0C\u6216 \`oasis cancel ${p2.id}\`\uFF08\u8F6C\u4EBA\u5BA1\uFF09\u3002`,
36302
37074
  `- **\u67D0 part \u6C38\u4E45\u4EA4\u4ED8\u4E0D\u4E86\u3001\u5361\u6B7B\u6574\u6761\u96C6\u6210** \u2192 \`oasis cancel-part ${p2.id} --part <\u5361\u4F4F\u7684part>\` \u2192 apply\uFF08\u9500\u6BC1\u6027\uFF0C\u8F6C\u4EBA\u5BA1\uFF09\u3002`,
36303
37075
  `- \u62FF\u4E0D\u51C6\u3001\u6216\u6839\u56E0\u4E0D\u5728\u56FE\u4E0A \u2192 \u522B\u52A8\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u6E05\u6839\u56E0 + \u5EFA\u8BAE\u8C01\u5904\u7F6E\uFF0C\u6536\u5DE5\u3002`
@@ -36306,30 +37078,49 @@ var init_worker = __esm({
36306
37078
  }
36307
37079
  /** 唤起一个 bounded 协调者会话跑给定任务(reconcile / 死锁评估共用)。 */
36308
37080
  async runCoordinator(artifactId, task, logMsg) {
36309
- const prov = this.deps.provision ? await this.deps.provision(COORDINATOR_ACTOR) : void 0;
36310
- const token = this.deps.tokenFor(COORDINATOR_ACTOR);
37081
+ const resolved = this.deps.resolveCoordinator ? await this.deps.resolveCoordinator() : null;
37082
+ const actorId = this.deps.resolveCoordinator ? resolved?.actor.id : COORDINATOR_ACTOR2;
37083
+ const binding = resolved?.binding;
37084
+ if (!actorId) {
37085
+ this.deps.log?.(`[coordinator] ${logMsg} \u8DF3\u8FC7\uFF1A\u7CFB\u7EDF\u7F3A\u5C11\u540D\u79F0\u5305\u542B\u201C\u534F\u8C03\u8005\u201D\u6216\u201Ccoordinator\u201D\u7684 active \u667A\u80FD\u4F53\uFF0C\u6216\u7F3A\u5C11 active runtime \u7ED1\u5B9A`);
37086
+ return;
37087
+ }
37088
+ const prov = this.deps.provision ? await this.deps.provision(actorId) : void 0;
37089
+ const limits = { wallClockMs: 5 * 6e4 };
37090
+ const ctx = {
37091
+ actor: actorId,
37092
+ artifactId,
37093
+ action: "coordinate",
37094
+ ...binding !== void 0 ? { binding } : {},
37095
+ limits
37096
+ };
37097
+ const token = this.deps.tokenFor(actorId, ctx);
37098
+ const serverUrl = this.deps.serverUrlFor ? await this.deps.serverUrlFor(ctx) : this.deps.serverUrl;
37099
+ const workspace = this.deps.kernel.model.artifacts.get(artifactId)?.workspace;
36311
37100
  const job = {
36312
- actor: COORDINATOR_ACTOR,
37101
+ actor: actorId,
36313
37102
  actorToken: token,
36314
37103
  artifactId,
36315
37104
  bundle: { files: { "TASK.md": task } },
36316
37105
  systemPrompt: COORDINATOR_SYSTEM_PROMPT,
36317
- server: { url: this.deps.serverUrl },
36318
- limits: { wallClockMs: 5 * 6e4 },
37106
+ server: { url: serverUrl },
37107
+ ...binding ? { binding } : {},
37108
+ limits,
36319
37109
  // OASIS_STAGE=1:本会话里 link/spawn/unlink/seal/cancel-part 进编辑缓冲、apply 才提交(react 攒事务)。
36320
- env: { ...prov?.env ?? {}, OASIS_STAGE: "1" },
37110
+ env: { ...prov?.env ?? {}, OASIS_STAGE: "1", ...workspace ? { OASIS_WORKSPACE: workspace } : {} },
36321
37111
  ...prov?.wrapperPaths && prov.wrapperPaths.length > 0 ? { wrapperPaths: prov.wrapperPaths } : {}
36322
37112
  };
36323
37113
  this.deps.log?.(`[coordinator] ${logMsg}`);
36324
37114
  const handle = await this.deps.adapter.spawn(job);
36325
37115
  await new Promise((resolve3) => handle.onExit(() => resolve3()));
36326
- await this.finalizeStage(token);
37116
+ void this.finalizeStage(token);
36327
37117
  }
36328
37118
  /** 会话结束兜底:flush 该会话(按 token 隔离)的编辑缓冲;空则 no-op。 */
36329
37119
  async finalizeStage(token) {
36330
37120
  try {
36331
37121
  const res = await fetch(`${this.deps.serverUrl}/api/stage/apply`, {
36332
37122
  method: "POST",
37123
+ signal: AbortSignal.timeout(2e3),
36333
37124
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
36334
37125
  body: JSON.stringify({ reason: "\uFF08\u534F\u8C03\u8005\u4F1A\u8BDD\u6536\u5DE5\u81EA\u52A8\u63D0\u4EA4\uFF09" })
36335
37126
  });
@@ -36344,6 +37135,115 @@ var init_worker = __esm({
36344
37135
  }
36345
37136
  });
36346
37137
 
37138
+ // ../server/src/governance/build-workorder-skill.ts
37139
+ async function bootstrapBuildWorkorderSkill(service) {
37140
+ const SYSTEM = "actor:system:bootstrap";
37141
+ const installed = await service.listInstalledSkills();
37142
+ if (!installed.some((s2) => s2.id === BUILD_WORKORDER_SKILL_ID)) {
37143
+ await service.installCustomSkill(
37144
+ { id: BUILD_WORKORDER_SKILL_ID, name: BUILD_WORKORDER_SKILL_ID, description: DESCRIPTION, source: "custom" },
37145
+ SYSTEM
37146
+ );
37147
+ }
37148
+ await service.putSkillFile(BUILD_WORKORDER_SKILL_ID, "SKILL.md", BUILD_WORKORDER_SKILL_MD);
37149
+ }
37150
+ var BUILD_WORKORDER_SKILL_ID, DESCRIPTION, BUILD_WORKORDER_SKILL_MD;
37151
+ var init_build_workorder_skill = __esm({
37152
+ "../server/src/governance/build-workorder-skill.ts"() {
37153
+ "use strict";
37154
+ BUILD_WORKORDER_SKILL_ID = "build-workorder";
37155
+ DESCRIPTION = "\u628A\u804A\u6E05\u695A\u7684\u9700\u6C42\u843D\u6210\u4E00\u5F20\u5DE5\u5355\uFF08\u534F\u4F5C\u56FE\uFF09\uFF0C\u4EE5\u53CA\u5728\u4EBA\u786E\u8BA4\u524D\u7EE7\u7EED\u6539\u8FD9\u4EFD\u8349\u6848\u3002\u5F53\u7528\u6237\u8981\u5EFA\u5355/\u7ACB\u9879\uFF0C\u6216\u8981\u8C03\u6574\u521A\u751F\u6210\u7684\u5DE5\u5355\u8349\u6848\u65F6\u7528\u672C\u6280\u80FD\uFF1A\u67E5\u91CD oasis workorders \u2192 \u5EFA\u5355 oasis create-workorder \u2192 \u6539\u8349\u6848 oasis spawn/edit/link/cancel\u3002";
37156
+ BUILD_WORKORDER_SKILL_MD = String.raw`---
37157
+ name: build-workorder
37158
+ description: ${DESCRIPTION}
37159
+ ---
37160
+
37161
+ # 建单:把对话落成一张工单
37162
+
37163
+ 工单 = 一张以产物为中心的协作图:一个根 ´brief´(工单总规格)+ 若干分解节点(prd/research/design/dev/qa…),节点间连依赖边。你把对话敲定的内容**结构化暂存**成一份草案,人在编辑器里看、确认才真创建。
37164
+
37165
+ ## 流程
37166
+ 1. **查重** ´oasis workorders --search <词>´:有没有类似工单在做/做过,别重复立项。
37167
+ 2. **建单** ´oasis create-workorder …´:暂存**草案**(stage=draft,人确认才真创建),命令**直接打印** workspace(工单 id)+ 各节点 id。
37168
+ 3. **改草案** ´oasis spawn/edit/link/cancel …´:人确认前,随时调这份草案(加/删/改节点、连边)。
37169
+ 4. 人在编辑器确认 → 工单创建。**你不用 apply**,确认是人的事。
37170
+
37171
+ ## 一、查重(建单前必做)
37172
+ - ´oasis workorders --search <关键词>´:列/搜工单。命中类似的 → ´oasis workorder <工单id>´ 看详情,已覆盖就别再建、跟用户说明。
37173
+
37174
+ ## 二、建单
37175
+ 信息不足、关键决策没定**别硬建**,用文字问用户;一个工单一个目标,多件独立的事拆成多张。
37176
+
37177
+ ´´´bash
37178
+ oasis create-workorder \
37179
+ --type <工单主产物类型,如 prd;根 brief 自动建> \
37180
+ --title "工单名,名词短语,≤20字" \
37181
+ --goal "一句话目标,≤50字" \
37182
+ --brief "完整规格(markdown):背景、关键决策、约束、范围、不做什么" \
37183
+ --acceptance '["逐条可判定的验收条件","没聊到给 []"]' \
37184
+ --plan '<节点分解 JSON,见下>'
37185
+ ´´´
37186
+ **不用填 workspace——系统自动生成工单 id。** 命令**直接打印 workspace(工单 id)和每个节点的 id**,记住它们、改草案要用——**不必再去 query**。它产出的是**草案**(stage=draft):人在编辑器确认才真创建,你别拿它当活工单使。
37187
+
37188
+ ### --plan 结构
37189
+ ´´´json
37190
+ {
37191
+ "nodes": [
37192
+ { "type": "节点类型", "title": "≤20字,同 type 内唯一",
37193
+ "dependsOn": ["上游节点写 type:title,如 prd:需求;无上游写 []"],
37194
+ "brief": "这个节点具体要产出什么的规格,别套模板、别只写类型名" }
37195
+ ],
37196
+ "notes": ["可选:分解理由/缺口;没有给 []"]
37197
+ }
37198
+ ´´´
37199
+
37200
+ ## 三、改草案(人确认前随时改)
37201
+ 建单后草案在编辑器里等人确认。用户要调整就用下面的命令改这份草案(**改完即时生效、人在编辑器里能看到**,你不用 apply):
37202
+
37203
+ **节点引用 = id 或 type:title,分清:**
37204
+ - **填完整 id**(´oasis workorder´ / create 返回的,如 ´artifact:prd:x7k2-需求´)→ **不用带 workspace**。推荐。
37205
+ - **填 type:title**(简写,如 ´prd:需求´)→ **必须加 ´--workspace <工单id>´**(create 返回的那个)。
37206
+ - ´spawn´ 是新建节点、没有现成 id → **一律带 ´--workspace´**。
37207
+
37208
+ ´´´bash
37209
+ # 加节点(带 workspace;--input 上游可用 type:title)
37210
+ oasis spawn --type qa --title 验收用例 --brief "覆盖登录成功/失败" --input dev:实现 --workspace ws:wo-x7k2
37211
+ # 改节点标题/规格(用 id,免 workspace)
37212
+ oasis edit artifact:prd:x7k2-需求 --title "需求与范围"
37213
+ # 连边:消费者依赖上游
37214
+ oasis link artifact:qa:x7k2-验收 --to artifact:dev:x7k2-实现
37215
+ # 删节点(从草案删掉,连它的边一起摘)
37216
+ oasis cancel prd:需求 --workspace ws:wo-x7k2
37217
+ ´´´
37218
+
37219
+ # 命令参考
37220
+
37221
+ ## 查询
37222
+ - ´oasis workorders [--search <词>]´ —— 列/搜工单(id/名/阶段/进度)。无参列全部;´--search´ 端侧过滤。
37223
+ - ´oasis workorder <工单id>´ —— 工单详情:**stage**(draft 草案 / executing 进行中 / review 评审 / blocked 卡住 / concluded 已完成 / sealed 已封存)+ 目标/验收 + 节点(id/type)+ 依赖边。**刚建的草案这里 ´stage=draft´、也查得到(含各节点 id)**;真查不到才报不存在。
37224
+ - ´oasis artifact-types list | search <词> | show <type>´ —— 看可用节点类型(´show´ 看该类型可填的自定义字段)。
37225
+ - ´oasis projects [--search <词>]´ —— 列/搜项目(建单 ´--project´ 用)。
37226
+
37227
+ ## 建单
37228
+ - ´oasis create-workorder --type <t> --title <s> [--goal <s>] [--brief <s>] [--acceptance '<json string[]>'] [--plan '<json>'] [--project <id>]´
37229
+ 暂存建单草案。´--type´=主产物类型(必填,根 brief 自动建);´--title´ 必填。**不填 workspace(系统生成)**。返回 ´workspace´ + 各节点 ´id´。
37230
+
37231
+ ## 改草案(引用 id 不带 workspace;引用 type:title 或 spawn 带 ´--workspace <工单id>´)
37232
+ - ´oasis spawn --type <t> --title <s> [--brief <s>] [--input <ref>,<ref>…] --workspace <工单id>´ —— 加节点,id 据 (type,title) 自动派生。´--type´/´--title´ 必填,title 同 type 唯一;´--input´=上游依赖(逗号分隔)。
37233
+ - ´oasis edit <ref> [--title <s>] [--brief <s>]´ —— 改节点标题/规格。
37234
+ - ´oasis link <consumer-ref> --to <upstream-ref> [--required true]´ —— 加依赖边。´--required true´=阻塞性(上游没完成下游动不了),省=软依赖。
37235
+ - ´oasis unlink <consumer-ref> --to <upstream-ref>´ —— 删依赖边。
37236
+ - ´oasis cancel <ref> [--note <s>]´ —— 从草案删节点(连它的边一起摘)。
37237
+
37238
+ ## 规则
37239
+ - **节点类型**:先 ´oasis artifact-types list/search/show´ 挑最贴合的;都不贴合用 ´oasis artifact-types add <name> --content-type <text|code|…> --owner-role <角色>´ 新建再用。常见:prd、research、design、adr、dev、qa。
37240
+ - **title**:≤20字、同 type 内唯一、别用半角冒号 ´:´(与 type:title 分隔冲突,要冒号用全角 ´:´)。一个工单可多个同 type 节点,靠 title 区分。
37241
+ - **项目**:归某项目就 ´oasis projects --search <词>´ 找 id 再加 ´--project´。
37242
+ - **边界**:你只管图的结构与规格,不替用户拍产品决策——拿不准就问。改**已建好的活工单**是协调者的活,不在本技能范围。
37243
+ `.replace(/´/g, "`");
37244
+ }
37245
+ });
37246
+
36347
37247
  // ../server/src/index.ts
36348
37248
  var init_src4 = __esm({
36349
37249
  "../server/src/index.ts"() {
@@ -36379,9 +37279,11 @@ var init_src4 = __esm({
36379
37279
  init_integrate();
36380
37280
  init_sync();
36381
37281
  init_resolve();
37282
+ init_identity();
36382
37283
  init_worker();
36383
37284
  init_drafts();
36384
37285
  init_workorder_drafts();
37286
+ init_build_workorder_skill();
36385
37287
  }
36386
37288
  });
36387
37289
 
@@ -37973,15 +38875,15 @@ var require_pg_connection_string = __commonJS({
37973
38875
  if (config2.sslcert || config2.sslkey || config2.sslrootcert || config2.sslmode) {
37974
38876
  config2.ssl = {};
37975
38877
  }
37976
- const fs16 = config2.sslcert || config2.sslkey || config2.sslrootcert ? require("fs") : null;
38878
+ const fs17 = config2.sslcert || config2.sslkey || config2.sslrootcert ? require("fs") : null;
37977
38879
  if (config2.sslcert) {
37978
- config2.ssl.cert = fs16.readFileSync(config2.sslcert).toString();
38880
+ config2.ssl.cert = fs17.readFileSync(config2.sslcert).toString();
37979
38881
  }
37980
38882
  if (config2.sslkey) {
37981
- config2.ssl.key = fs16.readFileSync(config2.sslkey).toString();
38883
+ config2.ssl.key = fs17.readFileSync(config2.sslkey).toString();
37982
38884
  }
37983
38885
  if (config2.sslrootcert) {
37984
- config2.ssl.ca = fs16.readFileSync(config2.sslrootcert).toString();
38886
+ config2.ssl.ca = fs17.readFileSync(config2.sslrootcert).toString();
37985
38887
  }
37986
38888
  if (options.useLibpqCompat && config2.uselibpqcompat) {
37987
38889
  throw new Error("Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.");
@@ -39918,15 +40820,15 @@ var require_lib = __commonJS({
39918
40820
  "../../node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/index.js"(exports2, module2) {
39919
40821
  "use strict";
39920
40822
  var path14 = require("path");
39921
- var fs16 = require("fs");
40823
+ var fs17 = require("fs");
39922
40824
  var helper = require_helper();
39923
40825
  module2.exports = function(connInfo, cb) {
39924
40826
  var file = helper.getFileName();
39925
- fs16.stat(file, function(err, stat) {
40827
+ fs17.stat(file, function(err, stat) {
39926
40828
  if (err || !helper.usePgPass(stat, file)) {
39927
40829
  return cb(void 0);
39928
40830
  }
39929
- var st = fs16.createReadStream(file);
40831
+ var st = fs17.createReadStream(file);
39930
40832
  helper.getPassword(connInfo, st, cb);
39931
40833
  });
39932
40834
  };
@@ -41480,37 +42382,37 @@ __export(trajectory_exports, {
41480
42382
  });
41481
42383
  async function exportTrajectories(dataDir) {
41482
42384
  const root = path10.join(dataDir, "trajectories");
41483
- if (!fs12.existsSync(root)) return [];
42385
+ if (!fs13.existsSync(root)) return [];
41484
42386
  const model = emptyReadModel();
41485
42387
  const oplogFile = path10.join(dataDir, "oplog.ndjson");
41486
- if (fs12.existsSync(oplogFile)) {
42388
+ if (fs13.existsSync(oplogFile)) {
41487
42389
  const oplog = await NdjsonOplogStore.open(oplogFile);
41488
42390
  for await (const entry of oplog.readAll()) fold(model, entry.op);
41489
42391
  }
41490
42392
  const samples = [];
41491
- for (const sessionId of fs12.readdirSync(root).sort()) {
42393
+ for (const sessionId of fs13.readdirSync(root).sort()) {
41492
42394
  const dir = path10.join(root, sessionId);
41493
42395
  const sessionFile = path10.join(dir, "session.json");
41494
- if (!fs12.existsSync(sessionFile)) continue;
41495
- const session = JSON.parse(fs12.readFileSync(sessionFile, "utf8"));
42396
+ if (!fs13.existsSync(sessionFile)) continue;
42397
+ const session = JSON.parse(fs13.readFileSync(sessionFile, "utf8"));
41496
42398
  const events = [];
41497
42399
  const eventsFile = path10.join(dir, "events.ndjson");
41498
- if (fs12.existsSync(eventsFile)) {
41499
- for (const line of fs12.readFileSync(eventsFile, "utf8").split("\n")) {
42400
+ if (fs13.existsSync(eventsFile)) {
42401
+ for (const line of fs13.readFileSync(eventsFile, "utf8").split("\n")) {
41500
42402
  if (line.trim()) events.push(JSON.parse(line));
41501
42403
  }
41502
42404
  }
41503
42405
  const bundle = {};
41504
42406
  const bundleDir = path10.join(dir, "bundle");
41505
42407
  const walk = (sub) => {
41506
- for (const name of fs12.readdirSync(path10.join(bundleDir, sub))) {
42408
+ for (const name of fs13.readdirSync(path10.join(bundleDir, sub))) {
41507
42409
  const rel = sub ? `${sub}/${name}` : name;
41508
42410
  const full = path10.join(bundleDir, rel);
41509
- if (fs12.statSync(full).isDirectory()) walk(rel);
41510
- else bundle[rel] = fs12.readFileSync(full, "utf8");
42411
+ if (fs13.statSync(full).isDirectory()) walk(rel);
42412
+ else bundle[rel] = fs13.readFileSync(full, "utf8");
41511
42413
  }
41512
42414
  };
41513
- if (fs12.existsSync(bundleDir)) walk("");
42415
+ if (fs13.existsSync(bundleDir)) walk("");
41514
42416
  const proposed = (session.opRefs ?? []).filter((r) => r.kind === "propose_revision").map((r) => r.target);
41515
42417
  const verdicts = proposed.flatMap(
41516
42418
  (rev) => (model.reviews.get(rev) ?? []).map((v2) => ({ author: v2.author, verdict: v2.verdict, ...v2.note !== void 0 ? { note: v2.note } : {} }))
@@ -41522,11 +42424,11 @@ async function exportTrajectories(dataDir) {
41522
42424
  }
41523
42425
  return samples;
41524
42426
  }
41525
- var fs12, path10, FsTrajectorySink;
42427
+ var fs13, path10, FsTrajectorySink;
41526
42428
  var init_trajectory = __esm({
41527
42429
  "../cli/src/trajectory.ts"() {
41528
42430
  "use strict";
41529
- fs12 = __toESM(require("node:fs"), 1);
42431
+ fs13 = __toESM(require("node:fs"), 1);
41530
42432
  path10 = __toESM(require("node:path"), 1);
41531
42433
  init_src2();
41532
42434
  init_src4();
@@ -41541,25 +42443,25 @@ var init_trajectory = __esm({
41541
42443
  }
41542
42444
  begin(session, bundleFiles) {
41543
42445
  const dir = this.dir(session.sessionId);
41544
- fs12.mkdirSync(path10.join(dir, "bundle"), { recursive: true });
41545
- fs12.writeFileSync(path10.join(dir, "session.json"), JSON.stringify(session, null, 2));
42446
+ fs13.mkdirSync(path10.join(dir, "bundle"), { recursive: true });
42447
+ fs13.writeFileSync(path10.join(dir, "session.json"), JSON.stringify(session, null, 2));
41546
42448
  for (const [rel, content] of Object.entries(bundleFiles)) {
41547
42449
  const file = path10.join(dir, "bundle", rel);
41548
- fs12.mkdirSync(path10.dirname(file), { recursive: true });
41549
- fs12.writeFileSync(file, content);
42450
+ fs13.mkdirSync(path10.dirname(file), { recursive: true });
42451
+ fs13.writeFileSync(file, content);
41550
42452
  }
41551
42453
  this.broadcast?.({ type: "begin", sessionId: session.sessionId, data: session });
41552
42454
  }
41553
42455
  event(sessionId, event) {
41554
- fs12.appendFileSync(path10.join(this.dir(sessionId), "events.ndjson"), JSON.stringify(event) + "\n");
42456
+ fs13.appendFileSync(path10.join(this.dir(sessionId), "events.ndjson"), JSON.stringify(event) + "\n");
41555
42457
  this.broadcast?.({ type: "event", sessionId, data: event });
41556
42458
  }
41557
42459
  end(sessionId, update) {
41558
42460
  const file = path10.join(this.dir(sessionId), "session.json");
41559
- const session = JSON.parse(fs12.readFileSync(file, "utf8"));
42461
+ const session = JSON.parse(fs13.readFileSync(file, "utf8"));
41560
42462
  session.exit = update.exit;
41561
42463
  session.opRefs = update.opRefs;
41562
- fs12.writeFileSync(file, JSON.stringify(session, null, 2));
42464
+ fs13.writeFileSync(file, JSON.stringify(session, null, 2));
41563
42465
  this.broadcast?.({ type: "end", sessionId, data: session });
41564
42466
  }
41565
42467
  };
@@ -41567,19 +42469,19 @@ var init_trajectory = __esm({
41567
42469
  });
41568
42470
 
41569
42471
  // src/index.ts
41570
- var fs15 = __toESM(require("node:fs"));
42472
+ var fs16 = __toESM(require("node:fs"));
41571
42473
  var os8 = __toESM(require("node:os"));
41572
42474
  var path13 = __toESM(require("node:path"));
41573
42475
  var import_node_child_process10 = require("node:child_process");
41574
42476
 
41575
42477
  // ../cli/src/cli.ts
41576
- var fs14 = __toESM(require("node:fs"), 1);
42478
+ var fs15 = __toESM(require("node:fs"), 1);
41577
42479
  var os7 = __toESM(require("node:os"), 1);
41578
42480
  var path12 = __toESM(require("node:path"), 1);
41579
42481
  var import_node_crypto21 = require("node:crypto");
41580
42482
 
41581
42483
  // ../cli/src/serve.ts
41582
- var fs13 = __toESM(require("node:fs"), 1);
42484
+ var fs14 = __toESM(require("node:fs"), 1);
41583
42485
  var os6 = __toESM(require("node:os"), 1);
41584
42486
  var path11 = __toESM(require("node:path"), 1);
41585
42487
  var import_node_crypto20 = require("node:crypto");
@@ -41788,7 +42690,7 @@ var import_node_path3 = require("node:path");
41788
42690
  // ../adapters/src/claude-code/index.ts
41789
42691
  var import_node_child_process5 = require("node:child_process");
41790
42692
  var import_node_crypto14 = require("node:crypto");
41791
- var fs8 = __toESM(require("node:fs"), 1);
42693
+ var fs9 = __toESM(require("node:fs"), 1);
41792
42694
  var os2 = __toESM(require("node:os"), 1);
41793
42695
  var path6 = __toESM(require("node:path"), 1);
41794
42696
  var readline = __toESM(require("node:readline"), 1);
@@ -41909,12 +42811,17 @@ var ClaudeCodeAdapter = class {
41909
42811
  async spawn(job) {
41910
42812
  const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
41911
42813
  const id = `claude-${slug4}-${(0, import_node_crypto14.randomUUID)().slice(0, 8)}`;
41912
- const dir = job.runtimeSessionId ? path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-chat-sessions", job.runtimeSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_")) : fs8.mkdtempSync(path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-session-"));
41913
- fs8.mkdirSync(dir, { recursive: true });
42814
+ const dir = job.runtimeSessionId ? path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-chat-sessions", job.runtimeSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_")) : fs9.mkdtempSync(path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-session-"));
42815
+ fs9.mkdirSync(dir, { recursive: true });
41914
42816
  for (const [rel, content] of Object.entries(job.bundle.files)) {
41915
42817
  const file = path6.join(dir, rel);
41916
- fs8.mkdirSync(path6.dirname(file), { recursive: true });
41917
- fs8.writeFileSync(file, content);
42818
+ fs9.mkdirSync(path6.dirname(file), { recursive: true });
42819
+ fs9.writeFileSync(file, content);
42820
+ }
42821
+ for (const [rel, b64] of Object.entries(job.bundle.binaryFiles ?? {})) {
42822
+ const file = path6.join(dir, rel);
42823
+ fs9.mkdirSync(path6.dirname(file), { recursive: true });
42824
+ fs9.writeFileSync(file, Buffer.from(b64, "base64"));
41918
42825
  }
41919
42826
  const prompt = job.bundle.files["TASK.md"];
41920
42827
  if (!prompt) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
@@ -41953,7 +42860,7 @@ var ClaudeCodeAdapter = class {
41953
42860
  stdio: ["ignore", "pipe", "pipe"]
41954
42861
  });
41955
42862
  let transcriptRef = path6.join(dir, "transcript.txt");
41956
- const out = fs8.createWriteStream(transcriptRef);
42863
+ const out = fs9.createWriteStream(transcriptRef);
41957
42864
  const telemetryCbs = [];
41958
42865
  let eventSeq = 0;
41959
42866
  let lastResult;
@@ -42016,13 +42923,13 @@ var ClaudeCodeAdapter = class {
42016
42923
  if (this.opts.cleanupWorkdir && !job.runtimeSessionId) {
42017
42924
  try {
42018
42925
  const keepDir = path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-transcripts");
42019
- fs8.mkdirSync(keepDir, { recursive: true });
42926
+ fs9.mkdirSync(keepDir, { recursive: true });
42020
42927
  const kept = path6.join(keepDir, `${id}.txt`);
42021
42928
  const src = transcriptRef;
42022
42929
  out.end(() => {
42023
42930
  try {
42024
- fs8.renameSync(src, kept);
42025
- fs8.rmSync(dir, { recursive: true, force: true });
42931
+ fs9.renameSync(src, kept);
42932
+ fs9.rmSync(dir, { recursive: true, force: true });
42026
42933
  } catch {
42027
42934
  }
42028
42935
  });
@@ -42057,7 +42964,7 @@ var ClaudeCodeAdapter = class {
42057
42964
  // ../adapters/src/codex/index.ts
42058
42965
  var import_node_child_process6 = require("node:child_process");
42059
42966
  var import_node_crypto15 = require("node:crypto");
42060
- var fs9 = __toESM(require("node:fs"), 1);
42967
+ var fs10 = __toESM(require("node:fs"), 1);
42061
42968
  var os3 = __toESM(require("node:os"), 1);
42062
42969
  var path7 = __toESM(require("node:path"), 1);
42063
42970
  var readline2 = __toESM(require("node:readline"), 1);
@@ -42102,7 +43009,7 @@ function codexSessionsRoot() {
42102
43009
  if (home) candidates.push(path7.join(home, ".codex", "sessions"));
42103
43010
  for (const c of candidates) {
42104
43011
  try {
42105
- if (fs9.statSync(c).isDirectory()) return c;
43012
+ if (fs10.statSync(c).isDirectory()) return c;
42106
43013
  } catch {
42107
43014
  }
42108
43015
  }
@@ -42114,7 +43021,7 @@ function listRecentRollouts(root, sinceMs) {
42114
43021
  const walk = (d) => {
42115
43022
  let ents;
42116
43023
  try {
42117
- ents = fs9.readdirSync(d, { withFileTypes: true });
43024
+ ents = fs10.readdirSync(d, { withFileTypes: true });
42118
43025
  } catch {
42119
43026
  return;
42120
43027
  }
@@ -42124,7 +43031,7 @@ function listRecentRollouts(root, sinceMs) {
42124
43031
  else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(".jsonl")) {
42125
43032
  let m2 = 0;
42126
43033
  try {
42127
- m2 = fs9.statSync(p2).mtimeMs;
43034
+ m2 = fs10.statSync(p2).mtimeMs;
42128
43035
  } catch {
42129
43036
  }
42130
43037
  if (m2 >= margin) found.push({ f: p2, m: m2 });
@@ -42139,7 +43046,7 @@ function scanCodexUsage(workdir, sinceMs, sessionsRoot = codexSessionsRoot()) {
42139
43046
  for (const f2 of listRecentRollouts(sessionsRoot, sinceMs)) {
42140
43047
  let lines;
42141
43048
  try {
42142
- lines = fs9.readFileSync(f2, "utf8").split("\n");
43049
+ lines = fs10.readFileSync(f2, "utf8").split("\n");
42143
43050
  } catch {
42144
43051
  continue;
42145
43052
  }
@@ -42163,9 +43070,9 @@ function createCodexNormalizeState() {
42163
43070
  function record2(value) {
42164
43071
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
42165
43072
  }
42166
- function textOf2(value) {
43073
+ function textOf3(value) {
42167
43074
  if (typeof value === "string") return value;
42168
- if (Array.isArray(value)) return value.map(textOf2).filter(Boolean).join("\n");
43075
+ if (Array.isArray(value)) return value.map(textOf3).filter(Boolean).join("\n");
42169
43076
  const obj = record2(value);
42170
43077
  if (!obj) return void 0;
42171
43078
  if (typeof obj.text === "string") return obj.text;
@@ -42174,7 +43081,7 @@ function textOf2(value) {
42174
43081
  return void 0;
42175
43082
  }
42176
43083
  function stringifyContent(value) {
42177
- const text = textOf2(value);
43084
+ const text = textOf3(value);
42178
43085
  if (text !== void 0) return text;
42179
43086
  if (value === void 0 || value === null) return "";
42180
43087
  try {
@@ -42205,7 +43112,7 @@ ${text}` : text;
42205
43112
  return [{ kind: "message", payload: { text: delta }, outputText: delta }];
42206
43113
  }
42207
43114
  if (type === "reasoning") {
42208
- const text = textOf2(item.text ?? item.summary ?? item.content ?? item);
43115
+ const text = textOf3(item.text ?? item.summary ?? item.content ?? item);
42209
43116
  return text ? [{ kind: "thought", payload: { text } }] : [];
42210
43117
  }
42211
43118
  if (type === "command_execution" && typeof item.id === "string") {
@@ -42331,11 +43238,11 @@ var CodexAdapter = class {
42331
43238
  const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
42332
43239
  const id = `codex-${slug4}-${(0, import_node_crypto15.randomUUID)().slice(0, 8)}`;
42333
43240
  const startedAtMs = Date.now();
42334
- const dir = fs9.mkdtempSync(path7.join(this.opts.workRoot ?? os3.tmpdir(), "oasis-session-"));
43241
+ const dir = fs10.mkdtempSync(path7.join(this.opts.workRoot ?? os3.tmpdir(), "oasis-session-"));
42335
43242
  for (const [rel, content] of Object.entries(job.bundle.files)) {
42336
43243
  const file = path7.join(dir, rel);
42337
- fs9.mkdirSync(path7.dirname(file), { recursive: true });
42338
- fs9.writeFileSync(file, content);
43244
+ fs10.mkdirSync(path7.dirname(file), { recursive: true });
43245
+ fs10.writeFileSync(file, content);
42339
43246
  }
42340
43247
  const task = job.bundle.files["TASK.md"];
42341
43248
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
@@ -42367,7 +43274,7 @@ var CodexAdapter = class {
42367
43274
  stdio: ["ignore", "pipe", "pipe"]
42368
43275
  });
42369
43276
  let transcriptRef = path7.join(dir, "transcript.txt");
42370
- const out = fs9.createWriteStream(transcriptRef);
43277
+ const out = fs10.createWriteStream(transcriptRef);
42371
43278
  const outputCbs = [];
42372
43279
  const telemetryCbs = [];
42373
43280
  const normalizeState = createCodexNormalizeState();
@@ -42398,13 +43305,13 @@ var CodexAdapter = class {
42398
43305
  if (this.opts.cleanupWorkdir) {
42399
43306
  try {
42400
43307
  const keepDir = path7.join(this.opts.workRoot ?? os3.tmpdir(), "oasis-transcripts");
42401
- fs9.mkdirSync(keepDir, { recursive: true });
43308
+ fs10.mkdirSync(keepDir, { recursive: true });
42402
43309
  const kept = path7.join(keepDir, `${id}.txt`);
42403
43310
  const src = transcriptRef;
42404
43311
  out.end(() => {
42405
43312
  try {
42406
- fs9.renameSync(src, kept);
42407
- fs9.rmSync(dir, { recursive: true, force: true });
43313
+ fs10.renameSync(src, kept);
43314
+ fs10.rmSync(dir, { recursive: true, force: true });
42408
43315
  } catch {
42409
43316
  }
42410
43317
  });
@@ -42457,7 +43364,7 @@ var CodexAdapter = class {
42457
43364
  // ../adapters/src/_core/acp.ts
42458
43365
  var import_node_child_process7 = require("node:child_process");
42459
43366
  var import_node_crypto16 = require("node:crypto");
42460
- var fs10 = __toESM(require("node:fs"), 1);
43367
+ var fs11 = __toESM(require("node:fs"), 1);
42461
43368
  var os4 = __toESM(require("node:os"), 1);
42462
43369
  var path8 = __toESM(require("node:path"), 1);
42463
43370
  var readline3 = __toESM(require("node:readline"), 1);
@@ -42472,6 +43379,7 @@ var ACPClient = class {
42472
43379
  nextId = 0;
42473
43380
  pending = /* @__PURE__ */ new Map();
42474
43381
  pendingTools = /* @__PURE__ */ new Map();
43382
+ textBuffers = /* @__PURE__ */ new Map();
42475
43383
  seq = 0;
42476
43384
  request(method, params) {
42477
43385
  const id = this.nextId++;
@@ -42481,6 +43389,7 @@ var ACPClient = class {
42481
43389
  });
42482
43390
  }
42483
43391
  closeAllPending(err) {
43392
+ this.flushTextBuffers();
42484
43393
  for (const p2 of this.pending.values()) p2.reject(err);
42485
43394
  this.pending.clear();
42486
43395
  }
@@ -42539,17 +43448,22 @@ var ACPClient = class {
42539
43448
  this.handleAgentThought(updateData);
42540
43449
  break;
42541
43450
  case "tool_call":
43451
+ this.flushTextBuffers();
42542
43452
  this.handleToolCallStart(updateData);
43453
+ this.handleToolCallUpdate(updateData);
42543
43454
  break;
42544
43455
  case "tool_call_update":
43456
+ this.flushTextBuffers();
42545
43457
  this.handleToolCallUpdate(updateData);
42546
43458
  break;
42547
43459
  case "turn_end":
43460
+ this.flushTextBuffers();
42548
43461
  this.extractStopReason(updateData);
42549
43462
  break;
42550
43463
  }
42551
43464
  }
42552
43465
  extractStopReason(data) {
43466
+ this.flushTextBuffers();
42553
43467
  const r = data;
42554
43468
  if (r?.stopReason) this.onStop(r.stopReason);
42555
43469
  }
@@ -42557,45 +43471,235 @@ var ACPClient = class {
42557
43471
  if (outputText) this.onOutput(outputText);
42558
43472
  this.onTelemetry({ kind, payload, seq: ++this.seq, ts: (/* @__PURE__ */ new Date()).toISOString() });
42559
43473
  }
43474
+ appendText(kind, text) {
43475
+ if (kind === "message") this.onOutput(text);
43476
+ const other = kind === "message" ? "thought" : "message";
43477
+ this.flushTextBuffer(other);
43478
+ const existing = this.textBuffers.get(kind);
43479
+ if (existing) existing.text += text;
43480
+ else this.textBuffers.set(kind, { text, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
43481
+ }
43482
+ flushTextBuffer(kind) {
43483
+ const buffered = this.textBuffers.get(kind);
43484
+ if (!buffered) return;
43485
+ this.textBuffers.delete(kind);
43486
+ const text = buffered.text;
43487
+ if (!text) return;
43488
+ this.onTelemetry({ kind, payload: { text }, seq: ++this.seq, ts: buffered.startedAt });
43489
+ }
43490
+ flushTextBuffers() {
43491
+ this.flushTextBuffer("thought");
43492
+ this.flushTextBuffer("message");
43493
+ }
42560
43494
  handleAgentMessage(data) {
42561
- const text = data?.content?.text;
42562
- if (text) this.emit("message", { text }, text);
43495
+ const text = textFromUnknown(record3(data)?.["content"] ?? data);
43496
+ if (text) this.appendText("message", text);
42563
43497
  }
42564
43498
  handleAgentThought(data) {
42565
- const text = data?.content?.text;
42566
- if (text) this.emit("thought", { text });
43499
+ const text = textFromUnknown(record3(data)?.["content"] ?? data);
43500
+ if (text) this.appendText("thought", text);
42567
43501
  }
42568
43502
  handleToolCallStart(data) {
42569
- const d = data;
42570
- if (!d?.toolCallId) return;
42571
- const toolName = toolNameFromTitle(d.title ?? "", d.kind ?? "");
42572
- const rawInput = d.rawInput ?? d.input;
42573
- if (rawInput) {
42574
- this.pendingTools.set(d.toolCallId, { toolName, input: rawInput, argsText: "", emitted: true });
42575
- this.emit("tool_use", { id: d.toolCallId, name: toolName, input: rawInput });
43503
+ const d = toolPayload(data);
43504
+ const toolCallId = toolCallIdOf(d);
43505
+ if (!toolCallId) return;
43506
+ const toolName = toolNameOf(d);
43507
+ const input = toolInputOf(d);
43508
+ if (input !== void 0) {
43509
+ this.pendingTools.set(toolCallId, { toolName, input, argsText: "", outputParts: [], emitted: true });
43510
+ this.emit("tool_use", { id: toolCallId, name: toolName, input });
42576
43511
  } else {
42577
- this.pendingTools.set(d.toolCallId, { toolName, argsText: "", emitted: false });
43512
+ this.pendingTools.set(toolCallId, {
43513
+ toolName,
43514
+ argsText: argsTextOf(d) ?? "",
43515
+ outputParts: [],
43516
+ emitted: false
43517
+ });
42578
43518
  }
42579
43519
  }
42580
43520
  handleToolCallUpdate(data) {
42581
- const d = data;
42582
- if (!d?.toolCallId) return;
42583
- const status = d.status;
42584
- if (status !== "completed" && status !== "failed") return;
42585
- const pending = this.pendingTools.get(d.toolCallId);
42586
- this.pendingTools.delete(d.toolCallId);
43521
+ const d = toolPayload(data);
43522
+ const toolCallId = toolCallIdOf(d);
43523
+ if (!toolCallId) return;
43524
+ let pending = this.pendingTools.get(toolCallId);
43525
+ if (!pending) {
43526
+ pending = {
43527
+ toolName: toolNameOf(d),
43528
+ input: toolInputOf(d),
43529
+ argsText: argsTextOf(d) ?? "",
43530
+ outputParts: [],
43531
+ emitted: false
43532
+ };
43533
+ this.pendingTools.set(toolCallId, pending);
43534
+ }
43535
+ const input = toolInputOf(d);
43536
+ if (input !== void 0) pending.input = input;
43537
+ const argsText = argsTextOf(d);
43538
+ if (argsText) pending.argsText += argsText;
43539
+ const output = toolOutputOf(d);
43540
+ if (output !== void 0) pending.outputParts.push(output);
43541
+ if (!pending.emitted && pending.input !== void 0) {
43542
+ pending.emitted = true;
43543
+ this.emit("tool_use", { id: toolCallId, name: pending.toolName, input: pending.input });
43544
+ }
43545
+ const status = statusOf(d);
43546
+ if (!isFinalToolStatus(status, d)) return;
43547
+ this.pendingTools.delete(toolCallId);
42587
43548
  if (!pending?.emitted) {
42588
- const toolName = pending?.toolName ?? toolNameFromTitle(d.title ?? "", d.kind ?? "");
42589
- const input = pending?.input ?? parseArgsJSON(pending?.argsText ?? "");
42590
- this.emit("tool_use", { id: d.toolCallId, name: toolName, input });
43549
+ const input2 = pending.input ?? parseArgsJSON(pending.argsText);
43550
+ this.emit("tool_use", { id: toolCallId, name: pending.toolName, input: input2 ?? {} });
42591
43551
  }
42592
43552
  this.emit("tool_result", {
42593
- tool_use_id: d.toolCallId,
42594
- content: d.rawOutput ?? d.output ?? "",
42595
- is_error: status === "failed"
43553
+ tool_use_id: toolCallId,
43554
+ content: output ?? joinOutputParts(pending.outputParts),
43555
+ is_error: isErrorToolStatus(status, d)
42596
43556
  });
42597
43557
  }
42598
43558
  };
43559
+ function record3(value) {
43560
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
43561
+ }
43562
+ function firstString(...values) {
43563
+ for (const value of values) {
43564
+ if (typeof value === "string" && value.trim()) return value;
43565
+ }
43566
+ return void 0;
43567
+ }
43568
+ function textFromUnknown(value) {
43569
+ if (typeof value === "string") return value;
43570
+ if (Array.isArray(value)) {
43571
+ const parts = value.map(textFromUnknown).filter((x2) => Boolean(x2));
43572
+ return parts.length ? parts.join("") : void 0;
43573
+ }
43574
+ const obj = record3(value);
43575
+ if (!obj) return void 0;
43576
+ const direct = firstString(obj["text"], obj["delta"], obj["message"], record3(obj["error"])?.["message"]);
43577
+ if (direct) return direct;
43578
+ return textFromUnknown(obj["content"]) ?? textFromUnknown(record3(obj["result"])?.["content"]) ?? textFromUnknown(obj["result"]);
43579
+ }
43580
+ function jsonLike(value) {
43581
+ if (typeof value !== "string") return value;
43582
+ const t = value.trim();
43583
+ if (!t) return void 0;
43584
+ if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
43585
+ try {
43586
+ return JSON.parse(t);
43587
+ } catch {
43588
+ }
43589
+ }
43590
+ return { text: value };
43591
+ }
43592
+ function toolPayload(data) {
43593
+ const d = record3(data) ?? {};
43594
+ const nested = record3(d["toolCall"]) ?? record3(d["tool_call"]) ?? record3(d["call"]) ?? record3(d["tool"]) ?? record3(d["data"]) ?? {};
43595
+ return { ...nested, ...d };
43596
+ }
43597
+ function toolCallIdOf(d) {
43598
+ return firstString(d["toolCallId"], d["tool_call_id"], d["callId"], d["call_id"], d["id"]);
43599
+ }
43600
+ function toolNameOf(d) {
43601
+ return firstString(d["name"], d["toolName"], d["tool_name"], record3(d["tool"])?.["name"]) ?? toolNameFromTitle(firstString(d["title"]) ?? "", firstString(d["kind"]) ?? "");
43602
+ }
43603
+ function inputFromTitle(title, toolName) {
43604
+ const colon = title.indexOf(":");
43605
+ if (colon < 0) return void 0;
43606
+ const tail = title.slice(colon + 1).trim();
43607
+ if (!tail) return void 0;
43608
+ if (toolName === "terminal" || toolName === "execute_code" || toolName === "python") return { command: tail };
43609
+ if (toolName.includes("read") || toolName.includes("write")) return { path: tail };
43610
+ if (toolName.includes("search")) return { query: tail };
43611
+ return { text: tail };
43612
+ }
43613
+ function toolInputOf(d) {
43614
+ const direct = d["rawInput"] ?? d["raw_input"] ?? d["input"] ?? d["arguments"] ?? d["args"] ?? d["parameters"] ?? d["params"];
43615
+ const parsed = jsonLike(direct);
43616
+ if (parsed !== void 0) return parsed;
43617
+ const command = firstString(d["command"], d["cmd"]);
43618
+ if (command) return { command };
43619
+ const common = compactRecord({
43620
+ query: d["query"] ?? d["q"] ?? d["searchQuery"] ?? d["search_query"],
43621
+ queries: d["queries"],
43622
+ url: d["url"] ?? d["uri"],
43623
+ urls: d["urls"],
43624
+ path: d["path"] ?? d["filePath"] ?? d["file_path"],
43625
+ paths: d["paths"],
43626
+ pattern: d["pattern"] ?? d["regex"],
43627
+ prompt: d["prompt"],
43628
+ description: d["description"]
43629
+ });
43630
+ if (common) return common;
43631
+ const fromTitle = inputFromTitle(firstString(d["title"]) ?? "", toolNameOf(d));
43632
+ const locations = locationsOf(d);
43633
+ if (fromTitle && locations) return { ...fromTitle, locations };
43634
+ if (fromTitle) return fromTitle;
43635
+ if (locations) return { locations };
43636
+ return void 0;
43637
+ }
43638
+ function argsTextOf(d) {
43639
+ const value = d["argsText"] ?? d["args_text"] ?? d["argumentsText"] ?? d["arguments_text"];
43640
+ return typeof value === "string" ? value : void 0;
43641
+ }
43642
+ function toolOutputOf(d) {
43643
+ const error2 = record3(d["error"]);
43644
+ if (error2) return error2["message"] ?? error2;
43645
+ const raw = d["rawOutput"] ?? d["raw_output"] ?? d["output"] ?? d["result"] ?? d["results"] ?? d["response"] ?? d["data"] ?? d["content"];
43646
+ const stream = compactRecord({
43647
+ stdout: d["stdout"],
43648
+ stderr: d["stderr"],
43649
+ exit_code: d["exit_code"] ?? d["exitCode"]
43650
+ });
43651
+ if (raw === void 0) return stream;
43652
+ const text = textFromUnknown(raw);
43653
+ const primary = text ?? raw;
43654
+ if (stream && typeof primary === "string" && primary.trim()) return { output: primary, ...stream };
43655
+ return stream ?? primary;
43656
+ }
43657
+ function compactRecord(input) {
43658
+ const out = {};
43659
+ for (const [key, value] of Object.entries(input)) {
43660
+ if (value === void 0 || value === null) continue;
43661
+ if (typeof value === "string" && !value.trim()) continue;
43662
+ if (Array.isArray(value) && value.length === 0) continue;
43663
+ out[key] = parseJsonString(value);
43664
+ }
43665
+ return Object.keys(out).length ? out : void 0;
43666
+ }
43667
+ function parseJsonString(value) {
43668
+ if (typeof value !== "string") return value;
43669
+ const t = value.trim();
43670
+ if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
43671
+ try {
43672
+ return JSON.parse(t);
43673
+ } catch {
43674
+ }
43675
+ }
43676
+ return value;
43677
+ }
43678
+ function locationsOf(d) {
43679
+ const locations = d["locations"];
43680
+ if (!Array.isArray(locations)) return void 0;
43681
+ const out = locations.filter((item) => Boolean(record3(item)));
43682
+ return out.length ? out : void 0;
43683
+ }
43684
+ function statusOf(d) {
43685
+ const status = firstString(d["status"], d["state"]);
43686
+ if (status) return status.toLowerCase();
43687
+ if (d["success"] === true) return "succeeded";
43688
+ if (d["success"] === false || d["isError"] === true || d["is_error"] === true || d["error"]) return "failed";
43689
+ return void 0;
43690
+ }
43691
+ function isFinalToolStatus(status, d) {
43692
+ if (d["done"] === true || d["final"] === true) return true;
43693
+ return status === "completed" || status === "complete" || status === "succeeded" || status === "success" || status === "failed" || status === "error" || status === "cancelled" || status === "canceled" || status === "done" || status === "finished";
43694
+ }
43695
+ function isErrorToolStatus(status, d) {
43696
+ return status === "failed" || status === "error" || status === "cancelled" || status === "canceled" || d["success"] === false || d["isError"] === true || d["is_error"] === true || Boolean(d["error"]);
43697
+ }
43698
+ function joinOutputParts(parts) {
43699
+ if (parts.length === 0) return "";
43700
+ if (parts.every((p2) => typeof p2 === "string")) return parts.join("");
43701
+ return parts;
43702
+ }
42599
43703
  function normalizeACPUpdate(data) {
42600
43704
  const d = data;
42601
43705
  if (!d) return ["", data];
@@ -42613,7 +43717,17 @@ function normalizeUpdateType(t) {
42613
43717
  agentmessagechunk: "agent_message_chunk",
42614
43718
  agentthoughtchunk: "agent_thought_chunk",
42615
43719
  toolcall: "tool_call",
43720
+ toolcallstart: "tool_call",
43721
+ toolcallstarted: "tool_call",
43722
+ toolcallbegin: "tool_call",
43723
+ toolcallcreated: "tool_call",
42616
43724
  toolcallupdate: "tool_call_update",
43725
+ toolcallupdated: "tool_call_update",
43726
+ toolcallprogress: "tool_call_update",
43727
+ toolcallstatus: "tool_call_update",
43728
+ toolcalldelta: "tool_call_update",
43729
+ toolcallfinished: "tool_call_update",
43730
+ toolcallcompleted: "tool_call_update",
42617
43731
  usageupdate: "usage_update",
42618
43732
  turnend: "turn_end",
42619
43733
  endturn: "turn_end"
@@ -42663,11 +43777,11 @@ async function runACPSession(job, cfg) {
42663
43777
  const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
42664
43778
  const binTag = path8.basename(cfg.bin).replace(/[^a-zA-Z0-9]/g, "").slice(0, 12) || "acp";
42665
43779
  const id = `${binTag}-${slug4}-${(0, import_node_crypto16.randomUUID)().slice(0, 8)}`;
42666
- const dir = fs10.mkdtempSync(path8.join(cfg.workRoot ?? os4.tmpdir(), "oasis-session-"));
43780
+ const dir = fs11.mkdtempSync(path8.join(cfg.workRoot ?? os4.tmpdir(), "oasis-session-"));
42667
43781
  for (const [rel, content] of Object.entries(job.bundle.files)) {
42668
43782
  const file = path8.join(dir, rel);
42669
- fs10.mkdirSync(path8.dirname(file), { recursive: true });
42670
- fs10.writeFileSync(file, content);
43783
+ fs11.mkdirSync(path8.dirname(file), { recursive: true });
43784
+ fs11.writeFileSync(file, content);
42671
43785
  }
42672
43786
  const task = job.bundle.files["TASK.md"];
42673
43787
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
@@ -42685,7 +43799,7 @@ async function runACPSession(job, cfg) {
42685
43799
  stdio: ["pipe", "pipe", "pipe"]
42686
43800
  });
42687
43801
  let transcriptRef = path8.join(dir, "transcript.txt");
42688
- const transcriptOut = fs10.createWriteStream(transcriptRef);
43802
+ const transcriptOut = fs11.createWriteStream(transcriptRef);
42689
43803
  const outputCbs = [];
42690
43804
  const telemetryCbs = [];
42691
43805
  const exitCbs = [];
@@ -42696,13 +43810,13 @@ async function runACPSession(job, cfg) {
42696
43810
  if (cfg.cleanupWorkdir) {
42697
43811
  try {
42698
43812
  const keepDir = path8.join(cfg.workRoot ?? os4.tmpdir(), "oasis-transcripts");
42699
- fs10.mkdirSync(keepDir, { recursive: true });
43813
+ fs11.mkdirSync(keepDir, { recursive: true });
42700
43814
  const kept = path8.join(keepDir, `${id}.txt`);
42701
43815
  const src = transcriptRef;
42702
43816
  transcriptOut.end(() => {
42703
43817
  try {
42704
- fs10.renameSync(src, kept);
42705
- fs10.rmSync(dir, { recursive: true, force: true });
43818
+ fs11.renameSync(src, kept);
43819
+ fs11.rmSync(dir, { recursive: true, force: true });
42706
43820
  } catch {
42707
43821
  }
42708
43822
  });
@@ -42879,18 +43993,18 @@ var KiroAdapter = class {
42879
43993
  // ../adapters/src/_core/subprocess.ts
42880
43994
  var import_node_child_process8 = require("node:child_process");
42881
43995
  var import_node_crypto17 = require("node:crypto");
42882
- var fs11 = __toESM(require("node:fs"), 1);
43996
+ var fs12 = __toESM(require("node:fs"), 1);
42883
43997
  var os5 = __toESM(require("node:os"), 1);
42884
43998
  var path9 = __toESM(require("node:path"), 1);
42885
43999
  var readline4 = __toESM(require("node:readline"), 1);
42886
44000
  function materialize(job, workRoot) {
42887
44001
  const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
42888
44002
  const id = `${slug4}-${(0, import_node_crypto17.randomUUID)().slice(0, 8)}`;
42889
- const dir = fs11.mkdtempSync(path9.join(workRoot ?? os5.tmpdir(), "oasis-session-"));
44003
+ const dir = fs12.mkdtempSync(path9.join(workRoot ?? os5.tmpdir(), "oasis-session-"));
42890
44004
  for (const [rel, content] of Object.entries(job.bundle.files)) {
42891
44005
  const file = path9.join(dir, rel);
42892
- fs11.mkdirSync(path9.dirname(file), { recursive: true });
42893
- fs11.writeFileSync(file, content);
44006
+ fs12.mkdirSync(path9.dirname(file), { recursive: true });
44007
+ fs12.writeFileSync(file, content);
42894
44008
  }
42895
44009
  const task = job.bundle.files["TASK.md"];
42896
44010
  if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
@@ -42913,13 +44027,13 @@ function makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, exitCbs) {
42913
44027
  if (cfg.cleanupWorkdir) {
42914
44028
  try {
42915
44029
  const keepDir = path9.join(cfg.workRoot ?? os5.tmpdir(), "oasis-transcripts");
42916
- fs11.mkdirSync(keepDir, { recursive: true });
44030
+ fs12.mkdirSync(keepDir, { recursive: true });
42917
44031
  const kept = path9.join(keepDir, `${id}.txt`);
42918
44032
  const src = transcriptRefPtr.value;
42919
44033
  transcriptOut.end(() => {
42920
44034
  try {
42921
- fs11.renameSync(src, kept);
42922
- fs11.rmSync(dir, { recursive: true, force: true });
44035
+ fs12.renameSync(src, kept);
44036
+ fs12.rmSync(dir, { recursive: true, force: true });
42923
44037
  } catch {
42924
44038
  }
42925
44039
  });
@@ -42942,7 +44056,7 @@ function runStreamJson(job, cfg) {
42942
44056
  stdio: ["ignore", "pipe", "pipe"]
42943
44057
  });
42944
44058
  const transcriptRefPtr = { value: path9.join(dir, "transcript.txt") };
42945
- const transcriptOut = fs11.createWriteStream(transcriptRefPtr.value);
44059
+ const transcriptOut = fs12.createWriteStream(transcriptRefPtr.value);
42946
44060
  const outputCbs = [];
42947
44061
  const telemetryCbs = [];
42948
44062
  const exitCbs = [];
@@ -43000,7 +44114,7 @@ function runOneShotText(job, cfg) {
43000
44114
  stdio: ["ignore", "pipe", "pipe"]
43001
44115
  });
43002
44116
  const transcriptRefPtr = { value: path9.join(dir, "transcript.txt") };
43003
- const transcriptOut = fs11.createWriteStream(transcriptRefPtr.value);
44117
+ const transcriptOut = fs12.createWriteStream(transcriptRefPtr.value);
43004
44118
  const outputCbs = [];
43005
44119
  const exitCbs = [];
43006
44120
  const { finish, exited } = makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, exitCbs);
@@ -45743,9 +46857,18 @@ var PostgresChatSessionStore = class _PostgresChatSessionStore {
45743
46857
  role text NOT NULL,
45744
46858
  content text NOT NULL,
45745
46859
  seq integer NOT NULL,
45746
- created_at timestamptz NOT NULL
46860
+ created_at timestamptz NOT NULL,
46861
+ run_id text
45747
46862
  )`);
45748
46863
  await pool.query(`CREATE INDEX IF NOT EXISTS chat_messages_session_idx ON "${s2}".chat_messages (session_id, seq)`);
46864
+ await pool.query(`ALTER TABLE "${s2}".chat_messages ADD COLUMN IF NOT EXISTS run_id text`);
46865
+ await pool.query(`
46866
+ CREATE TABLE IF NOT EXISTS "${s2}".chat_session_work_orders (
46867
+ session_id text NOT NULL,
46868
+ work_order_id text NOT NULL,
46869
+ linked_at timestamptz NOT NULL DEFAULT now(),
46870
+ PRIMARY KEY (session_id, work_order_id)
46871
+ )`);
45749
46872
  return new _PostgresChatSessionStore(pool, schema);
45750
46873
  }
45751
46874
  async createSession(s2) {
@@ -45795,9 +46918,9 @@ var PostgresChatSessionStore = class _PostgresChatSessionStore {
45795
46918
  }
45796
46919
  async appendMessage(m2) {
45797
46920
  await this.pool.query(
45798
- `INSERT INTO ${this.s}.chat_messages (id, session_id, role, content, seq, created_at)
45799
- VALUES ($1,$2,$3,$4,$5,$6)`,
45800
- [m2.id, m2.sessionId, m2.role, m2.content, m2.seq, m2.createdAt]
46921
+ `INSERT INTO ${this.s}.chat_messages (id, session_id, role, content, seq, created_at, run_id)
46922
+ VALUES ($1,$2,$3,$4,$5,$6,$7)`,
46923
+ [m2.id, m2.sessionId, m2.role, m2.content, m2.seq, m2.createdAt, m2.runId ?? null]
45801
46924
  );
45802
46925
  }
45803
46926
  async listMessages(sessionId, limit) {
@@ -45809,6 +46932,19 @@ var PostgresChatSessionStore = class _PostgresChatSessionStore {
45809
46932
  const rows = r.rows.map(rowToMessage);
45810
46933
  return limit ? rows.reverse() : rows;
45811
46934
  }
46935
+ async linkWorkOrder(sessionId, workOrderId) {
46936
+ await this.pool.query(
46937
+ `INSERT INTO ${this.s}.chat_session_work_orders (session_id, work_order_id) VALUES ($1,$2) ON CONFLICT DO NOTHING`,
46938
+ [sessionId, workOrderId]
46939
+ );
46940
+ }
46941
+ async listWorkOrdersForSession(sessionId) {
46942
+ const r = await this.pool.query(
46943
+ `SELECT work_order_id FROM ${this.s}.chat_session_work_orders WHERE session_id = $1 ORDER BY linked_at`,
46944
+ [sessionId]
46945
+ );
46946
+ return r.rows.map((row) => row.work_order_id);
46947
+ }
45812
46948
  };
45813
46949
  var rowToSession = (row) => ({
45814
46950
  id: row.id,
@@ -45826,7 +46962,8 @@ var rowToMessage = (row) => ({
45826
46962
  role: row.role,
45827
46963
  content: row.content,
45828
46964
  seq: Number(row.seq),
45829
- createdAt: new Date(row.created_at).toISOString()
46965
+ createdAt: new Date(row.created_at).toISOString(),
46966
+ ...row.run_id != null ? { runId: row.run_id } : {}
45830
46967
  });
45831
46968
 
45832
46969
  // ../storage/src/postgres-nodes.ts
@@ -46003,18 +47140,42 @@ var rowToRuntime = (row) => ({
46003
47140
  init_src3();
46004
47141
  init_trajectory();
46005
47142
  var DEFAULT_CHAT_SKILLS = ["build-workorder"];
47143
+ var SESSION_TOKEN_GRACE_MS = 15 * 6e4;
47144
+ function normalizeHttpBaseUrl(raw) {
47145
+ const url = new URL(raw);
47146
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
47147
+ throw new Error(`public URL \u5FC5\u987B\u662F http(s): ${raw}`);
47148
+ }
47149
+ url.hash = "";
47150
+ url.search = "";
47151
+ const out = url.toString();
47152
+ return out.endsWith("/") ? out.slice(0, -1) : out;
47153
+ }
47154
+ function apiUrlFromGatewayUrl(raw) {
47155
+ if (!raw) return void 0;
47156
+ try {
47157
+ const url = new URL(raw);
47158
+ if (url.protocol === "wss:") url.protocol = "https:";
47159
+ else if (url.protocol === "ws:") url.protocol = "http:";
47160
+ else if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
47161
+ url.pathname = url.pathname.replace(/\/node-gateway\/?$/, "") || "/";
47162
+ return normalizeHttpBaseUrl(url.toString());
47163
+ } catch {
47164
+ return void 0;
47165
+ }
47166
+ }
46006
47167
  var oasisWrapperCache;
46007
47168
  function oasisWrapperScript() {
46008
- if (oasisWrapperCache && fs13.existsSync(oasisWrapperCache)) return oasisWrapperCache;
47169
+ if (oasisWrapperCache && fs14.existsSync(oasisWrapperCache)) return oasisWrapperCache;
46009
47170
  const repoRoot = (0, import_node_url3.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
46010
47171
  const tsx = path11.join(repoRoot, "node_modules/.bin/tsx");
46011
47172
  const main = path11.join(repoRoot, "packages/cli/src/main.ts");
46012
- const dir = fs13.mkdtempSync(path11.join(os6.tmpdir(), "oasis-cli-"));
47173
+ const dir = fs14.mkdtempSync(path11.join(os6.tmpdir(), "oasis-cli-"));
46013
47174
  const wrapper = path11.join(dir, "oasis");
46014
- fs13.writeFileSync(wrapper, `#!/usr/bin/env bash
47175
+ fs14.writeFileSync(wrapper, `#!/usr/bin/env bash
46015
47176
  exec ${JSON.stringify(tsx)} ${JSON.stringify(main)} "$@"
46016
47177
  `);
46017
- fs13.chmodSync(wrapper, 493);
47178
+ fs14.chmodSync(wrapper, 493);
46018
47179
  oasisWrapperCache = wrapper;
46019
47180
  return wrapper;
46020
47181
  }
@@ -46071,13 +47232,14 @@ async function buildActorContext(service, actorId) {
46071
47232
  function traceRuntimeKind(runtimeKind) {
46072
47233
  if (runtimeKind === "claude" || runtimeKind === "claude-code") return "claude-code";
46073
47234
  if (runtimeKind === "codex") return "codex";
47235
+ if (runtimeKind === "hermes") return "hermes";
46074
47236
  return "custom";
46075
47237
  }
46076
47238
  async function startServe(opts) {
46077
- fs13.mkdirSync(opts.dir, { recursive: true });
47239
+ fs14.mkdirSync(opts.dir, { recursive: true });
46078
47240
  const lock = path11.join(opts.dir, "serve.lock");
46079
- if (fs13.existsSync(lock)) {
46080
- const pid = Number(fs13.readFileSync(lock, "utf8"));
47241
+ if (fs14.existsSync(lock)) {
47242
+ const pid = Number(fs14.readFileSync(lock, "utf8"));
46081
47243
  let alive = false;
46082
47244
  try {
46083
47245
  process.kill(pid, 0);
@@ -46085,12 +47247,12 @@ async function startServe(opts) {
46085
47247
  } catch {
46086
47248
  }
46087
47249
  if (alive) throw new Error(`\u8BE5\u6570\u636E\u76EE\u5F55\u5DF2\u6709 serve \u5728\u8DD1\uFF08pid=${pid}\uFF09\u2014\u2014\u5355\u5199\u8005\u8FDB\u7A0B\uFF0C\u4E0D\u80FD\u53CC\u5F00`);
46088
- fs13.unlinkSync(lock);
47250
+ fs14.unlinkSync(lock);
46089
47251
  }
46090
- fs13.writeFileSync(lock, String(process.pid));
47252
+ fs14.writeFileSync(lock, String(process.pid));
46091
47253
  const loadJson = (name) => {
46092
47254
  const file = path11.join(opts.dir, name);
46093
- return fs13.existsSync(file) ? JSON.parse(fs13.readFileSync(file, "utf8")) : void 0;
47255
+ return fs14.existsSync(file) ? JSON.parse(fs14.readFileSync(file, "utf8")) : void 0;
46094
47256
  };
46095
47257
  const oplog = await NdjsonOplogStore.open(path11.join(opts.dir, "oplog.ndjson"));
46096
47258
  const blobs = new DirBlobStore(path11.join(opts.dir, "blobs"));
@@ -46135,16 +47297,16 @@ async function startServe(opts) {
46135
47297
  const issuer = createTokenIssuer();
46136
47298
  const operatorActor = `actor:human:${os6.userInfo().username}`;
46137
47299
  const operatorToken = issuer.issue(operatorActor);
46138
- fs13.writeFileSync(path11.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
47300
+ fs14.writeFileSync(path11.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
46139
47301
  let nodeTokens = createNodeTokenStore(path11.join(opts.dir, "node-tokens.json"));
46140
47302
  const enrollTokens = createEnrollTokenStore(30 * 60 * 1e3, path11.join(opts.dir, "enroll-tokens.json"));
46141
47303
  let nodeStore = await FileNodeStore.open(path11.join(opts.dir, "nodes.json"));
46142
47304
  const registryAuditFile = path11.join(opts.dir, "registry-audit.ndjson");
46143
47305
  const registryListeners = /* @__PURE__ */ new Set();
46144
- const registryAudit = (record3) => {
46145
- fs13.appendFile(registryAuditFile, JSON.stringify(record3) + "\n", () => {
47306
+ const registryAudit = (record4) => {
47307
+ fs14.appendFile(registryAuditFile, JSON.stringify(record4) + "\n", () => {
46146
47308
  });
46147
- for (const l of registryListeners) l({ kind: record3.kind, actor: record3.actor, target: record3.target, timestamp: record3.timestamp });
47309
+ for (const l of registryListeners) l({ kind: record4.kind, actor: record4.actor, target: record4.target, timestamp: record4.timestamp });
46148
47310
  };
46149
47311
  await syncRegistryRolesToKernel(registryStore, kernel);
46150
47312
  const actors = createActorsDomain({
@@ -46226,20 +47388,22 @@ async function startServe(opts) {
46226
47388
  console.log("[serve] \u8FD0\u884C\u8F68\u8FF9\u4F53\u7CFB\uFF1A\u672C\u5730 ndjson dev store");
46227
47389
  }
46228
47390
  const trace = createTraceDomain({ store: traceStore });
46229
- const traceStoreSink = new TraceStoreSink({ store: traceStore, runtimeKind: "claude-code" });
47391
+ const traceStoreSink = new TraceStoreSink({ store: traceStore, runtimeKind: "auto" });
46230
47392
  const automationStore = pgPool ? await PostgresAutomationStore.open(pgPool, pgSchema) : new MemoryAutomationStore();
46231
47393
  console.log(`[serve] \u81EA\u52A8\u5316\u4F53\u7CFB\uFF1A${pgPool ? `Postgres schema=${pgSchema}` : "\u5185\u5B58 dev store"}`);
46232
47394
  const automations = createAutomationsDomain({ store: automationStore, kernel });
46233
47395
  const controlPlaneStore = await FileControlPlaneStore.open(path11.join(opts.dir, "control-plane.json"));
46234
- const companies = createCompaniesDomain({ store: controlPlaneStore, blobs });
46235
- const jwtSecret = process.env["OASIS_JWT_SECRET"];
46236
- if (!jwtSecret) console.warn("[serve] OASIS_JWT_SECRET \u672A\u8BBE\u2014\u2014\u7528 dev \u9ED8\u8BA4\u5BC6\u94A5\uFF0C\u751F\u4EA7\u52A1\u5FC5\u914D\u7F6E\uFF08\u5426\u5219\u4F1A\u8BDD\u53EF\u4F2A\u9020\uFF09");
46237
47396
  const resendKey = process.env["RESEND_API_KEY"];
46238
47397
  const mailFrom = process.env["RESEND_FROM_EMAIL"] ?? process.env["OASIS_MAIL_FROM"] ?? "noreply@open-friday.com";
46239
- if (!resendKey) console.warn("[serve] RESEND_API_KEY \u672A\u8BBE\u2014\u2014\u9A8C\u8BC1\u7801\u6253\u5370\u5230\u65E5\u5FD7\uFF08dev\uFF09\uFF1B\u751F\u4EA7\u8BF7\u914D RESEND_API_KEY + RESEND_FROM_EMAIL\uFF08Resend \u5DF2\u9A8C\u8BC1\u57DF\u540D\uFF09");
47398
+ if (!resendKey) console.warn("[serve] RESEND_API_KEY \u672A\u8BBE\u2014\u2014\u9A8C\u8BC1\u7801/\u9080\u8BF7\u6253\u5370\u5230\u65E5\u5FD7\uFF08dev\uFF09\uFF1B\u751F\u4EA7\u8BF7\u914D RESEND_API_KEY + RESEND_FROM_EMAIL\uFF08Resend \u5DF2\u9A8C\u8BC1\u57DF\u540D\uFF09");
47399
+ const mailSender = resendKey ? resendSender({ apiKey: resendKey, from: mailFrom }) : consoleSender();
47400
+ const consoleUrl = process.env["OASIS_CONSOLE_URL"] ?? "https://design.open-friday.com/oasis-preview/";
47401
+ const companies = createCompaniesDomain({ store: controlPlaneStore, blobs, sender: mailSender, consoleUrl });
47402
+ const jwtSecret = process.env["OASIS_JWT_SECRET"];
47403
+ if (!jwtSecret) console.warn("[serve] OASIS_JWT_SECRET \u672A\u8BBE\u2014\u2014\u7528 dev \u9ED8\u8BA4\u5BC6\u94A5\uFF0C\u751F\u4EA7\u52A1\u5FC5\u914D\u7F6E\uFF08\u5426\u5219\u4F1A\u8BDD\u53EF\u4F2A\u9020\uFF09");
46240
47404
  const auth = createAuth({
46241
47405
  store: controlPlaneStore,
46242
- sender: resendKey ? resendSender({ apiKey: resendKey, from: mailFrom }) : consoleSender(),
47406
+ sender: mailSender,
46243
47407
  jwtSecret: jwtSecret ?? "oasis-dev-secret-change-me",
46244
47408
  // dev 经 http/vite 反代,置 Secure 会丢 cookie;生产经 https 应由部署设 OASIS_SECURE_COOKIE=1
46245
47409
  secureCookie: process.env["OASIS_SECURE_COOKIE"] === "1",
@@ -46247,11 +47411,7 @@ async function startServe(opts) {
46247
47411
  provisionAccount: (email2) => companies.service.ensureAccount(email2)
46248
47412
  });
46249
47413
  await migrateDefaultCompany(controlPlaneStore, await actors.service.listActors());
46250
- for (const devActor of ["actor:human:yx"]) {
46251
- if (!await controlPlaneStore.getMember(DEFAULT_COMPANY_ID, devActor)) {
46252
- await controlPlaneStore.upsertMember({ companyId: DEFAULT_COMPANY_ID, accountId: devActor, actorId: devActor, role: "member" });
46253
- }
46254
- }
47414
+ await bootstrapBuildWorkorderSkill(actors.service).catch((err) => console.warn(`[serve] build-workorder skill bootstrap failed: ${String(err)}`));
46255
47415
  if (!await controlPlaneStore.getMember(DEFAULT_COMPANY_ID, operatorActor)) {
46256
47416
  await controlPlaneStore.upsertMember({ companyId: DEFAULT_COMPANY_ID, accountId: operatorActor, actorId: operatorActor, role: "owner" });
46257
47417
  }
@@ -46283,12 +47443,12 @@ async function startServe(opts) {
46283
47443
  return auth.sessionAccountId(headers);
46284
47444
  };
46285
47445
  const interventionDrafts = new InterventionDraftStore();
46286
- const workorderDrafts = new WorkorderDraftStore();
47446
+ const workorderDrafts = new WorkorderDraftStore(path11.join(opts.dir, "workorder-drafts.json"));
46287
47447
  const journalFile = path11.join(opts.dir, "dispatch-journal.ndjson");
46288
47448
  const journalRing = [];
46289
- if (fs13.existsSync(journalFile)) {
47449
+ if (fs14.existsSync(journalFile)) {
46290
47450
  try {
46291
- const lines = fs13.readFileSync(journalFile, "utf8").split(/\r?\n/).filter(Boolean);
47451
+ const lines = fs14.readFileSync(journalFile, "utf8").split(/\r?\n/).filter(Boolean);
46292
47452
  for (const line of lines.slice(-500)) {
46293
47453
  try {
46294
47454
  journalRing.push(JSON.parse(line));
@@ -46301,9 +47461,43 @@ async function startServe(opts) {
46301
47461
  const journal = (entry) => {
46302
47462
  journalRing.push(entry);
46303
47463
  if (journalRing.length > 500) journalRing.shift();
46304
- fs13.appendFile(journalFile, JSON.stringify(entry) + "\n", () => {
47464
+ fs14.appendFile(journalFile, JSON.stringify(entry) + "\n", () => {
46305
47465
  });
46306
47466
  };
47467
+ const closeOrphanedDispatches = () => {
47468
+ const open = /* @__PURE__ */ new Map();
47469
+ for (const entry of journalRing) {
47470
+ if (entry.kind === "dispatched") {
47471
+ open.set(entry.jobKey, entry);
47472
+ } else if (entry.kind === "exited" || entry.kind === "fused") {
47473
+ open.delete(entry.jobKey);
47474
+ }
47475
+ }
47476
+ if (open.size === 0) return;
47477
+ const now = Date.now();
47478
+ const at = new Date(now).toISOString();
47479
+ for (const entry of open.values()) {
47480
+ const startedAt = Date.parse(entry.at);
47481
+ const lastOpAt = kernel.model.lastOpAt.get(entry.artifactId);
47482
+ const lastOpTime = lastOpAt ? Date.parse(lastOpAt) : NaN;
47483
+ const progressed = Number.isFinite(startedAt) && Number.isFinite(lastOpTime) && lastOpTime > startedAt;
47484
+ journal({
47485
+ kind: "exited",
47486
+ at,
47487
+ jobKey: entry.jobKey,
47488
+ artifactId: entry.artifactId,
47489
+ actor: entry.actor,
47490
+ action: entry.action,
47491
+ code: null,
47492
+ reason: "timeout",
47493
+ durationMs: Number.isFinite(startedAt) ? Math.max(0, now - startedAt) : 0,
47494
+ progressed,
47495
+ consecutiveFailures: progressed ? 0 : 1
47496
+ });
47497
+ }
47498
+ console.warn(`[dispatch] closed ${open.size} orphaned dispatch journal entr${open.size === 1 ? "y" : "ies"} from a previous server process`);
47499
+ };
47500
+ if (opts.dispatch) closeOrphanedDispatches();
46307
47501
  let dispatcher;
46308
47502
  const trajListeners = /* @__PURE__ */ new Set();
46309
47503
  const trajectorySink = new FsTrajectorySink(opts.dir, (msg) => {
@@ -46326,6 +47520,27 @@ async function startServe(opts) {
46326
47520
  let hub = null;
46327
47521
  let chatRemoteAdapter = null;
46328
47522
  let localUrl = "";
47523
+ const remoteApiUrl = opts.publicUrl ? normalizeHttpBaseUrl(opts.publicUrl) : apiUrlFromGatewayUrl(opts.gatewayUrl);
47524
+ const serverUrlForBinding = (binding) => {
47525
+ if (!binding?.nodeId) return localUrl;
47526
+ if (remoteApiUrl) return remoteApiUrl;
47527
+ throw new Error(`\u8FDC\u7A0B\u8282\u70B9 ${binding.nodeId} \u9700\u8981\u53EF\u8FBE\u7684 Oasis API \u5730\u5740\uFF1A\u914D\u7F6E --public-url / $OASIS_PUBLIC_URL\uFF0C\u6216\u4F7F\u7528\u53EF\u63A8\u5BFC\u7684 --gateway-url`);
47528
+ };
47529
+ const issueSessionToken = (actorId, ctx) => {
47530
+ const token = issuer.issue(actorId, {
47531
+ ttlMs: ctx.limits.wallClockMs + SESSION_TOKEN_GRACE_MS,
47532
+ artifactId: ctx.artifactId,
47533
+ action: ctx.action,
47534
+ ...ctx.sessionId !== void 0 ? { sessionId: ctx.sessionId } : {},
47535
+ ...ctx.binding?.nodeId !== void 0 ? { nodeId: ctx.binding.nodeId } : {},
47536
+ ...ctx.binding?.runtimeKind !== void 0 ? { runtimeKind: ctx.binding.runtimeKind } : {},
47537
+ scopes: ctx.scopes ?? ["oasis:api"]
47538
+ });
47539
+ if (issuer.resolve(token) !== actorId) {
47540
+ throw new Error(`\u521A\u7B7E\u53D1\u7684 OASIS_TOKEN \u65E0\u6CD5\u89E3\u6790\u4E3A ${actorId}`);
47541
+ }
47542
+ return token;
47543
+ };
46329
47544
  let resolveCodeRepoRef;
46330
47545
  const server = await startOasisServer({
46331
47546
  kernel: engine.kernel,
@@ -46373,15 +47588,17 @@ async function startServe(opts) {
46373
47588
  console.warn(`[planner] skill materialize failed for ${planner.id}: ${String(err)}`);
46374
47589
  return {};
46375
47590
  }));
47591
+ const limits = { wallClockMs: 5 * 6e4 };
47592
+ const artifactId = `artifact:planner:${(0, import_node_crypto20.randomUUID)()}`;
46376
47593
  const handle = await chatRemoteAdapter.spawn({
46377
47594
  actor: planner.id,
46378
- actorToken: issuer.issue(planner.id),
46379
- artifactId: `artifact:planner:${(0, import_node_crypto20.randomUUID)()}`,
47595
+ actorToken: issueSessionToken(planner.id, { artifactId, action: "plan-workorder", limits, binding }),
47596
+ artifactId,
46380
47597
  bundle: { files },
46381
47598
  ...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
46382
- server: { url: localUrl },
47599
+ server: { url: serverUrlForBinding(binding) },
46383
47600
  binding,
46384
- limits: { wallClockMs: 5 * 6e4 },
47601
+ limits,
46385
47602
  env: provision.env,
46386
47603
  wrapperPaths: provision.wrapperPaths
46387
47604
  });
@@ -46415,15 +47632,18 @@ async function startServe(opts) {
46415
47632
  registry: registryStore,
46416
47633
  artifacts: artifactStateStore,
46417
47634
  dispatchJournal: () => journalRing.slice(-200),
47635
+ workorderDrafts,
47636
+ // 活工单查不到时回退查建单草案(status="draft")
46418
47637
  ...opts.sla ? { sla: opts.sla } : {},
46419
47638
  resolveEngine: (cid) => engineRouter.getEngine(cid ?? DEFAULT_COMPANY_ID)
46420
47639
  }),
46421
47640
  trace.register,
46422
47641
  automations.register,
46423
- ...chatSessionStore ? [createChatSessionsDomain({ store: chatSessionStore, registry: registryStore })] : []
47642
+ ...chatSessionStore ? [createChatSessionsDomain({ store: chatSessionStore, registry: registryStore, trace: traceStore, kernel, artifactState: artifactStateStore })] : []
46424
47643
  ],
46425
47644
  drafts: interventionDrafts,
46426
47645
  workorderDrafts,
47646
+ ...chatSessionStore ? { chatSession: chatSessionStore } : {},
46427
47647
  // 暴露注册的 artifact 类型,给前端「发起工单」动态拉取根产物可选类型——schema.json 增改即自动扩展,
46428
47648
  // 前端不再写死枚举。走通用 /api/view(generic data),非域路由契约,不进 gen:api。
46429
47649
  views: {
@@ -46443,6 +47663,7 @@ async function startServe(opts) {
46443
47663
  resolveActorEnv: async (actorId) => (await buildActorProvision(actors.service, actorId)).env,
46444
47664
  resolveActorContext: (actorId) => buildActorContext(actors.service, actorId),
46445
47665
  materializeSkills: (actorId, runtimeKind) => materializeSkillFiles({ service: actors.service, blobs: assets, actorId, runtimeKind }),
47666
+ uploadChatAttachment: async ({ contentBase64 }) => ({ blobRef: await assets.put(new Uint8Array(Buffer.from(contentBase64, "base64"))) }),
46446
47667
  dispatchProduce: async ({ artifactId, actorId, part }) => {
46447
47668
  if (!dispatcher) {
46448
47669
  throw new ApiError(503, "DISPATCH_NOT_ENABLED", "\u670D\u52A1\u7AEF\u672A\u542F\u7528 --dispatch true\uFF0C\u4E0D\u80FD\u62C9\u8D77\u771F\u5B9E\u667A\u80FD\u4F53\u4FEE\u6539");
@@ -46453,7 +47674,7 @@ async function startServe(opts) {
46453
47674
  ...part !== void 0 ? { part } : {}
46454
47675
  });
46455
47676
  },
46456
- dispatchChat: async ({ actorId, message, sessionId }) => {
47677
+ dispatchChat: async ({ actorId, message, sessionId, chatSessionId, attachments }) => {
46457
47678
  const binding = await actors.service.resolveBinding()(actorId);
46458
47679
  if (!binding) {
46459
47680
  throw new ApiError(409, "ACTOR_BINDING_MISSING", `\u6570\u5B57\u5458\u5DE5 ${actorId} \u6CA1\u6709 active runtime \u7ED1\u5B9A`);
@@ -46526,6 +47747,29 @@ async function startServe(opts) {
46526
47747
  };
46527
47748
  const provision = await buildActorProvision(actors.service, actorId);
46528
47749
  const actorCtx = await buildActorContext(actors.service, actorId).catch(() => null);
47750
+ const attachmentFiles = {};
47751
+ const attachmentBinary = {};
47752
+ const attachmentPaths = [];
47753
+ const usedRel = /* @__PURE__ */ new Set();
47754
+ for (const att of attachments ?? []) {
47755
+ const safe = (att.name || "attachment").replace(/[^\w.\-]+/g, "_").replace(/^\.+/, "").slice(0, 80) || "attachment";
47756
+ let rel = `inputs/attachments/${safe}`;
47757
+ let n = 1;
47758
+ while (usedRel.has(rel)) rel = `inputs/attachments/${n++}-${safe}`;
47759
+ usedRel.add(rel);
47760
+ if (typeof att.text === "string") {
47761
+ attachmentFiles[rel] = att.text;
47762
+ attachmentPaths.push(rel);
47763
+ } else if (att.blobRef) {
47764
+ try {
47765
+ const bytes = await assets.get(att.blobRef);
47766
+ attachmentBinary[rel] = Buffer.from(bytes).toString("base64");
47767
+ attachmentPaths.push(rel);
47768
+ } catch (err) {
47769
+ console.warn(`[chat] \u9644\u4EF6 blob ${att.blobRef} \u8BFB\u53D6\u5931\u8D25: ${String(err)}`);
47770
+ }
47771
+ }
47772
+ }
46529
47773
  const files = {
46530
47774
  "TASK.md": [
46531
47775
  "# \u5373\u65F6\u8111\u66B4\u5BF9\u8BDD",
@@ -46534,12 +47778,14 @@ async function startServe(opts) {
46534
47778
  `actor_id: ${actorId}`,
46535
47779
  "",
46536
47780
  "\u8BF7\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u5199\u5165\u7CFB\u7EDF\uFF0C\u5426\u5219\u4E0D\u8981\u521B\u5EFA\u6216\u4FEE\u6539 artifact\u3002",
47781
+ ...attachmentPaths.length ? ["", "## \u7528\u6237\u9644\u4EF6", "", "\u7528\u6237\u968F\u6D88\u606F\u9644\u5E26\u4E86\u4EE5\u4E0B\u6587\u4EF6\uFF08\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...attachmentPaths.map((p2) => `- ${p2}`)] : [],
46537
47782
  "",
46538
47783
  "## \u7528\u6237\u6D88\u606F",
46539
47784
  "",
46540
47785
  message
46541
47786
  ].join("\n")
46542
47787
  };
47788
+ Object.assign(files, attachmentFiles);
46543
47789
  if (actorCtx?.config?.prompt) {
46544
47790
  Object.assign(files, splitIdentityFiles3(actorCtx.config.prompt));
46545
47791
  }
@@ -46553,18 +47799,23 @@ async function startServe(opts) {
46553
47799
  console.warn(`[chat] skill materialize failed for ${actorId}: ${String(err)}`);
46554
47800
  return {};
46555
47801
  }));
47802
+ const limits = { wallClockMs: 10 * 6e4 };
47803
+ const artifactId = `artifact:chat:${(0, import_node_crypto20.randomUUID)()}`;
46556
47804
  const handle = await chatRemoteAdapter.spawn({
46557
47805
  actor: actorId,
46558
- actorToken: issuer.issue(actorId),
46559
- artifactId: `artifact:chat:${(0, import_node_crypto20.randomUUID)()}`,
46560
- bundle: { files },
47806
+ actorToken: issueSessionToken(actorId, { artifactId, action: "chat", limits, binding, sessionId: runtimeSessionId }),
47807
+ artifactId,
47808
+ bundle: { files, ...Object.keys(attachmentBinary).length ? { binaryFiles: attachmentBinary } : {} },
46561
47809
  ...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
46562
47810
  runtimeSessionId,
46563
47811
  resumeRuntimeSession,
46564
- server: { url: localUrl },
47812
+ server: { url: serverUrlForBinding(binding) },
46565
47813
  binding,
46566
- limits: { wallClockMs: 10 * 6e4 },
46567
- env: provision.env,
47814
+ limits,
47815
+ // OASIS_STAGE=1:chat agent 的改图命令(edit/link/spawn/cancel)经 /api/stage → 草案路由
47816
+ // (建单/改图暂存统一,见 docs/proposals/draft-edit-unification.md)。不设 OASIS_WORKSPACE——
47817
+ // chat agent 不绑单一工单,按命令 --workspace / id 定位是哪份草案。
47818
+ env: { ...provision.env, OASIS_STAGE: "1", ...chatSessionId ? { OASIS_CHAT_SESSION_ID: chatSessionId } : {} },
46568
47819
  wrapperPaths: provision.wrapperPaths
46569
47820
  });
46570
47821
  const buffered = [];
@@ -46607,6 +47858,7 @@ async function startServe(opts) {
46607
47858
  });
46608
47859
  return {
46609
47860
  id: runtimeSessionId,
47861
+ runId: traceRunId,
46610
47862
  onOutput(cb) {
46611
47863
  for (const chunk of buffered.splice(0)) cb(chunk);
46612
47864
  outputCbs.push(cb);
@@ -46637,6 +47889,7 @@ async function startServe(opts) {
46637
47889
  chatRemoteAdapter = new DaemonHubAdapter(hub);
46638
47890
  const port = new URL(server.baseUrl).port;
46639
47891
  localUrl = `http://127.0.0.1:${port}`;
47892
+ console.log(`[serve] \u8FDC\u7A0B agent API URL\uFF1A${remoteApiUrl ?? "\u672A\u914D\u7F6E\uFF08\u4EC5\u652F\u6301\u672C\u673A runtime\uFF09"}`);
46640
47893
  const projectsCfg = loadJson("projects.json");
46641
47894
  const seedProjects = (projectsCfg?.projects ?? []).map(
46642
47895
  (p2) => p2.repos?.length ? { ...p2, repos: p2.repos.map((r) => r.repoDir ? { ...r, repoDir: path11.resolve(opts.dir, r.repoDir) } : r) } : p2
@@ -46684,6 +47937,7 @@ async function startServe(opts) {
46684
47937
  remote: new DaemonHubAdapter(hub)
46685
47938
  }),
46686
47939
  serverUrl: localUrl,
47940
+ serverUrlFor: (ctx) => serverUrlForBinding(ctx.binding),
46687
47941
  resolveBinding: actors.service.resolveBinding(),
46688
47942
  provision: (actorId) => buildActorProvision(actors.service, actorId),
46689
47943
  resolveActorContext: (actorId) => buildActorContext(actors.service, actorId),
@@ -46693,7 +47947,13 @@ async function startServe(opts) {
46693
47947
  // §4/C:异步从权威源(store)解析;dispatcher 装配前 await
46694
47948
  // #2 派单开关:工单"待派发"(控制面旁存标 held)时不派其活。held 集通常空、表极小,每次查可接受。
46695
47949
  resolveDispatchHold: async (ws) => (await artifactStateStore.listWorkspaceDispatchHeld()).includes(ws),
46696
- tokenFor: (actor) => issuer.issue(actor),
47950
+ tokenFor: (actor, ctx) => issueSessionToken(actor, {
47951
+ artifactId: ctx.artifactId,
47952
+ action: ctx.action,
47953
+ limits: ctx.limits,
47954
+ ...ctx.binding !== void 0 ? { binding: ctx.binding } : {},
47955
+ ...ctx.part !== void 0 ? { sessionId: `${ctx.action}:${ctx.artifactId}:${ctx.part}` } : {}
47956
+ }),
46697
47957
  journal,
46698
47958
  // TrajectoryEvent 是唯一事件模型;trace 旁账与 M7 蒸馏库并行消费同一流(§16.D 收口)。
46699
47959
  trajectory: combineTrajectorySinks([trajectorySink, traceStoreSink]),
@@ -46834,14 +48094,24 @@ async function startServe(opts) {
46834
48094
  const coordinator = new CoordinatorWorker({
46835
48095
  kernel,
46836
48096
  oplog,
46837
- adapter: new ConnectorAwareAdapter({
46838
- streamJson: true,
46839
- maxOutputTokens: opts.maxOutputTokens ?? 64e3,
46840
- cleanupWorkdir: true,
46841
- ...opts.model ? { model: opts.model } : {}
48097
+ adapter: new BindingRouterAdapter({
48098
+ local: new ConnectorAwareAdapter({
48099
+ streamJson: true,
48100
+ maxOutputTokens: opts.maxOutputTokens ?? 64e3,
48101
+ cleanupWorkdir: true,
48102
+ ...opts.model ? { model: opts.model } : {}
48103
+ }),
48104
+ remote: new DaemonHubAdapter(hub)
46842
48105
  }),
46843
48106
  serverUrl: localUrl,
46844
- tokenFor: (actor) => issuer.issue(actor),
48107
+ serverUrlFor: (ctx) => serverUrlForBinding(ctx.binding),
48108
+ tokenFor: (actor, ctx) => issueSessionToken(actor, {
48109
+ artifactId: ctx?.artifactId ?? "artifact:coordinator",
48110
+ action: ctx?.action ?? "coordinate",
48111
+ limits: ctx?.limits ?? { wallClockMs: 5 * 6e4 },
48112
+ ...ctx?.binding !== void 0 ? { binding: ctx.binding } : {}
48113
+ }),
48114
+ resolveCoordinator: () => resolveCoordinator(registryStore),
46845
48115
  schemaTypes: schema.map((d) => d.name),
46846
48116
  schemaTypeDescriptions: Object.fromEntries(schema.filter((d) => d.description).map((d) => [d.name, d.description])),
46847
48117
  provision: (actorId) => buildActorProvision(actors.service, actorId),
@@ -46897,7 +48167,7 @@ async function startServe(opts) {
46897
48167
  await server.close();
46898
48168
  if (pgPool) await pgPool.end().catch(() => {
46899
48169
  });
46900
- fs13.rmSync(lock, { force: true });
48170
+ fs14.rmSync(lock, { force: true });
46901
48171
  }
46902
48172
  };
46903
48173
  }
@@ -46945,8 +48215,22 @@ function log2(tag, msg, extra) {
46945
48215
  // ../cli/src/daemon/ws-client.ts
46946
48216
  var RECONNECT_DELAY_MS = 3e3;
46947
48217
  var PING_INTERVAL_MS = 2e4;
48218
+ async function preflightOasisAccess(job) {
48219
+ const base = job.server.url.replace(/\/+$/, "");
48220
+ try {
48221
+ const res = await fetch(`${base}/api/whoami`, {
48222
+ headers: { authorization: `Bearer ${job.actorToken}` },
48223
+ signal: AbortSignal.timeout(5e3)
48224
+ });
48225
+ if (res.status === 401 || res.status === 403) return { code: null, reason: "auth-error" };
48226
+ if (!res.ok) return { code: null, reason: "server-unreachable" };
48227
+ return null;
48228
+ } catch {
48229
+ return { code: null, reason: "server-unreachable" };
48230
+ }
48231
+ }
46948
48232
  var DaemonWsClient = class {
46949
- constructor(serverUrl, daemonId, sessions, adapters = ["claude-code"], token, runtimes = adapters.map((kind) => ({ kind })), nodeName, reportRuntimes = false) {
48233
+ constructor(serverUrl, daemonId, sessions, adapters = ["claude-code"], token, runtimes = adapters.map((kind) => ({ kind })), nodeName, reportRuntimes = true, preflight = true) {
46950
48234
  this.serverUrl = serverUrl;
46951
48235
  this.daemonId = daemonId;
46952
48236
  this.sessions = sessions;
@@ -46955,6 +48239,7 @@ var DaemonWsClient = class {
46955
48239
  this.runtimes = runtimes;
46956
48240
  this.nodeName = nodeName;
46957
48241
  this.reportRuntimes = reportRuntimes;
48242
+ this.preflight = preflight;
46958
48243
  }
46959
48244
  ws = null;
46960
48245
  pingTimer = null;
@@ -47011,6 +48296,14 @@ var DaemonWsClient = class {
47011
48296
  const t0 = Date.now();
47012
48297
  log2("[node-cli]", `\u2190 dispatch ${dispatchId} artifact=${job.artifactId}`);
47013
48298
  try {
48299
+ if (this.preflight) {
48300
+ const preflightFailure = await preflightOasisAccess(job);
48301
+ if (preflightFailure) {
48302
+ log2("[node-cli]", ` preflight failed: ${preflightFailure.reason}`);
48303
+ this.send({ type: "session_exited", dispatchId, sessionId: "preflight-failed", info: preflightFailure });
48304
+ return;
48305
+ }
48306
+ }
47014
48307
  const handle = await this.sessions.getAdapter().spawn(job);
47015
48308
  this.sessions.register(dispatchId, handle);
47016
48309
  log2("[node-cli]", ` \u2192 session_started id=${handle.id} setup=${Date.now() - t0}ms`);
@@ -47030,7 +48323,10 @@ var DaemonWsClient = class {
47030
48323
  }
47031
48324
  case "kill": {
47032
48325
  log2("[node-cli]", `\u2190 kill dispatch=${msg.dispatchId}`);
47033
- await this.sessions.kill(msg.dispatchId);
48326
+ const killed = await this.sessions.kill(msg.dispatchId);
48327
+ if (!killed) {
48328
+ this.send({ type: "session_exited", dispatchId: msg.dispatchId, sessionId: "missing-session", info: { code: null, reason: "timeout" } });
48329
+ }
47034
48330
  break;
47035
48331
  }
47036
48332
  case "ping":
@@ -47163,8 +48459,9 @@ async function startNode(opts) {
47163
48459
  init_src4();
47164
48460
  var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uFF08\u670D\u52A1\u7AEF\u7626\u5BA2\u6237\u7AEF\uFF09
47165
48461
 
47166
- oasis serve [--dir .oasis] [--port 7320] [--dispatch true [--model haiku] [--max-produce-per-agent 5] [--max-concurrent-produce N]] [--allow-simple-tokens true] [--gateway-url wss://host/oasis-api/node-gateway]
48462
+ oasis serve [--dir .oasis] [--port 7320] [--dispatch true [--model haiku] [--max-produce-per-agent 5] [--max-concurrent-produce N]] [--allow-simple-tokens true] [--public-url https://host/oasis-api] [--gateway-url wss://host/oasis-api/node-gateway]
47167
48463
  # --gateway-url\uFF08\u6216 $OASIS_GATEWAY_URL\uFF09\uFF1A\u8282\u70B9\u5165\u7F51\u811A\u672C\u91CC\u5B88\u62A4\u8FDB\u7A0B\u8981\u8FDE\u7684\u7F51\u5173 URL\uFF1B\u516C\u7F51\u7ECF nginx \u5FC5\u8BBE\uFF0C\u5426\u5219\u9ED8\u8BA4\u672C\u673A ws://127.0.0.1:<port>\u3002
48464
+ # --public-url\uFF08\u6216 $OASIS_PUBLIC_URL\uFF09\uFF1A\u8FDC\u7A0B node \u4E0A agent \u56DE\u8FDE\u7684 HTTP API \u5730\u5740\uFF1B\u7F3A\u7701\u4ECE gateway-url \u63A8\u5BFC\u3002
47168
48465
  oasis export-trajectories [--dir d] [--out samples.jsonl] \u5BFC\u51FA\u84B8\u998F\u6837\u672C\uFF08\u8F93\u5165\u5305+\u4E8B\u4EF6\u6D41+\u7ED3\u679C\u5F15\u7528+gate \u6807\u7B7E\uFF09
47169
48466
  oasis serve install [--dir d] [--port p] [--dispatch true --model m] [--bin ~/.local/bin/oasis] [--unit-dir ~/.config/systemd/user] \u751F\u6210 systemd \u7528\u6237\u5355\u5143\uFF08D2 \u5B88\u62A4\u5316\uFF09
47170
48467
  \u5E38\u9A7B\u670D\u52A1\uFF08\u552F\u4E00\u5199\u8005\u8FDB\u7A0B\uFF09\uFF1A/api \u7ED9 CLI\u3001\u524D\u7AEF\u4E0E agent\uFF08agent \u901A\u8FC7 oasis CLI \u8C03\u7528\uFF09\u3002
@@ -47280,7 +48577,10 @@ function projectSlug(value) {
47280
48577
  return value.trim().toLowerCase().replace(/[^\w\u4e00-\u9fa5\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 32);
47281
48578
  }
47282
48579
  function makeClient(base, token) {
47283
- const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
48580
+ const baseHeaders = { authorization: `Bearer ${token}`, "content-type": "application/json" };
48581
+ const chatSessionId = process.env["OASIS_CHAT_SESSION_ID"];
48582
+ if (chatSessionId) baseHeaders["x-chat-session-id"] = chatSessionId;
48583
+ const headers = baseHeaders;
47284
48584
  const fail = async (res) => {
47285
48585
  const body = await res.json().catch(() => ({}));
47286
48586
  const err = body.error;
@@ -47336,9 +48636,9 @@ async function runCli(argv, println = console.log) {
47336
48636
  const nodeTokenFile = path12.join(dir, "node-token");
47337
48637
  const enrollTokenFile = path12.join(dir, "enroll-token");
47338
48638
  const reportMarkerFile = path12.join(dir, "report-runtimes");
47339
- let nodeId = fs14.existsSync(nodeIdFile) ? fs14.readFileSync(nodeIdFile, "utf8").trim() : null;
47340
- let token2 = flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? (fs14.existsSync(nodeTokenFile) ? fs14.readFileSync(nodeTokenFile, "utf8").trim() : void 0);
47341
- const enrollToken = flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"] ?? (fs14.existsSync(enrollTokenFile) ? fs14.readFileSync(enrollTokenFile, "utf8").trim() : void 0);
48639
+ let nodeId = fs15.existsSync(nodeIdFile) ? fs15.readFileSync(nodeIdFile, "utf8").trim() : null;
48640
+ let token2 = flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? (fs15.existsSync(nodeTokenFile) ? fs15.readFileSync(nodeTokenFile, "utf8").trim() : void 0);
48641
+ const enrollToken = flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"] ?? (fs15.existsSync(enrollTokenFile) ? fs15.readFileSync(enrollTokenFile, "utf8").trim() : void 0);
47342
48642
  let freshEnrollment = false;
47343
48643
  if ((!nodeId || !token2) && enrollToken) {
47344
48644
  const httpBase2 = serverUrl.replace(/^ws(s?):\/\//, "http$1://").replace(/\/node-gateway.*$/, "");
@@ -47351,26 +48651,26 @@ async function runCli(argv, println = console.log) {
47351
48651
  const body = await res.json();
47352
48652
  nodeId = body.nodeId;
47353
48653
  token2 = body.token;
47354
- fs14.mkdirSync(dir, { recursive: true });
47355
- fs14.writeFileSync(nodeIdFile, nodeId);
47356
- fs14.writeFileSync(nodeTokenFile, token2, { mode: 384 });
48654
+ fs15.mkdirSync(dir, { recursive: true });
48655
+ fs15.writeFileSync(nodeIdFile, nodeId);
48656
+ fs15.writeFileSync(nodeTokenFile, token2, { mode: 384 });
47357
48657
  try {
47358
- fs14.unlinkSync(enrollTokenFile);
48658
+ fs15.unlinkSync(enrollTokenFile);
47359
48659
  } catch {
47360
48660
  }
47361
48661
  freshEnrollment = true;
47362
48662
  }
47363
48663
  if (!nodeId) throw new Error("no nodeId \u2014 provide --enroll-token <ent_...> on first run");
47364
- const scriptRun = freshEnrollment || fs14.existsSync(reportMarkerFile);
48664
+ const scriptRun = freshEnrollment || fs15.existsSync(reportMarkerFile);
47365
48665
  if (flags.has("service")) {
47366
48666
  const { spawnSync, execSync: execSync2 } = await import("node:child_process");
47367
48667
  if (token2) {
47368
- fs14.mkdirSync(dir, { recursive: true });
47369
- fs14.writeFileSync(nodeTokenFile, token2, { mode: 384 });
48668
+ fs15.mkdirSync(dir, { recursive: true });
48669
+ fs15.writeFileSync(nodeTokenFile, token2, { mode: 384 });
47370
48670
  }
47371
48671
  if (scriptRun) {
47372
- fs14.mkdirSync(dir, { recursive: true });
47373
- fs14.writeFileSync(reportMarkerFile, "");
48672
+ fs15.mkdirSync(dir, { recursive: true });
48673
+ fs15.writeFileSync(reportMarkerFile, "");
47374
48674
  }
47375
48675
  println("\u2192 Installing oasis_test globally...");
47376
48676
  spawnSync("npm", ["install", "-g", "oasis_test@latest"], { stdio: "inherit" });
@@ -47379,10 +48679,10 @@ async function runCli(argv, println = console.log) {
47379
48679
  const svcArgs = `node --server-ws ${serverUrl} --dir ${dir}${nodeName ? ` --name ${nodeName}` : ""}`;
47380
48680
  if (process.platform === "linux") {
47381
48681
  const svcFile = path12.join(os7.homedir(), ".config/systemd/user/oasis-node.service");
47382
- fs14.mkdirSync(path12.dirname(svcFile), { recursive: true });
48682
+ fs15.mkdirSync(path12.dirname(svcFile), { recursive: true });
47383
48683
  if (spawnSync("systemctl", ["--user", "is-active", "--quiet", "oasis-node"], {}).status === 0)
47384
48684
  spawnSync("systemctl", ["--user", "stop", "oasis-node"], { stdio: "inherit" });
47385
- fs14.writeFileSync(svcFile, [
48685
+ fs15.writeFileSync(svcFile, [
47386
48686
  "[Unit]",
47387
48687
  "Description=Oasis Node Daemon",
47388
48688
  "After=network.target",
@@ -47410,10 +48710,10 @@ async function runCli(argv, println = console.log) {
47410
48710
  println(" Stop : systemctl --user stop oasis-node");
47411
48711
  } else if (process.platform === "darwin") {
47412
48712
  const plist = path12.join(os7.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
47413
- fs14.mkdirSync(path12.dirname(plist), { recursive: true });
48713
+ fs15.mkdirSync(path12.dirname(plist), { recursive: true });
47414
48714
  spawnSync("launchctl", ["unload", plist], { stdio: "ignore" });
47415
48715
  const argXml = [binPath, "node", "--server-ws", serverUrl, "--dir", dir, ...nodeName ? ["--name", nodeName] : []].map((a) => `<string>${a}</string>`).join("");
47416
- fs14.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>${os7.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os7.homedir()}/.oasis-node.err</string></dict></plist>`);
48716
+ fs15.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>${os7.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os7.homedir()}/.oasis-node.err</string></dict></plist>`);
47417
48717
  spawnSync("launchctl", ["load", plist], { stdio: "inherit" });
47418
48718
  println("\u2713 Oasis node service started (macOS LaunchAgent com.oasis.node)");
47419
48719
  println(` Logs : tail -f ${os7.homedir()}/.oasis-node.log`);
@@ -47425,9 +48725,9 @@ async function runCli(argv, println = console.log) {
47425
48725
  }
47426
48726
  return;
47427
48727
  }
47428
- if (fs14.existsSync(reportMarkerFile)) {
48728
+ if (fs15.existsSync(reportMarkerFile)) {
47429
48729
  try {
47430
- fs14.unlinkSync(reportMarkerFile);
48730
+ fs15.unlinkSync(reportMarkerFile);
47431
48731
  } catch {
47432
48732
  }
47433
48733
  }
@@ -47479,7 +48779,7 @@ async function runCli(argv, println = console.log) {
47479
48779
  const outFile = flags.get("out");
47480
48780
  const lines = samples.map((sample) => JSON.stringify(sample)).join("\n");
47481
48781
  if (outFile !== void 0) {
47482
- fs14.writeFileSync(outFile, lines + (lines ? "\n" : ""));
48782
+ fs15.writeFileSync(outFile, lines + (lines ? "\n" : ""));
47483
48783
  println(`\u5DF2\u5BFC\u51FA ${samples.length} \u6761\u8F68\u8FF9\u6837\u672C \u2192 ${outFile}\uFF08approved/rejected \u6210\u5BF9\u5373\u504F\u597D\u5BF9\uFF09`);
47484
48784
  } else {
47485
48785
  println(lines);
@@ -47507,9 +48807,9 @@ async function runCli(argv, println = console.log) {
47507
48807
  `WantedBy=default.target`,
47508
48808
  ``
47509
48809
  ].join("\n");
47510
- fs14.mkdirSync(unitDir, { recursive: true });
48810
+ fs15.mkdirSync(unitDir, { recursive: true });
47511
48811
  const unitPath = path12.join(unitDir, `${name}.service`);
47512
- fs14.writeFileSync(unitPath, unit);
48812
+ fs15.writeFileSync(unitPath, unit);
47513
48813
  println(`\u5DF2\u5199\u5165 ${unitPath}`);
47514
48814
  println(`\u542F\u7528\uFF1Asystemctl --user daemon-reload && systemctl --user enable --now ${name}`);
47515
48815
  println(`\u5F00\u673A\u81EA\u542F\uFF08\u542B\u672A\u767B\u5F55\uFF09\uFF1Aloginctl enable-linger $USER`);
@@ -47526,6 +48826,7 @@ async function runCli(argv, println = console.log) {
47526
48826
  ...flags.has("max-produce-per-agent") ? { maxConcurrentProducePerActor: Number(flags.get("max-produce-per-agent")) } : {},
47527
48827
  ...flags.has("model") ? { model: flags.get("model") } : {},
47528
48828
  allowSimpleTokens: flags.get("allow-simple-tokens") === "true",
48829
+ ...flags.get("public-url") ?? process.env["OASIS_PUBLIC_URL"] ? { publicUrl: flags.get("public-url") ?? process.env["OASIS_PUBLIC_URL"] } : {},
47529
48830
  ...flags.get("gateway-url") ?? process.env["OASIS_GATEWAY_URL"] ? { gatewayUrl: flags.get("gateway-url") ?? process.env["OASIS_GATEWAY_URL"] } : {}
47530
48831
  });
47531
48832
  const port2 = new URL(handle.baseUrl).port;
@@ -47538,12 +48839,12 @@ async function runCli(argv, println = console.log) {
47538
48839
  }
47539
48840
  const base = flags.get("server") ?? process.env["OASIS_SERVER"] ?? "http://127.0.0.1:7320";
47540
48841
  const tokenFile = path12.join(dir, "operator-token");
47541
- const token = flags.get("token") ?? process.env["OASIS_TOKEN"] ?? (fs14.existsSync(tokenFile) ? fs14.readFileSync(tokenFile, "utf8").trim() : void 0);
48842
+ const token = flags.get("token") ?? process.env["OASIS_TOKEN"] ?? (fs15.existsSync(tokenFile) ? fs15.readFileSync(tokenFile, "utf8").trim() : void 0);
47542
48843
  if (!token) {
47543
48844
  throw new Error(`\u65E0 token\uFF1A\u5148 oasis serve\uFF08\u4F1A\u751F\u6210 ${tokenFile}\uFF09\uFF0C\u6216\u663E\u5F0F --token / $OASIS_TOKEN`);
47544
48845
  }
47545
48846
  const api = makeClient(base, token);
47546
- const readFileArg = (file) => fs14.readFileSync(file, "utf8");
48847
+ const readFileArg = (file) => fs15.readFileSync(file, "utf8");
47547
48848
  const parseJsonFlag = (value, name) => {
47548
48849
  const parsed = JSON.parse(value);
47549
48850
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -47561,11 +48862,11 @@ async function runCli(argv, println = console.log) {
47561
48862
  const readDirFiles = (root) => {
47562
48863
  const out = [];
47563
48864
  const walk = (abs, rel) => {
47564
- for (const name of fs14.readdirSync(abs)) {
48865
+ for (const name of fs15.readdirSync(abs)) {
47565
48866
  const childAbs = path12.join(abs, name);
47566
48867
  const childRel = rel ? `${rel}/${name}` : name;
47567
- if (fs14.statSync(childAbs).isDirectory()) walk(childAbs, childRel);
47568
- else out.push({ path: childRel, contentBase64: fs14.readFileSync(childAbs).toString("base64") });
48868
+ if (fs15.statSync(childAbs).isDirectory()) walk(childAbs, childRel);
48869
+ else out.push({ path: childRel, contentBase64: fs15.readFileSync(childAbs).toString("base64") });
47569
48870
  }
47570
48871
  };
47571
48872
  walk(root, "");
@@ -47608,9 +48909,10 @@ async function runCli(argv, println = console.log) {
47608
48909
  };
47609
48910
  case "spawn": {
47610
48911
  const fields = fieldsFromFlags(SPAWN_OWN_FLAGS);
48912
+ const spawnId = positional[0];
47611
48913
  return {
47612
48914
  action: "spawn",
47613
- id: needPos(positional, 0, "oasis spawn <id> --type <t>"),
48915
+ ...spawnId ? { id: spawnId } : {},
47614
48916
  type: need(flags, "type"),
47615
48917
  ...flags.get("title") !== void 0 ? { title: flags.get("title") } : {},
47616
48918
  ...(flags.get("brief") ?? flags.get("description")) !== void 0 ? { description: flags.get("brief") ?? flags.get("description") } : {},
@@ -47664,7 +48966,12 @@ async function runCli(argv, println = console.log) {
47664
48966
  };
47665
48967
  if (process.env["OASIS_STAGE"] === "1" && STAGE_COMMANDS.has(command)) {
47666
48968
  const op = buildStageOp(command);
47667
- const r = await api.post("/api/stage", { op });
48969
+ const editWs = flags.get("workspace") ?? process.env["OASIS_WORKSPACE"];
48970
+ const r = await api.post("/api/stage", { op, ...editWs ? { workspace: editWs } : {} });
48971
+ if (r.draft) {
48972
+ println(`\u5DF2\u6539\u8349\u6848\uFF1A${describeStageOp(op)}\uFF08\u5DE5\u5355 ${r.workspace}\uFF0C\u73B0 ${r.nodes} \u4E2A\u8282\u70B9\uFF1B\u4EBA\u5728\u7F16\u8F91\u5668\u786E\u8BA4\u5373\u521B\u5EFA\uFF09`);
48973
+ return;
48974
+ }
47668
48975
  let msg = `\u5DF2\u6682\u5B58\uFF1A${describeStageOp(op)}\uFF08\u7F13\u51B2 ${r.count} \u9879\uFF1B\u6539\u5B8C \`oasis apply --reason "\u2026"\` \u63D0\u4EA4\u3001\`oasis staged\` \u770B\u5168\u90E8\uFF09`;
47669
48976
  if (r.cascade && r.cascade.length > 0)
47670
48977
  msg += `
@@ -47674,29 +48981,71 @@ async function runCli(argv, println = console.log) {
47674
48981
  }
47675
48982
  switch (command) {
47676
48983
  case "create-workorder": {
47677
- const planRaw = flags.get("plan-file") !== void 0 ? fs14.readFileSync(flags.get("plan-file"), "utf8") : flags.get("plan");
48984
+ const planRaw = flags.get("plan-file") !== void 0 ? fs15.readFileSync(flags.get("plan-file"), "utf8") : flags.get("plan");
47678
48985
  const acceptanceFlag = flags.get("acceptance");
47679
48986
  const args = {
47680
48987
  createdVia: "seed",
47681
48988
  stage: true,
47682
48989
  type: need(flags, "type"),
47683
- workspace: need(flags, "workspace"),
48990
+ // workspace 可选:不给则服务端自动生成唯一工单 id(见 build-workorder skill:建单不必自带 ws)。
48991
+ ...flags.get("workspace") !== void 0 ? { workspace: flags.get("workspace") } : {},
47684
48992
  title: need(flags, "title"),
47685
48993
  ...flags.get("brief") !== void 0 ? { brief: flags.get("brief") } : {},
47686
48994
  ...flags.get("goal") !== void 0 ? { goal: flags.get("goal") } : {},
47687
48995
  ...acceptanceFlag !== void 0 ? { acceptance_criteria: parseStringArrayFlag(acceptanceFlag, "acceptance") } : {},
47688
48996
  ...planRaw !== void 0 ? { plan: parseJsonFlag(planRaw, "plan") } : {},
47689
48997
  ...flags.get("project") !== void 0 ? { project_id: flags.get("project") } : {},
47690
- ...flags.get("dispatch") === "false" ? { dispatch: false } : {}
48998
+ ...flags.get("dispatch") === "false" ? { dispatch: false } : {},
48999
+ ...flags.get("allow-missing-coordinator") === "true" ? { allowMissingCoordinator: true } : {}
47691
49000
  };
47692
49001
  const res = await api.cmd("spawn", args);
47693
49002
  println(res.message);
47694
49003
  const data = res.data;
47695
49004
  if (data?.draftId) {
47696
- println(`draftId=${data.draftId} workspace=${data.workspace} \u8282\u70B9=${data.spawned?.length ?? 0} \u89C4\u5212=${data.planning?.status ?? "?"}`);
49005
+ println(`draftId=${data.draftId} workspace=${data.workspace} \u72B6\u6001=draft\uFF08\u8349\u6848\uFF0C\u5F85\u4EBA\u786E\u8BA4\uFF09 \u8282\u70B9=${data.spawned?.length ?? 0} \u89C4\u5212=${data.planning?.status ?? "?"}`);
49006
+ for (const n of data.spawned ?? []) println(` ${n.id} (${n.type})`);
47697
49007
  }
47698
49008
  break;
47699
49009
  }
49010
+ case "workorders": {
49011
+ const sessionId = flags.get("session");
49012
+ if (sessionId) {
49013
+ const res = await api.request(
49014
+ "GET",
49015
+ `/api/chat-sessions/${encodeURIComponent(sessionId)}/work-orders`
49016
+ );
49017
+ println(`\u4F1A\u8BDD ${sessionId} \u7684\u5DE5\u5355\uFF08${res.items.length}\uFF09\uFF1A`);
49018
+ for (const w2 of res.items) {
49019
+ const p2 = w2.progress ? ` [${w2.progress.concluded}/${w2.progress.total}]` : "";
49020
+ println(` ${w2.id} ${w2.title} [${w2.stage}]${p2}`);
49021
+ }
49022
+ } else {
49023
+ const res = await api.request("GET", "/api/workorders");
49024
+ const q = flags.get("search")?.trim().toLowerCase();
49025
+ const items = q ? res.items.filter((w2) => `${w2.id} ${w2.title} ${w2.goal ?? ""} ${w2.description ?? ""}`.toLowerCase().includes(q)) : res.items;
49026
+ println(`\u5DE5\u5355(${items.length}${q ? ` / \u641C\u300C${q}\u300D` : ""})\u2014\u2014\u8BE6\u60C5/\u6539\u56FE\u524D\u5148 \`oasis workorder <id>\`:`);
49027
+ for (const w2 of items) {
49028
+ println(`- ${w2.id} ${w2.title}${w2.dispatchPaused ? " [\u5F85\u6D3E\u53D1]" : ""}`);
49029
+ println(` \u9636\u6BB5=${w2.stage} \u8FDB\u5EA6=${w2.progress.concluded}/${w2.progress.total} \u8282\u70B9=${w2.artifactCount} owner=${w2.owner.name ?? w2.owner.id}${w2.goal ? ` \u76EE\u6807: ${w2.goal}` : ""}`);
49030
+ }
49031
+ if (items.length === 0) println("\uFF08\u65E0\u5339\u914D\u5DE5\u5355\uFF09");
49032
+ }
49033
+ break;
49034
+ }
49035
+ case "artifacts": {
49036
+ const workOrderId = positional[0];
49037
+ if (!workOrderId) {
49038
+ println("\u7528\u6CD5: oasis artifacts <workOrderId>");
49039
+ break;
49040
+ }
49041
+ const res = await api.request(
49042
+ "GET",
49043
+ `/api/workorders/${encodeURIComponent(workOrderId)}`
49044
+ );
49045
+ println(`\u5DE5\u5355 ${res.summary.title} \u7684\u4EA7\u7269\uFF08${res.graph.nodes.length}\uFF09\uFF1A`);
49046
+ for (const n of res.graph.nodes) println(` ${n.id} [${n.type}] ${n.label} stage=${n.stage}`);
49047
+ break;
49048
+ }
47700
49049
  case "projects": {
47701
49050
  const res = await api.request("GET", "/api/projects");
47702
49051
  const q = flags.get("search")?.trim().toLowerCase();
@@ -47707,6 +49056,29 @@ async function runCli(argv, println = console.log) {
47707
49056
  if (res.next_cursor) println("\uFF08\u8FD8\u6709\u66F4\u591A\u9879\u76EE\u672A\u5217\u5168\uFF0C\u7528 --search \u7F29\u5C0F\u8303\u56F4\uFF09");
47708
49057
  break;
47709
49058
  }
49059
+ case "workorder": {
49060
+ const woId = needPos(positional, 0, "oasis workorder <\u5DE5\u5355id\uFF0C\u5982 ws:wo-abc>");
49061
+ const d = await api.request("GET", `/api/workorders/${encodeURIComponent(woId)}`);
49062
+ const s2 = d.summary;
49063
+ const draftNote = s2.stage === "draft" ? "\uFF08\u8349\u6848\u2014\u2014\u5F85\u4EBA\u5728\u7F16\u8F91\u5668\u786E\u8BA4\u624D\u771F\u521B\u5EFA\uFF1B\u4E0B\u9762\u7684\u8282\u70B9 id \u5DF2\u53EF\u7528\u4E8E\u6539\u8349\u6848\uFF09" : "";
49064
+ println(`${s2.id} ${s2.title} \u72B6\u6001=${s2.stage}${draftNote}${s2.dispatchPaused ? " [\u672A\u6D3E\u53D1]" : ""}`);
49065
+ if (s2.goal) println(`\u76EE\u6807: ${s2.goal}`);
49066
+ println(`\u8FDB\u5EA6=${s2.progress.concluded}/${s2.progress.total} owner=${s2.owner.name ?? s2.owner.id}`);
49067
+ if (d.acceptanceCriteria && d.acceptanceCriteria.length > 0) {
49068
+ println("\u9A8C\u6536\u6807\u51C6:");
49069
+ for (const a of d.acceptanceCriteria) println(` - ${a}`);
49070
+ }
49071
+ println("\n\u8282\u70B9\uFF08\u6539\u56FE\u5F15\u7528\u7528\u8FD9\u4E9B id\uFF09:");
49072
+ for (const n of d.graph.nodes) {
49073
+ println(` ${n.id}`);
49074
+ println(` type=${n.type} \u9636\u6BB5=${n.stage}${n.blocked ? " [\u5361\u4F4F]" : ""} owner=${n.owner.name ?? n.owner.id}${n.label ? ` ${n.label}` : ""}`);
49075
+ }
49076
+ if (d.graph.edges.length > 0) {
49077
+ println("\n\u4F9D\u8D56\u8FB9\uFF08\u6D88\u8D39\u8005 \u2192 \u4E0A\u6E38\uFF09:");
49078
+ for (const e of d.graph.edges) println(` ${e.from} \u2192 ${e.to}${e.required ? "" : "\uFF08\u53EF\u9009\uFF09"}`);
49079
+ }
49080
+ break;
49081
+ }
47710
49082
  case "project": {
47711
49083
  const projSub = positional[0];
47712
49084
  if (projSub && !["create", "docs", "progress", "run-state"].includes(projSub)) {
@@ -48460,18 +49832,18 @@ var parseFlags = (argv) => {
48460
49832
  };
48461
49833
  var readConfig = () => {
48462
49834
  try {
48463
- return JSON.parse(fs15.readFileSync(CONFIG_FILE, "utf8"));
49835
+ return JSON.parse(fs16.readFileSync(CONFIG_FILE, "utf8"));
48464
49836
  } catch {
48465
49837
  return null;
48466
49838
  }
48467
49839
  };
48468
49840
  var saveConfig = (c) => {
48469
- fs15.mkdirSync(OASIS_DIR, { recursive: true });
48470
- fs15.writeFileSync(CONFIG_FILE, JSON.stringify(c), { mode: 384 });
49841
+ fs16.mkdirSync(OASIS_DIR, { recursive: true });
49842
+ fs16.writeFileSync(CONFIG_FILE, JSON.stringify(c), { mode: 384 });
48471
49843
  };
48472
49844
  var readPid = () => {
48473
49845
  try {
48474
- const p2 = parseInt(fs15.readFileSync(PID_FILE, "utf8").trim(), 10);
49846
+ const p2 = parseInt(fs16.readFileSync(PID_FILE, "utf8").trim(), 10);
48475
49847
  return isNaN(p2) ? null : p2;
48476
49848
  } catch {
48477
49849
  return null;
@@ -48487,10 +49859,10 @@ var isRunning = (pid) => {
48487
49859
  };
48488
49860
  var httpBase = (wsUrl) => wsUrl.replace(/^ws(s?):\/\//, "http$1://").replace(/\/node-gateway.*$/, "");
48489
49861
  function installBinary() {
48490
- fs15.mkdirSync(path13.dirname(BIN_FILE), { recursive: true });
48491
- fs15.copyFileSync(process.argv[1], BIN_FILE);
48492
- fs15.mkdirSync(path13.dirname(LOCAL_BIN), { recursive: true });
48493
- fs15.writeFileSync(LOCAL_BIN, `#!/bin/sh
49862
+ fs16.mkdirSync(path13.dirname(BIN_FILE), { recursive: true });
49863
+ fs16.copyFileSync(process.argv[1], BIN_FILE);
49864
+ fs16.mkdirSync(path13.dirname(LOCAL_BIN), { recursive: true });
49865
+ fs16.writeFileSync(LOCAL_BIN, `#!/bin/sh
48494
49866
  exec node "${BIN_FILE}" "$@"
48495
49867
  `, { mode: 493 });
48496
49868
  }
@@ -48505,8 +49877,8 @@ function setupAutostart() {
48505
49877
  const daemonPath = [process.env["PATH"] ?? "", ...extraBins].filter(Boolean).join(":");
48506
49878
  if (process.platform === "linux") {
48507
49879
  const unitDir = path13.join(os8.homedir(), ".config", "systemd", "user");
48508
- fs15.mkdirSync(unitDir, { recursive: true });
48509
- fs15.writeFileSync(path13.join(unitDir, "oasis-node.service"), [
49880
+ fs16.mkdirSync(unitDir, { recursive: true });
49881
+ fs16.writeFileSync(path13.join(unitDir, "oasis-node.service"), [
48510
49882
  "[Unit]",
48511
49883
  "Description=Oasis Node Daemon",
48512
49884
  "After=network.target",
@@ -48529,8 +49901,8 @@ function setupAutostart() {
48529
49901
  }
48530
49902
  } else if (process.platform === "darwin") {
48531
49903
  const plist = path13.join(os8.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
48532
- fs15.mkdirSync(path13.dirname(plist), { recursive: true });
48533
- fs15.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
49904
+ fs16.mkdirSync(path13.dirname(plist), { recursive: true });
49905
+ fs16.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
48534
49906
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
48535
49907
  <plist version="1.0"><dict>
48536
49908
  <key>Label</key><string>com.oasis.node</string>
@@ -48547,14 +49919,14 @@ function setupAutostart() {
48547
49919
  }
48548
49920
  }
48549
49921
  function spawnDaemon() {
48550
- const log3 = fs15.openSync(LOG_FILE, "a");
49922
+ const log3 = fs16.openSync(LOG_FILE, "a");
48551
49923
  const child = (0, import_node_child_process10.spawn)(process.execPath, [BIN_FILE, "--_daemon"], {
48552
49924
  detached: true,
48553
49925
  stdio: ["ignore", log3, log3]
48554
49926
  });
48555
49927
  child.unref();
48556
49928
  const pid = child.pid ?? 0;
48557
- fs15.writeFileSync(PID_FILE, String(pid));
49929
+ fs16.writeFileSync(PID_FILE, String(pid));
48558
49930
  return pid;
48559
49931
  }
48560
49932
  function stopDaemon() {
@@ -48566,7 +49938,7 @@ function stopDaemon() {
48566
49938
  }
48567
49939
  }
48568
49940
  try {
48569
- fs15.unlinkSync(PID_FILE);
49941
+ fs16.unlinkSync(PID_FILE);
48570
49942
  } catch {
48571
49943
  }
48572
49944
  try {
@@ -48587,10 +49959,10 @@ void (async () => {
48587
49959
  console.error("[oasis] no config \u2014 run `oasis start` first");
48588
49960
  process.exit(1);
48589
49961
  }
48590
- fs15.writeFileSync(PID_FILE, String(process.pid));
49962
+ fs16.writeFileSync(PID_FILE, String(process.pid));
48591
49963
  const cleanup = () => {
48592
49964
  try {
48593
- fs15.unlinkSync(PID_FILE);
49965
+ fs16.unlinkSync(PID_FILE);
48594
49966
  } catch {
48595
49967
  }
48596
49968
  };