oasis_test_v2 2.2.17 → 2.2.18

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 +257 -31
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -5737,6 +5737,26 @@ CREATE TABLE IF NOT EXISTS ${q2}.event_handler_log (
5737
5737
  PRIMARY KEY (event_seq, handler_name)
5738
5738
  );
5739
5739
 
5740
+ -- \u5F85\u4EBA\u786E\u8BA4\u7684 intervention \u8349\u6848\u5361\uFF08\u547D\u4EE4\u6388\u6743\u5361 / \u6539\u56FE\u5361\uFF09\u3002
5741
+ --
5742
+ -- \u4E3A\u4EC0\u4E48\u8981\u843D\u5E93\uFF082026-09-11 \u5B9E\u5F55\uFF09\uFF1A\u65E7\u5F15\u64CE\u628A\u8D77\u8349\u8BB0\u6210\u4E00\u6761 propose_intervention op \u8FDB oplog\uFF0C
5743
+ -- \u91CD\u542F replay \u5C31 fold \u56DE pendingReviews\uFF1B\u51B3\u7B56 0130 \u9000\u5F79 oplog \u65F6\u8FD9\u4E00\u6BB5\u53EA\u642C\u4E86\u5185\u5B58 Map\uFF0C
5744
+ -- \u6CE8\u91CA\u8FD8\u5199\u7740\u300C\u77ED\u751F\u547D\u5468\u671F\u3001\u91CD\u542F\u4E22\u53EF\u63A5\u53D7\u300D\u3002\u4F46\u8FD9\u5F20\u5361\u7684\u8BED\u4E49\u662F**\u7B49\u4E00\u4E2A\u771F\u4EBA\u6765\u70B9**\uFF0C\u5BFF\u547D\u7531\u4EBA\u51B3\u5B9A\uFF0C
5745
+ -- \u4E0D\u7531\u8FDB\u7A0B\u51B3\u5B9A\u3002v2 \u4E0A 31 \u5C0F\u65F6\u91CD\u542F\u4E86 158 \u6B21\uFF08\u22485 \u6B21/\u5C0F\u65F6\uFF09\uFF0C\u5B9E\u6D4B\u4E00\u5F20\u5361\u8D77\u8349 24 \u79D2\u540E\u5C31\u88AB\u91CD\u542F\u62B9\u6389\uFF0C
5746
+ -- \u534F\u8C03\u8005\u5374\u4ECD\u5728\u5BF9\u8BDD\u91CC\u8BA9\u4EBA\u300C\u53BB\u6536\u4EF6\u7BB1\u627E\u90A3\u5F20\u5361\u300D\u2014\u2014\u804A\u5929\u9875\u548C\u6536\u4EF6\u7BB1\u8BFB\u7684\u662F\u540C\u4E00\u4EFD\u5185\u5B58\uFF0C\u4E00\u8D77\u53D8\u7A7A\u3002
5747
+ --
5748
+ -- card \u5217\u7528 jsonb \u6574\u5B58\uFF1A\u5361\u4F53\u7684\u5F62\u72B6\u5F52 core\uFF08KernelBridge.draftCards\uFF09\u7BA1\uFF0C\u5F15\u64CE\u53EA\u8D1F\u8D23\u5B58\u53D6\uFF0C
5749
+ -- \u4E0D\u8BA4\u91CC\u9762\u7684\u4E1A\u52A1\u5B57\u6BB5\u2014\u2014\u5426\u5219\u6BCF\u52A0\u4E00\u4E2A\u5B57\u6BB5\u90FD\u8981\u52A8 DDL\uFF0C\u800C\u5F15\u64CE\u4E0D\u8BE5\u77E5\u9053 InterventionPlan \u662F\u4EC0\u4E48\u3002
5750
+ -- \u7D22\u5F15\u4F4D\u53EA\u7559\u6392\u969C\u8981\u7528\u7684\u90A3\u4E24\u5217\u3002\u4E0D\u52A0 workorder_id \u5916\u952E\uFF1A\u5361\u53EF\u4EE5\u6307\u5411\u4E00\u4E2A\u521A\u88AB\u4F5C\u5E9F\u7684\u8282\u70B9\uFF0C
5751
+ -- \u5916\u952E\u4F1A\u8BA9\u300C\u8D77\u8349\u300D\u8FD9\u4E00\u6B65\u76F4\u63A5\u5931\u8D25\uFF0C\u6BD4\u7559\u4E00\u6761\u5B64\u513F\u5361\u7CDF\u5F97\u591A\u3002
5752
+ CREATE TABLE IF NOT EXISTS ${q2}.intervention_drafts (
5753
+ draft_id text PRIMARY KEY,
5754
+ workorder_id text,
5755
+ created_at timestamptz NOT NULL,
5756
+ card jsonb NOT NULL
5757
+ );
5758
+ CREATE INDEX IF NOT EXISTS intervention_drafts_wo_idx ON ${q2}.intervention_drafts (workorder_id);
5759
+
5740
5760
  -- \u2500\u2500 \u8FC1\u79FB\uFF1A\u65E2\u6709\u90E8\u7F72\u7684\u5217\u589E\u91CF\uFF08CREATE TABLE IF NOT EXISTS \u4E0D\u52A0\u5217\uFF0C\u8FD9\u91CC\u8865\uFF09\u2500\u2500
5741
5761
  -- \u2605 review_requirements.source \u6269\u4E3A\u56DB\u503C\uFF08+'closure'\uFF09\uFF1A\u7ED3\u6848\u4EBA\u95F8\u7531 scan \u5728\u6536\u53E3\u90A3\u4E00\u523B\u5199\u5165\uFF0C\u4E0E manual \u5206\u5BB6\u3002
5742
5762
  -- \u5199\u6CD5\u4E0E\u4E0B\u65B9 works_lane_check \u540C\u6B3E\uFF08\u5148\u5220\u540E\u52A0\uFF0C\u6210\u5BF9\u5E42\u7B49\uFF09\u3002
@@ -11278,6 +11298,38 @@ var init_store_postgres = __esm({
11278
11298
  createdAt: iso(r.createdAt)
11279
11299
  }));
11280
11300
  }
11301
+ /* ── intervention 草案卡(见 store.ts 的接口注释)。与上面两口钟同类:直连 pool 的窄读写,
11302
+ 标识符位一律经 quoteIdent——带连字符的公司 schema 裸拼会 syntax error。 ── */
11303
+ async listInterventionDrafts() {
11304
+ const res = await this.pool.query(
11305
+ `SELECT draft_id AS "draftId", workorder_id AS "workorderId", created_at AS "createdAt", card
11306
+ FROM ${quoteIdent(this.schema)}.intervention_drafts
11307
+ ORDER BY created_at`
11308
+ );
11309
+ return res.rows.map((r) => ({
11310
+ draftId: r.draftId,
11311
+ workorderId: r.workorderId ?? null,
11312
+ createdAt: iso(r.createdAt),
11313
+ card: r.card
11314
+ }));
11315
+ }
11316
+ async saveInterventionDraft(record8) {
11317
+ await this.pool.query(
11318
+ `INSERT INTO ${quoteIdent(this.schema)}.intervention_drafts (draft_id, workorder_id, created_at, card)
11319
+ VALUES ($1, $2, $3::timestamptz, $4::jsonb)
11320
+ ON CONFLICT (draft_id) DO UPDATE
11321
+ SET workorder_id = EXCLUDED.workorder_id,
11322
+ created_at = EXCLUDED.created_at,
11323
+ card = EXCLUDED.card`,
11324
+ [record8.draftId, record8.workorderId, record8.createdAt, JSON.stringify(record8.card ?? null)]
11325
+ );
11326
+ }
11327
+ async deleteInterventionDraft(draftId) {
11328
+ await this.pool.query(
11329
+ `DELETE FROM ${quoteIdent(this.schema)}.intervention_drafts WHERE draft_id = $1`,
11330
+ [draftId]
11331
+ );
11332
+ }
11281
11333
  };
11282
11334
  PgTx = class {
11283
11335
  constructor(c, s2) {
@@ -15588,7 +15640,7 @@ ${m2.spec.split("\n").map((l) => ` > ${l}`).join("\n")}`);
15588
15640
  `- \u7F3A\u4E00\u4E2A**\u8FD8\u4E0D\u5B58\u5728\u7684\u524D\u7F6E** \u2192 \`oasis gap\` \u4EA4\u4E0A\u53BB\uFF0C**\u522B\u786C\u7F16**\uFF1B\u5DF2\u505A\u5B8C\u7684\u90E8\u5206\u7167\u5E38 \`propose\`\uFF0C\u4F46\u8FD9\u4E00\u8F6E \`conclude\` \u4F1A\u88AB\u62D2\uFF0C\u63D0\u4EA4\u5B8C\u5373\u6536\u5DE5\u2014\u2014\u524D\u7F6E\u5C31\u7EEA\u540E\u7CFB\u7EDF\u4F1A\u5E26\u7740\u89E3\u6CD5\u8BF4\u660E\u91CD\u65B0\u5524\u8D77\u4F60\u3002`,
15589
15641
  `- \u5BF9**\u4E0A\u6E38**\u6709\u610F\u89C1\u2014\u2014\u6709\u7591\u95EE\u3001\u8981\u5B83\u6539\u3001\u5224\u5B83\u6CA1\u8FBE\u6807 \u2192 \`oasis annotate <\u4E0A\u6E38id> --body "<\u8BF4\u6E05\u695A>"\` \u540E**\u6536\u5DE5**\u3002\u5199\u7ED9\u4E0A\u6E38\u7684\u8BC4\u8BBA\u81EA\u52A8\u5E26\u963B\u585E\u529B\uFF1A**\u5B83\u7B54\u590D\u524D\u4E0D\u80FD\u6536\u5C3E**\uFF0C\u7B54\u590D\u540E\u7CFB\u7EDF\u91CD\u6D3E\u4F60\u3002`,
15590
15642
  ...args.part === void 0 ? [`- \u6D3B\u592A\u5927 / \u53EF\u5E76\u884C \u2192 \`oasis add-revision\` \u62C6\u6210\u5B50\u90E8\u4EF6\u6D3E\u51FA\u53BB\uFF0C\u522B\u4E00\u4EBA\u4E00\u4F1A\u8BDD\u786C\u625B\u3002`] : [],
15591
- ...batchedContinuation && args.part === void 0 ? [`- \u957F\u4EFB\u52A1\u5DF2\u5141\u8BB8\u5206\u6279\u7EED\u8DD1\uFF1A\u4E00\u6279\u6709\u8FDB\u5C55\u4F46\u6574\u4F53\u672A\u5B8C\u6210\u65F6\uFF0C\u6309\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u91CC\u7684 propose \u547D\u4EE4\u8FFD\u52A0 \`--checkpoint --continue\`\uFF0Creason \u5199\u6E05\u672C\u6279\u5B8C\u6210\u4E86\u4EC0\u4E48\u3001\u5269\u4EC0\u4E48\u3001\u4E0B\u6279\u4ECE\u54EA\u7EE7\u7EED\uFF0C\u7136\u540E\u6536\u5DE5\uFF1B\u7CFB\u7EDF\u4F1A\u6D3E\u53D1\u4E0B\u4E00\u8F6E\u6B63\u5F0F session\u3002\u5168\u90E8\u5B8C\u6210\u65F6\u7167\u5E38 propose \u540E \`oasis conclude ${artifactId}\`\u3002`] : [],
15643
+ ...batchedContinuation && args.part === void 0 ? [`- \u957F\u4EFB\u52A1\u5DF2\u5141\u8BB8\u5206\u6279\u7EED\u8DD1\uFF1A\u4E00\u6279\u6709\u8FDB\u5C55\u4F46\u6574\u4F53\u672A\u5B8C\u6210\u65F6\uFF0C\u6309\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u91CC\u7684 propose \u547D\u4EE4\u8FFD\u52A0 \`--checkpoint --continue\`\uFF0Creason \u5199\u6E05\u672C\u6279\u5B8C\u6210\u4E86\u4EC0\u4E48\u3001\u5269\u4EC0\u4E48\u3001\u4E0B\u6279\u4ECE\u54EA\u7EE7\u7EED\uFF0C\u7136\u540E\u6536\u5DE5\uFF1B\u7CFB\u7EDF\u4F1A\u6D3E\u53D1\u4E0B\u4E00\u8F6E\u6B63\u5F0F session\u3002\u5168\u90E8\u5B8C\u6210\u65F6\u7167\u5E38 propose \u540E \`oasis conclude ${artifactId} --note "<\u4EA4\u4ED8\u7ED3\u8BBA>"\`\u3002`] : [],
15592
15644
  `- \u5199\u5165\u53EA\u8D70 \`oasis propose\`\uFF0C\u771F\u6B63\u5B8C\u6574\u4E86\u624D \`oasis conclude\`\uFF1B\u5361\u6CA1\u5361\u4F4F\u3001\u8FDB\u5EA6\u90FD\u662F\u7CFB\u7EDF\u7B97\u7684\uFF0C\u522B\u81EA\u5DF1\u7EF4\u62A4\u3002`,
15593
15645
  `- \u4F46**\u7B80\u5355\u3001\u81EA\u8DB3\u3001\u4E0D\u4F9D\u8D56\u672A\u5B9A\u51B3\u7B56\u7684\u4EFB\u52A1**\uFF08\u5982\u6539\u4E2A\u80CC\u666F\u8272\uFF09\u2192 \u76F4\u63A5\u505A\uFF0C\u5224\u65AD\u6743\u5728\u4F60\u3002`,
15594
15646
  ...typeDef?.recommendedInputs && typeDef.recommendedInputs.length > 0 ? [`- \u672C\u7C7B\u578B\u5E38\u89C1\u4E0A\u6E38\u4F9D\u8D56\uFF08\u63A8\u8350\uFF0C\u975E\u5F3A\u5236\uFF09\uFF1A${typeDef.recommendedInputs.join("\u3001")}\u2014\u2014\u4F9D\u8D56\u5176\u4E2D\u67D0\u4E2A\u800C\u5B83\u8FD8\u6CA1\u6709\uFF0C\u6309\u4E0A\u9762 gap \u89C4\u5219\u62A5\u7F3A\u53E3\u3002`] : []
@@ -19353,8 +19405,14 @@ var init_kernel_bridge = __esm({
19353
19405
  /** 下一轮刷新是否顺带重扫一小片"不脏"的工单。只由 `refreshModel({ resync: true })`(读模型时钟)置位。 */
19354
19406
  resyncRequested = false;
19355
19407
  /**
19356
- * intervention 待授权卡(内存态)。旧引擎存 oplog + fold 出 pendingReviews;新引擎无 oplog,
19357
- * 用内存 map 承载同样的「propose → 人等确认 → apply-draft/reject」流。短生命周期、重启丢可接受。
19408
+ * intervention 待授权卡。内存 map 是**读路径的缓存**,真源在库里(`intervention_drafts` 表,
19409
+ * engine/schema.ts)——`open()` 启动时 rehydrate,propose 写库,apply/reject 删库。
19410
+ *
19411
+ * 旧引擎把起草记成一条 `propose_intervention` op 进 oplog,重启 replay 就 fold 回 pendingReviews;
19412
+ * 决策 0130 退役 oplog 时这一段只搬了内存 Map,注释写着「短生命周期、重启丢可接受」。
19413
+ * 2026-09-11 实录证伪了这句:v2 上 31 小时重启 158 次,一张卡起草 24 秒后就被抹掉,
19414
+ * 而协调者仍在对话里让人「去收件箱找那张卡」——聊天页和收件箱读的是同一份 `pendingReviews`,
19415
+ * 一起变空。这张卡的寿命由**等它的那个人**决定,不由进程决定,所以必须落库。
19358
19416
  */
19359
19417
  draftCards = /* @__PURE__ */ new Map();
19360
19418
  model;
@@ -19374,6 +19432,14 @@ var init_kernel_bridge = __esm({
19374
19432
  for (const record8 of records) kernel.markWorkorderDirty(record8.workorderId);
19375
19433
  });
19376
19434
  bus.onApplied((wid) => kernel.markWorkorderDirty(wid));
19435
+ try {
19436
+ for (const rec of await store.listInterventionDrafts()) {
19437
+ const card2 = rec.card;
19438
+ kernel.draftCards.set(rec.draftId, { ...card2, at: card2?.at || rec.createdAt });
19439
+ }
19440
+ } catch (err) {
19441
+ console.warn(`[kernel-bridge] open \u6062\u590D\u5F85\u6388\u6743\u5361\u5931\u8D25\uFF08\u672C\u6B21\u91CD\u542F\u7684\u5B58\u91CF\u5361\u4F1A\u4E22\uFF09\uFF1A${String(err)}`);
19442
+ }
19377
19443
  try {
19378
19444
  const all2 = await store.transaction((tx) => tx.listAllWorkorders());
19379
19445
  for (const id of all2) kernel.trackedWorkorders.add(id);
@@ -19384,6 +19450,39 @@ var init_kernel_bridge = __esm({
19384
19450
  return kernel;
19385
19451
  }
19386
19452
  /* ─────────────────────── 内部工具 ─────────────────────── */
19453
+ /**
19454
+ * 起草落库。**写失败不阻断起草**——卡已经在内存里、已经显示给人了,这时候抛错会让
19455
+ * `oasis force-conclude` 整条命令失败,比"这张卡撑不过下次重启"糟得多。退化成本次改动之前的行为。
19456
+ */
19457
+ async persistDraftCard(draftId, card2) {
19458
+ try {
19459
+ await this.store.saveInterventionDraft({
19460
+ draftId,
19461
+ workorderId: this.workorderOfCard(card2),
19462
+ createdAt: card2.at,
19463
+ card: card2
19464
+ });
19465
+ } catch (err) {
19466
+ console.warn(`[kernel-bridge] \u5F85\u6388\u6743\u5361\u843D\u5E93\u5931\u8D25 ${draftId}\uFF08\u91CD\u542F\u540E\u4F1A\u4E22\uFF09\uFF1A${String(err)}`);
19467
+ }
19468
+ }
19469
+ /** 卡已终局(人点了确认或驳回)→ 删库。失败只记日志:内存已出队,残留行会在下次 rehydrate 后
19470
+ * 被 apply/reject 的「卡不存在」分支挡掉,不会让已执行的命令被再执行一次。 */
19471
+ async forgetDraftCard(draftId) {
19472
+ try {
19473
+ await this.store.deleteInterventionDraft(draftId);
19474
+ } catch (err) {
19475
+ console.warn(`[kernel-bridge] \u5F85\u6388\u6743\u5361\u5220\u5E93\u5931\u8D25 ${draftId}\uFF1A${String(err)}`);
19476
+ }
19477
+ }
19478
+ /** 卡挂在哪张工单下(排障列用,允许为空)。锚点可能指向一个模型里已经没有的产物。 */
19479
+ workorderOfCard(card2) {
19480
+ const fromPlan = card2.plan?.workspace;
19481
+ if (fromPlan) return fromPlan;
19482
+ const anchor = card2.anchor;
19483
+ if (!anchor) return null;
19484
+ return this.model.artifacts.get(anchor)?.workspace ?? null;
19485
+ }
19387
19486
  /** artifactId → 所属 workorderId(从 model 查)。 */
19388
19487
  wo(artifactId) {
19389
19488
  const a = this.model.artifacts.get(artifactId);
@@ -19554,7 +19653,8 @@ var init_kernel_bridge = __esm({
19554
19653
  workspace: cardWs,
19555
19654
  advisory: [],
19556
19655
  downstreamClosure: [],
19557
- at: "",
19656
+ // 真实起草时刻。曾经恒为 "":收件箱那行因此时间列印 "-"、排序键空串最小、恒沉到列表最后一行。
19657
+ at: card2.at ?? "",
19558
19658
  // ★ anchor 也兜底到 plan ops 的产物 id——inbox change-confirm 用 r.anchor 找「anchor owner 的
19559
19659
  // 人类上级」(leadOf(r.anchor));graph-edit 卡之前恒空、boss 收不到卡(collab-inbox-audit 实测)。
19560
19660
  anchor: anchorId || card2.anchor || "",
@@ -21007,30 +21107,33 @@ var init_kernel_bridge = __esm({
21007
21107
  async proposeIntervention(plan, actor, opts) {
21008
21108
  const preview2 = await previewIntervention(this.model, plan, { schema: this.schema, roleIndex: this.roleIndex });
21009
21109
  const draftId = `draft:${(0, import_node_crypto3.randomUUID)()}`;
21010
- this.draftCards.set(draftId, {
21110
+ const card2 = {
21011
21111
  kind: "graph-edit",
21012
21112
  plan,
21013
21113
  proposedBy: actor,
21014
21114
  origin: opts?.origin,
21015
21115
  parentMessageId: opts?.parentMessageId,
21016
21116
  changeClass: preview2.changeClass,
21017
- nonMonotonicOps: preview2.nonMonotonicOps
21018
- });
21117
+ nonMonotonicOps: preview2.nonMonotonicOps,
21118
+ at: (/* @__PURE__ */ new Date()).toISOString()
21119
+ };
21120
+ this.draftCards.set(draftId, card2);
21121
+ await this.persistDraftCard(draftId, card2);
21019
21122
  await this.refreshModel();
21020
21123
  return { draftId, changeClass: preview2.changeClass, nonMonotonicOps: preview2.nonMonotonicOps };
21021
21124
  }
21022
21125
  async proposeCommandAuthorization(args) {
21023
21126
  const originNorm = args.origin ?? null;
21024
21127
  const argsJson = JSON.stringify(args.args ?? {});
21025
- for (const [id, card2] of this.draftCards) {
21026
- if (card2.kind !== "command" || card2.command !== args.command) continue;
21027
- if ((card2.origin ?? null) !== originNorm) continue;
21028
- if (JSON.stringify(card2.commandArgs ?? {}) !== argsJson) continue;
21128
+ for (const [id, card3] of this.draftCards) {
21129
+ if (card3.kind !== "command" || card3.command !== args.command) continue;
21130
+ if ((card3.origin ?? null) !== originNorm) continue;
21131
+ if (JSON.stringify(card3.commandArgs ?? {}) !== argsJson) continue;
21029
21132
  await this.refreshModel();
21030
21133
  return { draftId: id };
21031
21134
  }
21032
21135
  const draftId = `draft:${(0, import_node_crypto3.randomUUID)()}`;
21033
- this.draftCards.set(draftId, {
21136
+ const card2 = {
21034
21137
  kind: "command",
21035
21138
  command: args.command,
21036
21139
  commandArgs: args.args,
@@ -21038,8 +21141,11 @@ var init_kernel_bridge = __esm({
21038
21141
  anchor: args.artifactId,
21039
21142
  proposedBy: args.actor,
21040
21143
  origin: args.origin,
21041
- parentMessageId: args.parentMessageId
21042
- });
21144
+ parentMessageId: args.parentMessageId,
21145
+ at: (/* @__PURE__ */ new Date()).toISOString()
21146
+ };
21147
+ this.draftCards.set(draftId, card2);
21148
+ await this.persistDraftCard(draftId, card2);
21043
21149
  await this.refreshModel();
21044
21150
  return { draftId };
21045
21151
  }
@@ -21068,11 +21174,13 @@ var init_kernel_bridge = __esm({
21068
21174
  throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u4E0D\u652F\u6301\u6267\u884C ${card2.command}`);
21069
21175
  }
21070
21176
  this.draftCards.delete(args.draftId);
21177
+ await this.forgetDraftCard(args.draftId);
21071
21178
  await this.refreshModel();
21072
21179
  return { command: card2.command, artifactId };
21073
21180
  }
21074
21181
  async rejectIntervention(args) {
21075
21182
  this.draftCards.delete(args.draftId);
21183
+ await this.forgetDraftCard(args.draftId);
21076
21184
  await this.refreshModel();
21077
21185
  }
21078
21186
  /**
@@ -21383,6 +21491,7 @@ var init_kernel_bridge = __esm({
21383
21491
  }
21384
21492
  if (typeof plan.resolvesProposal === "string") {
21385
21493
  this.draftCards.delete(plan.resolvesProposal);
21494
+ await this.forgetDraftCard(plan.resolvesProposal);
21386
21495
  await this.refreshModel();
21387
21496
  }
21388
21497
  return {
@@ -221047,7 +221156,7 @@ async function actorStats(actorId, opts = {}) {
221047
221156
  const now = opts.now ?? /* @__PURE__ */ new Date();
221048
221157
  const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3).toISOString();
221049
221158
  const to = now.toISOString();
221050
- const weeklyUsage = opts.usage?.listRuns ? summarizeWeeklyRuns(await listWeeklyRuns(opts.usage, actorId, from, to)) : summarizeWeeklyUsage(opts.usage?.queryUsageStats ? await opts.usage.queryUsageStats({
221159
+ const weeklyUsage = opts.usage?.listRuns ? summarizeWeeklyRuns(await listWeeklyRuns(opts.usage, actorId, from, to, opts.artifactIds)) : summarizeWeeklyUsage(!opts.artifactIds && opts.usage?.queryUsageStats ? await opts.usage.queryUsageStats({
221051
221160
  actorIds: [actorId],
221052
221161
  from,
221053
221162
  to,
@@ -221055,6 +221164,8 @@ async function actorStats(actorId, opts = {}) {
221055
221164
  }) : []);
221056
221165
  return {
221057
221166
  actorId,
221167
+ usageAvailable: !!(opts.usage?.listRuns || !opts.artifactIds && opts.usage?.queryUsageStats),
221168
+ ...opts.model ? { participatingTaskCount: participatingTaskCount(opts.model, actorId) } : {},
221058
221169
  tokenWeek: weeklyUsage.tokenWeek,
221059
221170
  tokenWeekBreakdown: weeklyUsage.tokenWeekBreakdown,
221060
221171
  costWeekUsdMicros: weeklyUsage.costWeekUsdMicros,
@@ -221064,12 +221175,12 @@ async function actorStats(actorId, opts = {}) {
221064
221175
  unpricedModels: weeklyUsage.unpricedModels
221065
221176
  };
221066
221177
  }
221067
- async function listWeeklyRuns(trace, actorId, from, to) {
221178
+ async function listWeeklyRuns(trace, actorId, from, to, artifactIds) {
221068
221179
  const out = [];
221069
221180
  let cursor;
221070
221181
  do {
221071
221182
  const page = await trace.listRuns({ actorId, from, to, limit: 500, ...cursor ? { cursor } : {} });
221072
- out.push(...page.items);
221183
+ out.push(...page.items.filter((run) => !artifactIds || run.artifactId != null && artifactIds.has(run.artifactId)));
221073
221184
  cursor = page.nextCursor ?? void 0;
221074
221185
  } while (cursor);
221075
221186
  return out;
@@ -221080,6 +221191,13 @@ function summarizeWeeklyRuns(runs) {
221080
221191
  function summarizeWeeklyUsage(rows) {
221081
221192
  return summarize2(rows.map((row) => ({ model: row.effectiveModel, usage: row })));
221082
221193
  }
221194
+ function participatingTaskCount(model, actorId) {
221195
+ const workorders = /* @__PURE__ */ new Set();
221196
+ for (const artifact of model.artifacts.values()) {
221197
+ if (artifact.owner === actorId) workorders.add(artifact.workspace);
221198
+ }
221199
+ return workorders.size;
221200
+ }
221083
221201
  var init_stats = __esm({
221084
221202
  "../server/src/domains/actors/stats.ts"() {
221085
221203
  "use strict";
@@ -222415,9 +222533,16 @@ function actorsDomain(opts) {
222415
222533
  throw new ApiError(400, "BAD_REQUEST", "\u4F20 enabled\uFF08\u5F00\u5173\uFF09/ appliedToAll\uFF08\u5168\u5458\u751F\u6548\uFF09/ name\xB7description\uFF08\u66F4\u65B0\u5143\u6570\u636E\uFF09");
222416
222534
  });
222417
222535
  router.get("/api/actors/:id/stats", async (req) => {
222536
+ const ctx = await resolveCtx(req.auth.companyId);
222537
+ if (!await ctx.service.getActor(req.params.id)) throw new ApiError(404, "NOT_FOUND", "\u6CA1\u6709\u8FD9\u4E2A\u5458\u5DE5");
222538
+ const model = ctx.getModel?.();
222418
222539
  return {
222419
222540
  status: 200,
222420
- body: await actorStats(req.params.id, { usage: opts.trace?.() })
222541
+ body: await actorStats(req.params.id, {
222542
+ usage: req.auth.companyId && !model ? void 0 : opts.trace?.(),
222543
+ model,
222544
+ ...req.auth.companyId ? { artifactIds: new Set(model?.artifacts.keys() ?? []) } : {}
222545
+ })
222421
222546
  };
222422
222547
  });
222423
222548
  router.get("/api/actors/:id/running-tasks", async (req) => {
@@ -222975,6 +223100,7 @@ function createActorsDomain(opts) {
222975
223100
  const engineStoreOf = (k2) => k2?.getStore?.();
222976
223101
  const defaultCtx = {
222977
223102
  service: buildService(opts.store, opts.kernel, opts.blobs),
223103
+ ...opts.kernel?.model ? { getModel: () => opts.kernel.model } : {},
222978
223104
  ...opts.oplog !== void 0 ? { oplog: opts.oplog } : {},
222979
223105
  ...engineStoreOf(opts.kernel) !== void 0 ? { engineStore: engineStoreOf(opts.kernel) } : {},
222980
223106
  ...opts.blobs !== void 0 ? { blobs: opts.blobs } : {}
@@ -222988,6 +223114,7 @@ function createActorsDomain(opts) {
222988
223114
  const blobs = engine.assets ?? engine.blobs;
222989
223115
  const ctx = {
222990
223116
  service: buildService(engine.registry, engine.kernel, blobs),
223117
+ getModel: () => engine.kernel.model,
222991
223118
  oplog: engine.oplog,
222992
223119
  ...engineStoreOf(engine.kernel) !== void 0 ? { engineStore: engineStoreOf(engine.kernel) } : {},
222993
223120
  blobs
@@ -224873,8 +225000,10 @@ function buildWorkorderActivity(input) {
224873
225000
  ] : []
224874
225001
  });
224875
225002
  }
225003
+ const openedSeq = events.find((rec) => rec.event.kind === "work.create")?.seq;
225004
+ const beforeOpen = (seq) => openedSeq !== void 0 && seq < openedSeq;
224876
225005
  const drafts = [
224877
- ...standalone,
225006
+ ...standalone.filter((d) => !beforeOpen(d.seq)),
224878
225007
  ...executionCards,
224879
225008
  ...visibleReviews.values(),
224880
225009
  ...issueCards.values(),
@@ -224950,7 +225079,7 @@ function buildWorkorderActivity(input) {
224950
225079
  .../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(traced ? runIdOfCard(d) : void 0)
224951
225080
  };
224952
225081
  });
224953
- cards.push(...reviewRequirementActivity(snap, events, ref2));
225082
+ cards.push(...reviewRequirementActivity(snap, events, ref2).filter((c) => !beforeOpen(c.seq)));
224954
225083
  cards.sort((a, b2) => a.updatedAt.localeCompare(b2.updatedAt) || a.seq - b2.seq || a.id.localeCompare(b2.id));
224955
225084
  return { workorderId, cards, truncated };
224956
225085
  }
@@ -225620,6 +225749,12 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
225620
225749
  actionRequired: true,
225621
225750
  summary: changeConfirmSummary(r),
225622
225751
  draftId: r.draftId,
225752
+ // ★ 落点(2026-09-11):带上起草它的那段对话,让前端把这一行跳回**卡真正渲染着的地方**。
225753
+ // 此前这一行恒跳「任务详情 ?tab=activity」,而全仓库渲染待审卡的只有 chat 页与任务页的协调者
225754
+ // 对话(都按 origin=会话 id 查),动态 tab 里一行渲染代码都没有——于是收件箱这条路
225755
+ // 「找得到那一行、却点不到确认键」。origin 缺省(自主会话起草、没人在场)时不带,
225756
+ // 仍回落工单详情。
225757
+ ...r.origin ? { chatSessionId: r.origin } : {},
225623
225758
  ...anchorArt ? { title: nodeLabel(anchorArt) } : {},
225624
225759
  detail: `${actorName(r.proposedBy) || "\u7BA1\u7406\u8005"}\uFF1A${r.command?.effectLabel ?? firstSentence(r.plan.reason)}`,
225625
225760
  ...r.kind !== "command" && opCount > 0 ? { impact: `\u6D89\u53CA ${opCount} \u5904\u6539\u52A8${r.nonMonotonicOps.length > 0 ? " \xB7 \u542B\u9500\u6BC1\u6027" : ""}` } : {},
@@ -253379,6 +253514,17 @@ var init_cutover_aware_trace = __esm({
253379
253514
  if (row.state === "frozen") throw new BundleFrozenError("trace");
253380
253515
  return this.o.cutover.readsCompanySchema(row) ? this.o.owned : this.o.legacy;
253381
253516
  }
253517
+ /**
253518
+ * 这家公司的 trace 当前**物理落在哪**:切走了就是它自己的引擎 schema,没切走就是那个
253519
+ * 所有未切公司共用的 legacy store。
253520
+ *
253521
+ * 给 {@link FanOutTraceStore} 去重用。判据**不在调用方重算**——就是 {@link pick} 那一条
253522
+ * (`cutover.get` + `readsCompanySchema`),照抄一遍就是又造一处会分叉的实现。
253523
+ */
253524
+ async storageKey() {
253525
+ const row = await this.o.cutover.get(this.o.companyId, "trace");
253526
+ return this.o.cutover.readsCompanySchema(row) ? `owned:${this.o.companyId}` : "legacy";
253527
+ }
253382
253528
  // ── run ──────────────────────────────────────────────────────────────────
253383
253529
  async createRun(input) {
253384
253530
  return (await this.pickWritable()).createRun(input);
@@ -253496,10 +253642,43 @@ var init_cutover_aware_trace = __esm({
253496
253642
  constructor(o) {
253497
253643
  this.o = o;
253498
253644
  }
253499
- async first(op, fn) {
253645
+ /**
253646
+ * 本次要查的物理落点清单——**同一个 store 只出现一次**。
253647
+ *
253648
+ * 为什么必须去重:`trace` 是按 (公司, bundle) 逐个切的,没切走的公司**全部共用同一个
253649
+ * legacy store**(读同一张 `public.agent_runs`)。此前 `union` 直接按公司逐家拼接,于是
253650
+ * 一条 run 在库里只有一行,却被取回 N 份(N = 未切走的公司数)。
253651
+ *
253652
+ * 2026-09-11 v2 生产实证(`ws:wo-9f4652cd`):35 家公司、其中 33 家 trace 未切走,
253653
+ * 动态卡的「执行轨迹」抽屉里同一轮 attempt 出现 **33 次**;`listEvents` 更狠——那一轮
253654
+ * `agent_run_events` 有 1158 行,扇出后返回 ≈3.8 万行,还附带 33 条一模一样的 SQL。
253655
+ * 不是显示层重复,是响应体和查询量一起炸。
253656
+ *
253657
+ * 去重键由 store 自己报,不在这里按 cutover 状态重算(见 `storageKey` 的注释)。
253658
+ * 也不能按 `storeFor` 返回的对象做身份去重:`openCompanyPlane` 给每家各造了一个
253659
+ * `CutoverAwareTraceStore` 包装实例,33 个不同对象包着同一个 legacy store。
253660
+ */
253661
+ async targets(op) {
253662
+ const out = [];
253663
+ const seen = /* @__PURE__ */ new Set();
253500
253664
  for (const cid of await this.o.listCompanies()) {
253501
253665
  try {
253502
- const r = await fn(await this.o.storeFor(cid));
253666
+ if (this.o.storageKeyFor) {
253667
+ const key = await this.o.storageKeyFor(cid);
253668
+ if (seen.has(key)) continue;
253669
+ seen.add(key);
253670
+ }
253671
+ out.push({ cid, store: await this.o.storeFor(cid) });
253672
+ } catch (err) {
253673
+ this.o.onError?.(cid, op, err);
253674
+ }
253675
+ }
253676
+ return out;
253677
+ }
253678
+ async first(op, fn) {
253679
+ for (const { cid, store } of await this.targets(op)) {
253680
+ try {
253681
+ const r = await fn(store);
253503
253682
  if (r != null) return r;
253504
253683
  } catch (err) {
253505
253684
  this.o.onError?.(cid, op, err);
@@ -253509,20 +253688,26 @@ var init_cutover_aware_trace = __esm({
253509
253688
  }
253510
253689
  async union(op, fn) {
253511
253690
  const out = [];
253512
- for (const cid of await this.o.listCompanies()) {
253691
+ for (const { cid, store } of await this.targets(op)) {
253513
253692
  try {
253514
- out.push(...await fn(await this.o.storeFor(cid)));
253693
+ out.push(...await fn(store));
253515
253694
  } catch (err) {
253516
253695
  this.o.onError?.(cid, op, err);
253517
253696
  }
253518
253697
  }
253519
253698
  return out;
253520
253699
  }
253521
- /** 这个 run 属于哪一家。写侧要先拿它,别直接写。 */
253700
+ /**
253701
+ * 这个 run 属于哪一家。写侧要先拿它,别直接写。
253702
+ *
253703
+ * ⚠ 未切走的那批公司共用一张 legacy 表,这里**判不出真正的归属**——它们都「有」这条 run,
253704
+ * 返回的是名单里第一家。去重不改变这个答案(第一家仍是第一家),只是把 N 次重复查询收成一次。
253705
+ * 要真正判准归属,得等 `trace` bundle 切完、每家各有自己的 schema。
253706
+ */
253522
253707
  async companyOfRun(runId) {
253523
- for (const cid of await this.o.listCompanies()) {
253708
+ for (const { cid, store } of await this.targets("companyOfRun")) {
253524
253709
  try {
253525
- if (await (await this.o.storeFor(cid)).getRun(runId)) return cid;
253710
+ if (await store.getRun(runId)) return cid;
253526
253711
  } catch (err) {
253527
253712
  this.o.onError?.(cid, "companyOfRun", err);
253528
253713
  }
@@ -256328,6 +256513,11 @@ var init_default_company_schema_move = __esm({
256328
256513
  "chat_sessions",
256329
256514
  "event_handler_log",
256330
256515
  "events",
256516
+ // 待人确认的 intervention 草案卡(2026-09-11 落库)。与 events/workorders 同族:锚在本公司的
256517
+ // 工单与产物上、不跨公司读,所以**整表搬**。不进 ENGINE_SCHEMA_BUNDLE_TABLES——那份是
256518
+ // legacy 里混着所有公司行的表(dispatches/blobs/trace 一族),只能逐行按归属复制;
256519
+ // 这张表在默认公司 schema 里只有总部自己的卡。
256520
+ "intervention_drafts",
256331
256521
  "issue_attachments",
256332
256522
  "issue_recipients",
256333
256523
  "issue_replies",
@@ -260362,6 +260552,15 @@ async function startServe(opts) {
260362
260552
  const fanOutTrace = new FanOutTraceStore({
260363
260553
  listCompanies: async () => pgPool ? (await controlPlaneStore.listCompanies()).filter((c) => c.status === "active").map((c) => c.id) : [defaultCompanyId],
260364
260554
  storeFor: (cid) => traceStoreFor(cid),
260555
+ /**
260556
+ * 去重键:`trace` 未切走的公司全部共用同一个 legacy store,扇出只该查它一次。
260557
+ * 由 store 自己报落点,这里不按 cutover 状态重算(见 FanOutTraceStore.targets 的注释)。
260558
+ * 非 PG / 非 cutover-aware 的路径没有这个方法 ⇒ 退回按公司逐家查,行为不变。
260559
+ */
260560
+ storageKeyFor: async (cid) => {
260561
+ const s2 = await traceStoreFor(cid);
260562
+ return typeof s2.storageKey === "function" ? s2.storageKey() : cid;
260563
+ },
260365
260564
  onError: (cid, op, err) => console.warn(`[trace] ${cid} \u7684 ${op} \u5931\u8D25\uFF08\u5176\u4F59\u516C\u53F8\u7EE7\u7EED\uFF09\uFF1A${String(err)}`)
260366
260565
  });
260367
260566
  const traceSinkCache = /* @__PURE__ */ new Map();
@@ -268370,6 +268569,28 @@ var ASYNC_AUTH_MESSAGE = "\u5DF2\u63D0\u4EA4\u4EBA\u5DE5\u5BA1\u6838\uFF1B\u5148
268370
268569
  function asyncAuthorizationReturn(data) {
268371
268570
  return data !== void 0 ? { message: ASYNC_AUTH_MESSAGE, data } : { message: ASYNC_AUTH_MESSAGE };
268372
268571
  }
268572
+ var AUTH_CARD_GONE_MESSAGE = "\u8FD9\u5F20\u5F85\u6388\u6743\u5361\u5DF2\u7ECF\u4E0D\u5728\u5F85\u5904\u7406\u5217\u8868\u91CC\u4E86\u2014\u2014\u53EF\u80FD\u5DF2\u88AB\u4EBA\u5904\u7F6E\uFF0C\u4E5F\u53EF\u80FD\u5DF2\u5931\u6548\u3002**\u522B\u518D\u8BA9\u7528\u6237\u53BB\u6536\u4EF6\u7BB1\u627E\u8FD9\u5F20\u5361**\uFF1A\u5148\u7528\u53EA\u8BFB\u547D\u4EE4\u6838\u5BF9\u5F53\u524D\u72B6\u6001\uFF08\u5982 `oasis status <\u8282\u70B9>`\uFF09\uFF0C\u786E\u8BA4\u8FD9\u4EF6\u4E8B\u6CA1\u751F\u6548\uFF0C\u518D\u91CD\u65B0\u53D1\u8D77\u540C\u4E00\u6761\u547D\u4EE4\u3002";
268573
+ function authorizationGoneReturn(draftId) {
268574
+ return { message: AUTH_CARD_GONE_MESSAGE, data: { outcome: "authorization-gone", draftId } };
268575
+ }
268576
+ async function draftStillPending(base, headers, draftId) {
268577
+ try {
268578
+ const ctrl = new AbortController();
268579
+ const timer = setTimeout(() => ctrl.abort(), 5e3);
268580
+ timer.unref?.();
268581
+ try {
268582
+ const res = await fetch(`${base}/api/view?name=intervention-drafts`, { headers, signal: ctrl.signal });
268583
+ if (!res.ok) return null;
268584
+ const body2 = await res.json();
268585
+ if (!Array.isArray(body2.data)) return null;
268586
+ return body2.data.some((d) => d?.id === draftId);
268587
+ } finally {
268588
+ clearTimeout(timer);
268589
+ }
268590
+ } catch {
268591
+ return null;
268592
+ }
268593
+ }
268373
268594
  function renderGraphLines(graph) {
268374
268595
  const closureIds = new Set(graph.nodes.filter((n) => n.type === DELIVERY_SUMMARY_ARTIFACT_TYPE).map((n) => n.id));
268375
268596
  const out = ["\n\u8282\u70B9\uFF08\u6539\u56FE\u5F15\u7528\u7528\u8FD9\u4E9B id\uFF09:"];
@@ -268391,6 +268612,10 @@ function resolveAuthWaitMs() {
268391
268612
  }
268392
268613
  async function waitForCommandAuthorization(base, headers, draftId, fallback) {
268393
268614
  const asyncFallback = asyncAuthorizationReturn(fallback.data);
268615
+ const giveUp = async () => {
268616
+ const pending = await draftStillPending(base, headers, draftId);
268617
+ return pending === false ? authorizationGoneReturn(draftId) : asyncFallback;
268618
+ };
268394
268619
  const timeoutMs = resolveAuthWaitMs();
268395
268620
  const controller = new AbortController();
268396
268621
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -268448,12 +268673,13 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
268448
268673
  data: { outcome: "denied", draftId }
268449
268674
  };
268450
268675
  }
268451
- return asyncFallback;
268676
+ if (p2.status === "gone") return authorizationGoneReturn(draftId);
268677
+ return await giveUp();
268452
268678
  }
268453
268679
  }
268454
- return asyncFallback;
268680
+ return await giveUp();
268455
268681
  } catch {
268456
- return asyncFallback;
268682
+ return await giveUp();
268457
268683
  } finally {
268458
268684
  clearTimeout(timer);
268459
268685
  clearInterval(progress);
@@ -271958,7 +272184,7 @@ function shimScript() {
271958
272184
  }
271959
272185
 
271960
272186
  // src/index.ts
271961
- var PKG_VERSION = true ? "2.2.17" : "dev";
272187
+ var PKG_VERSION = true ? "2.2.18" : "dev";
271962
272188
  var LOCAL_BIN = localBin();
271963
272189
  var NPM_PREFIX = npmPrefix();
271964
272190
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.17",
3
+ "version": "2.2.18",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"
@@ -26,6 +26,6 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "oasisRelease": {
29
- "sourceHead": "1c93b971ada0fb88e327a81413365e770998710a"
29
+ "sourceHead": "dacd0d5f49191a0ba05c308ee96fef59efac071d"
30
30
  }
31
31
  }