oasis_test_v2 2.2.8 → 2.2.10

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 +366 -77
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -665,7 +665,20 @@ function isAgentAvatarPresetRef(ref2) {
665
665
  const id = ref2.slice(AGENT_AVATAR_PRESET_PREFIX.length);
666
666
  return AGENT_AVATAR_PRESET_IDS.includes(id);
667
667
  }
668
- var AGENT_AVATAR_PRESET_PREFIX, AGENT_AVATAR_LEGACY_PRESET_IDS, AGENT_AVATAR_V2_PRESET_IDS, AGENT_AVATAR_PRESET_IDS;
668
+ function humanAvatarToneOf(seed) {
669
+ let hash2 = 2166136261;
670
+ for (const ch of seed.trim()) {
671
+ hash2 = Math.imul(hash2 ^ (ch.codePointAt(0) ?? 0), 16777619) >>> 0;
672
+ }
673
+ return hash2 % HUMAN_AVATAR_TONE_COUNT;
674
+ }
675
+ function humanAvatarPresetRef(tone) {
676
+ return `${HUMAN_AVATAR_PRESET_PREFIX}${tone}`;
677
+ }
678
+ function defaultHumanAvatarRef(actorId) {
679
+ return humanAvatarPresetRef(humanAvatarToneOf(actorId));
680
+ }
681
+ var AGENT_AVATAR_PRESET_PREFIX, AGENT_AVATAR_LEGACY_PRESET_IDS, AGENT_AVATAR_V2_PRESET_IDS, AGENT_AVATAR_PRESET_IDS, SYSTEM_AVATAR_REF, HUMAN_AVATAR_PRESET_PREFIX, HUMAN_AVATAR_TONE_COUNT;
669
682
  var init_avatar = __esm({
670
683
  "../contract/src/avatar.ts"() {
671
684
  "use strict";
@@ -695,6 +708,9 @@ var init_avatar = __esm({
695
708
  ...AGENT_AVATAR_LEGACY_PRESET_IDS,
696
709
  ...AGENT_AVATAR_V2_PRESET_IDS
697
710
  ];
711
+ SYSTEM_AVATAR_REF = "preset:system";
712
+ HUMAN_AVATAR_PRESET_PREFIX = "preset:human:";
713
+ HUMAN_AVATAR_TONE_COUNT = 8;
698
714
  }
699
715
  });
700
716
 
@@ -904,16 +920,35 @@ var init_link_windows = __esm({
904
920
  /** orphan-sweep 判「账上在跑、四张认领表都不认」为孤儿的宽限(`serve.ts`)。 */
905
921
  orphanGraceMs: 12e4,
906
922
  /** orphan-sweep 在 serve 冷启动后推迟判孤儿的窗口(`serve.ts`)。 */
907
- orphanColdStartGraceMs: 18e4
923
+ orphanColdStartGraceMs: 18e4,
924
+ /**
925
+ * `/items` 读时把「账上还 running、本进程 live 注册表里却没有」的一轮**显示**成 `interrupted`
926
+ * 的宽限(ADR-0510 D6 表格第二行)。
927
+ *
928
+ * 取值必须 **≥ {@link LINK_WINDOWS.orphanGraceMs}**:这条判定只写 wire、不写账本,它的作用是
929
+ * 让页面别在权威扫描还没开始看的时候就一直转圈;但它也**不该比权威更早下结论**——早于
930
+ * `orphanGraceMs` 就等于拿时间当判据抢在核实前面说话(ADR-0166)。两个数写在同一处,
931
+ * 谁单边调参 `tests/adr-0300-link-windows.test.ts` 当场变红。
932
+ */
933
+ chatTurnPhaseStaleAfterMs: 12e4
908
934
  };
909
935
  }
910
936
  });
911
937
 
912
938
  // ../contract/src/registry.ts
913
- var AGENT_PREFS_DEFAULTS, SkillOwnedByAnotherCompanyError;
939
+ function isSystemActorId(id) {
940
+ return typeof id === "string" && id.startsWith(SYSTEM_ACTOR_ID_PREFIX);
941
+ }
942
+ function isHumanActorId(id) {
943
+ return typeof id === "string" && id.startsWith(HUMAN_ACTOR_ID_PREFIX);
944
+ }
945
+ var SYSTEM_ACTOR_ID_PREFIX, SYSTEM_ACTOR_DISPLAY_NAME, HUMAN_ACTOR_ID_PREFIX, AGENT_PREFS_DEFAULTS, SkillOwnedByAnotherCompanyError;
914
946
  var init_registry = __esm({
915
947
  "../contract/src/registry.ts"() {
916
948
  "use strict";
949
+ SYSTEM_ACTOR_ID_PREFIX = "actor:system";
950
+ SYSTEM_ACTOR_DISPLAY_NAME = "\u7CFB\u7EDF";
951
+ HUMAN_ACTOR_ID_PREFIX = "actor:human:";
917
952
  AGENT_PREFS_DEFAULTS = { memoryEnabled: true };
918
953
  SkillOwnedByAnotherCompanyError = class extends Error {
919
954
  constructor(skillId, companyId) {
@@ -5554,6 +5589,7 @@ CREATE TABLE IF NOT EXISTS ${q2}.work_artifacts (
5554
5589
  content_kind text NOT NULL CHECK (content_kind IN ('inline','external','manifest')),
5555
5590
  ordinal integer,
5556
5591
  created_at timestamptz NOT NULL,
5592
+ content_edited_at timestamptz,
5557
5593
  UNIQUE (work_id, logical_path)
5558
5594
  );
5559
5595
  CREATE INDEX IF NOT EXISTS work_artifacts_node_idx ON ${q2}.work_artifacts (node_id);
@@ -5719,6 +5755,9 @@ ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS proposal_reason text;
5719
5755
  -- \u2605 \u8FD0\u884C\u6D3B\u8DC3\u5FC3\u8DF3\uFF1Av2 submit_output / work.conclude \u5199\u5165\uFF1Bscan \u8D85\u65F6\u4EE5\u5B83\u8D77\u7B97\uFF08work \u751F\u547D\u5468\u671F\u5EF6\u957F\u540E
5720
5756
  -- 30min \u949F\u4E0D\u80FD\u518D\u7F69\u6B7B\u6574\u4E2A\u4F1A\u8BDD\uFF09\u3002\u5B58\u91CF\u4E3A NULL \u2192 \u56DE\u9000 started_at/created_at\uFF0C\u884C\u4E3A\u4E0D\u53D8\u3002
5721
5757
  ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS last_activity_at timestamptz;
5758
+ -- \u4EA7\u7269\u6B63\u6587\u5C31\u5730\u6539\u5199\uFF08work.content_edited\uFF09\u7684\u65F6\u523B\u3002\u5C31\u5730\u6539\u5199\u4E0D\u65B0\u5F00 work\u3001\u4E0D\u94F8\u65B0\u7248\uFF0C
5759
+ -- \u6CA1\u6709\u8FD9\u4E00\u5217\uFF0C\u300C\u76F8\u5173\u6587\u4EF6\u300D\u8868\u7684\u65F6\u95F4\u5217\u5C31\u53EA\u80FD\u505C\u5728\u9A8C\u6536\u65F6\u523B\u3002\u5B58\u91CF\u884C\u4E3A NULL = \u4ECE\u672A\u88AB\u6539\u5199\u8FC7\u3002
5760
+ ALTER TABLE ${q2}.work_artifacts ADD COLUMN IF NOT EXISTS content_edited_at timestamptz;
5722
5761
  -- \u2605 \u51BB\u7ED3\u6765\u4FE1\u96C6\u5408\uFF08\u8BA1\u5212 \xA76.5\uFF0C\u9636\u6BB5 E\uFF09\uFF1Awork.create \u6309 reasons \u7684 issue \u9879\u51BB\u7ED3\u7684 issue id \u6279\u6B21\uFF08jsonb \u6570\u7EC4\uFF09\u3002
5723
5762
  -- reply \u5F52\u5C5E/\u9500\u8D26/\u5BA1\u8BA1\u53EA\u8BA4\u5B83\uFF0C\u4E0D\u9760\u65F6\u95F4\u7A97\u731C\u6D4B\u3002\u5B58\u91CF\u4E3A NULL\uFF08\u65E0\u6279\u6B21\u8BED\u4E49\uFF09\u3002
5724
5763
  ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS frozen_issue_ids jsonb;
@@ -11382,7 +11421,11 @@ var init_store_postgres = __esm({
11382
11421
  contentRef: a.content_ref,
11383
11422
  contentKind: a.content_kind,
11384
11423
  ordinal: a.ordinal,
11385
- createdAt: iso(a.created_at)
11424
+ createdAt: iso(a.created_at),
11425
+ // 只在真被改写过时才带这个键:从未改写的行在内存态里是**没有这个字段**的,
11426
+ // PG 回读时补一个 `null` 会让「事件重放出来的态」和「从库里读出来的态」深比不相等
11427
+ // (packages/engine/tests/store-conformance.postgres.test.ts 就是钉这条一致性的)。
11428
+ ...iso(a.content_edited_at) ? { contentEditedAt: iso(a.content_edited_at) } : {}
11386
11429
  })),
11387
11430
  workInputs: inputs.rows.map((i) => ({
11388
11431
  workId: i.work_id,
@@ -11617,9 +11660,9 @@ var init_store_postgres = __esm({
11617
11660
  for (const a of m2.rows) {
11618
11661
  await this.c.query(
11619
11662
  `INSERT INTO ${this.s}.work_artifacts
11620
- (id,work_id,node_id,type,name,logical_path,content_ref,content_kind,ordinal,created_at)
11621
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT (id) DO NOTHING`,
11622
- [a.id, a.workId, a.nodeId, a.type, a.name, a.logicalPath, a.contentRef, a.contentKind, a.ordinal, a.createdAt]
11663
+ (id,work_id,node_id,type,name,logical_path,content_ref,content_kind,ordinal,created_at,content_edited_at)
11664
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT (id) DO NOTHING`,
11665
+ [a.id, a.workId, a.nodeId, a.type, a.name, a.logicalPath, a.contentRef, a.contentKind, a.ordinal, a.createdAt, a.contentEditedAt ?? null]
11623
11666
  );
11624
11667
  }
11625
11668
  break;
@@ -12620,7 +12663,7 @@ var init_work = __esm({
12620
12663
  workContentEdited = {
12621
12664
  name: "work/content-edited",
12622
12665
  kind: "work.content_edited",
12623
- apply(e, state) {
12666
+ apply(e, state, ctx) {
12624
12667
  const work = state.work(e.workId);
12625
12668
  if (!work) return;
12626
12669
  const node2 = state.node(work.nodeId);
@@ -12628,7 +12671,7 @@ var init_work = __esm({
12628
12671
  const artifacts = state.artifactsOf(e.workId);
12629
12672
  const target = artifacts.find((a) => a.id === e.artifactId);
12630
12673
  if (!target || target.contentRef !== e.previousRef || target.contentKind === "external") return;
12631
- state.replaceArtifacts(e.workId, artifacts.map((a) => a.id === target.id ? { ...a, contentRef: e.contentRef } : a));
12674
+ state.replaceArtifacts(e.workId, artifacts.map((a) => a.id === target.id ? { ...a, contentRef: e.contentRef, contentEditedAt: ctx.at } : a));
12632
12675
  }
12633
12676
  };
12634
12677
  workConclude = {
@@ -28733,9 +28776,22 @@ var init_chat_item_ledger = __esm({
28733
28776
  ...deterministicId ? { deterministicId } : {}
28734
28777
  });
28735
28778
  }
28736
- /** 服务端合成的错误帧(session.done 抛 / exit 判失败)。不入正文,归 control。 */
28779
+ /**
28780
+ * 服务端合成的错误帧(session.done 抛 / exit 判失败)。不入正文,归 control。
28781
+ *
28782
+ * **返回 outcome 是为了让调用方把它推上 v3 流**(ADR-0510 D5/D6,2026-09-10 修)。
28783
+ * 在此之前这条行只落库、不发帧:客户端在这一轮里唯一收到的终态信号是 `emitV3TurnTerminal`
28784
+ * 发的 `turn_failed`,而那一帧不带任何正文、客户端的 `applyFrame` 又明说「不建 item、
28785
+ * 仅推进 frameSeq」——于是失败原因整条飞不到还开着的页面上,只有下一次 `/items` 快照
28786
+ * 才补得上。生产现场(v2.openfriday.ai,2026-09-10 05:39 那一轮):runtime 报
28787
+ * 「401 Authentication Fails」,库里三处都记着,页面上从头到尾是空的。
28788
+ *
28789
+ * 号取的是**建行时那两格钟**(`startedVersion` 给 wire 的 `started` 帧、`version` 给这一行
28790
+ * 本身的写),与 provider item 走 {@link ChatItemLedger.apply} 那条路逐字同构——
28791
+ * 快照与增量流因此还是同一套版本空间。
28792
+ */
28737
28793
  recordError(text5, extra) {
28738
- this.insert({
28794
+ const id = this.insert({
28739
28795
  kind: "control",
28740
28796
  role: "assistant",
28741
28797
  status: "failed",
@@ -28751,6 +28807,8 @@ var init_chat_item_ledger = __esm({
28751
28807
  ...extra?.detail ? { detail: extra.detail } : {}
28752
28808
  }
28753
28809
  });
28810
+ const row = this.rows.get(id);
28811
+ return { itemId: id, version: row.wireVersion, startedVersion: row.startedVersion, ord: row.ord };
28754
28812
  }
28755
28813
  /**
28756
28814
  * 回灌重启前已经落库的那段正文(`chat-recovery` 重挂 / 迟到对账用)。
@@ -29523,7 +29581,7 @@ async function sweepStaleChatTurns(deps) {
29523
29581
  `[chat-turn-sweep] \u8F6E\u6B21 ${turn.id}\uFF08\u4F1A\u8BDD ${turn.chatSessionId}\uFF09\u6536\u53E3\u4E3A ${outcome.status}\uFF08${outcome.reason}\uFF09\uFF0C\u4F1A\u8BDD\u69FD\u5DF2\u91CA\u653E`
29524
29582
  );
29525
29583
  const settledStatus = outcome.settled ? outcome.status : void 0;
29526
- if (deps.onTurnSettled && settledStatus && settledStatus !== "succeeded") {
29584
+ if (deps.onTurnSettled && settledStatus) {
29527
29585
  await deps.onTurnSettled({
29528
29586
  chatSessionId: turn.chatSessionId,
29529
29587
  turnId: turn.id,
@@ -159829,6 +159887,7 @@ var init_live_chat = __esm({
159829
159887
  },
159830
159888
  emitLive: (event) => this.emitLive(turn, event),
159831
159889
  applyNormalizedEvent: (event) => this.applyNormalizedEvent(turn, event),
159890
+ publishServerItem: (item) => this.publishServerItem(turn, item),
159832
159891
  subscribe: (sub) => {
159833
159892
  turn.subscribers.add(sub);
159834
159893
  return () => {
@@ -159975,6 +160034,45 @@ var init_live_chat = __esm({
159975
160034
  };
159976
160035
  this.publishV3Coalesced(turn, frame);
159977
160036
  }
160037
+ /**
160038
+ * 服务端合成 item → 两帧(`started` + 终态),与 {@link emitV3} 给 provider item 发的那两帧
160039
+ * **逐字同构**:先用建行预留的那一格发 `started`(客户端以它为该 item 首次亮相、payload 首次成形),
160040
+ * 再用这一行本身的写号发 `completed` / `failed`。
160041
+ *
160042
+ * 为什么不学 {@link publishUserItem} 用一帧 `set_text`:那条路是给「一次性成形的**正文**」用的,
160043
+ * 落在客户端桶里 `status` 恒为 `streaming`——用户那条消息无所谓,失败收尾的 control 行却必须是
160044
+ * 终态,否则这一轮在客户端看来永远没关。
160045
+ */
160046
+ publishServerItem(turn, item) {
160047
+ if (turn.status !== "running") return;
160048
+ const perItem = turn.v3Items.get(item.itemId) ?? { textOffset: 0, started: false };
160049
+ const ord = turn.items?.ordFor(item.itemId) ?? item.ord ?? void 0;
160050
+ const base = {
160051
+ protocolVersion: 3,
160052
+ streamId: turn.liveStreamId,
160053
+ turnId: this.wireTurnId(turn),
160054
+ itemId: item.itemId,
160055
+ itemType: item.itemType,
160056
+ ...ord !== void 0 ? { ord } : {},
160057
+ offset: 0
160058
+ };
160059
+ if (!perItem.started && item.startedVersion !== void 0) {
160060
+ this.publishV3Coalesced(turn, {
160061
+ ...base,
160062
+ operation: "started",
160063
+ itemVersion: item.startedVersion,
160064
+ payload: item.payload
160065
+ });
160066
+ perItem.started = true;
160067
+ }
160068
+ turn.v3Items.set(item.itemId, perItem);
160069
+ this.publishV3Coalesced(turn, {
160070
+ ...base,
160071
+ operation: item.status === "failed" ? "failed" : "completed",
160072
+ itemVersion: item.version,
160073
+ payload: item.payload
160074
+ });
160075
+ }
159978
160076
  assignV3Seq(turn, frame) {
159979
160077
  return { ...frame, epoch: turn.liveEpoch, seq: ++turn.v3Seq };
159980
160078
  }
@@ -160112,11 +160210,17 @@ var init_live_chat = __esm({
160112
160210
  const frame = {
160113
160211
  protocolVersion: 3,
160114
160212
  streamId: turn.liveStreamId,
160115
- turnId: turn.liveTurnId,
160213
+ // 账本轮次 id,与 item 帧同一套(#1249 把 emitV3 改成了 wireTurnId,收尾帧当时漏了)。
160214
+ // 客户端此前把收尾帧整条丢弃(`applyFrame`:「不建 item,仅推进 frameSeq」),所以这个
160215
+ // 号是对是错都看不出来;相位一挂上去它就得对——否则相位会记到一个桶里根本不存在的 turnId 上。
160216
+ turnId: this.wireTurnId(turn),
160116
160217
  itemId: `oasis-turn:${turn.liveEpoch}:${status}`,
160117
160218
  itemType: "control",
160118
160219
  operation,
160119
160220
  itemVersion: 0,
160221
+ // 相位被携带,不被推导(ADR-0510 D6):这一轮到底是成了还是败了,服务端此刻知道,
160222
+ // 客户端替换即可,不用再去把一轮里每条 item 的 status 取或来猜。
160223
+ phase: status === "done" ? "succeeded" : "failed",
160120
160224
  epoch: turn.liveEpoch,
160121
160225
  seq: ++turn.v3Seq
160122
160226
  };
@@ -162347,6 +162451,28 @@ var init_planner = __esm({
162347
162451
  });
162348
162452
 
162349
162453
  // ../server/src/domains/collab/workorders.ts
162454
+ function systemActorFallback(id) {
162455
+ return isSystemActorId(id) ? { name: SYSTEM_ACTOR_DISPLAY_NAME, avatar: SYSTEM_AVATAR_REF } : void 0;
162456
+ }
162457
+ function defaultAvatarRefOf(id, kind) {
162458
+ if (kind === "system" || isSystemActorId(id)) return SYSTEM_AVATAR_REF;
162459
+ if (kind === "human" || isHumanActorId(id)) return defaultHumanAvatarRef(id);
162460
+ return void 0;
162461
+ }
162462
+ function actorRefFieldsOf(a) {
162463
+ const avatar = a.avatar ?? defaultAvatarRefOf(a.id, a.kind);
162464
+ return {
162465
+ name: a.name,
162466
+ ...a.roles[0] ? { role: a.roles[0] } : {},
162467
+ ...avatar ? { avatar } : {}
162468
+ };
162469
+ }
162470
+ function actorRefFallbackOf(id) {
162471
+ const system = systemActorFallback(id);
162472
+ if (system) return system;
162473
+ const avatar = defaultAvatarRefOf(id);
162474
+ return avatar ? { avatar } : void 0;
162475
+ }
162350
162476
  function overviewRootIdOf(arts) {
162351
162477
  const roots = arts.filter((a) => a.inputs.length === 0);
162352
162478
  if (roots.length <= 1) return roots[0]?.id;
@@ -162607,6 +162733,7 @@ var init_workorders = __esm({
162607
162733
  "../server/src/domains/collab/workorders.ts"() {
162608
162734
  "use strict";
162609
162735
  init_src5();
162736
+ init_src();
162610
162737
  init_node_state();
162611
162738
  init_planner();
162612
162739
  init_workorder_detail();
@@ -196005,11 +196132,12 @@ var init_service3 = __esm({
196005
196132
  );
196006
196133
  }
196007
196134
  }
196008
- async settleFromSweep(childSessionId, reason, companyId) {
196135
+ async settleFromSweep(childSessionId, reason, companyId, opts) {
196009
196136
  const store = await this.options.resolveStore(companyId).catch(() => null);
196010
196137
  if (!store) return null;
196011
196138
  const record8 = await store.getByChildSession(childSessionId).catch(() => null);
196012
196139
  if (!record8 || record8.state !== "running") return record8;
196140
+ if (opts?.outcome === "succeeded") return this.settleSucceededFromSweep(record8, companyId);
196013
196141
  const at = this.now();
196014
196142
  const settledSeqAtRound = record8.settledSeq + 1;
196015
196143
  const newSummary = {
@@ -196029,6 +196157,58 @@ var init_service3 = __esm({
196029
196157
  if (settled) await this.notifySettled(settled, companyId);
196030
196158
  return settled;
196031
196159
  }
196160
+ /**
196161
+ * 清扫拍把这一轮收成 **succeeded** 时的收口(2026-09-10 生产实测的那个洞)。
196162
+ *
196163
+ * 什么时候会走到这里:`settleRound` 是**派发那一刻建的闭包**,serve 一重启它就随老进程消失,
196164
+ * 这一轮改由 `chat-recovery` 重挂、跑完、把 assistant 行写成 done;随后清扫拍按「行已终态」
196165
+ * 把轮次收成 succeeded。此前这一支不外报,于是**没有任何人**去记这一轮、判终态、通知父会话、
196166
+ * 把产物搬回主会话工作区——专家干完了,页面上却永远转圈。
196167
+ * 现场:委派 e6196ab7 第 6 轮 04:02 写完 `outputs/org-collaboration-relationship.md` 并收尾,
196168
+ * 轮次 04:03:30 succeeded,台账仍是 `running`/5 轮、`settled_at` 空。
196169
+ *
196170
+ * 与失败那一支的差别只有三处,其余(追加式记账、`settledSeq`、唯一出口 `notifySettled`)逐字相同:
196171
+ * ① 摘要正文取**子会话这一轮真正的回答**,不是「被时钟闭合」那句;
196172
+ * ② 收成 `done`,不带 `failureKind`,`consecutiveFails` 归零;
196173
+ * ③ 多一道幂等闸,见下。
196174
+ *
196175
+ * **幂等闸**:`roundSummaries.length > dispatchSeq` 就说明这一轮 `settleRound` 已经记过了
196176
+ * (第 N 轮用的是 `dispatchSeq = N-1`),直接收手。没有它,「本进程没重启、`settleRound`
196177
+ * 正在后台跑收尾」与这一拍撞车时会把同一轮记两遍——这正是这一支原先被整个关掉的理由,
196178
+ * 现在用一道显式的闸换掉那个一刀切。
196179
+ */
196180
+ async settleSucceededFromSweep(record8, companyId) {
196181
+ const store = await this.options.resolveStore(companyId).catch(() => null);
196182
+ if (!store) return record8;
196183
+ const roundsRecorded = record8.roundSummaries.length;
196184
+ const thisRound = (record8.dispatchSeq ?? 0) + 1;
196185
+ if (roundsRecorded >= thisRound) return record8;
196186
+ const at = this.now();
196187
+ const text5 = await this.readChildRoundAnswer(record8, thisRound, companyId);
196188
+ const settledSeqAtRound = record8.settledSeq + 1;
196189
+ const newSummary = {
196190
+ round: (record8.roundSummaries[record8.roundSummaries.length - 1]?.round ?? 0) + 1,
196191
+ text: text5 || "\u8FD9\u4E00\u8F6E\u5DF2\u5B8C\u6210\uFF08\u6536\u5C3E\u65F6\u6B63\u6587\u6CA1\u8BFB\u5230\uFF0C\u4EA7\u7269\u4EE5\u5DE5\u4F5C\u533A\u91CC\u7684\u6587\u4EF6\u4E3A\u51C6\uFF09\u3002",
196192
+ at,
196193
+ settledSeq: settledSeqAtRound
196194
+ };
196195
+ const settled = await store.appendRoundSummary(record8.id, newSummary, {
196196
+ state: "done",
196197
+ consecutiveFails: 0,
196198
+ settledSeq: settledSeqAtRound,
196199
+ settledAt: at
196200
+ }).catch(() => null);
196201
+ if (settled) await this.notifySettled(settled, companyId);
196202
+ return settled;
196203
+ }
196204
+ /** 子会话第 `round` 轮那条回答行的正文(`childRoundMessageIds` 是唯一的 id 口径)。 */
196205
+ async readChildRoundAnswer(record8, round, companyId) {
196206
+ const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
196207
+ if (!chatStore?.listMessages) return "";
196208
+ const { placeholderId } = childRoundMessageIds(record8.childSessionId, round);
196209
+ const messages = await chatStore.listMessages(record8.childSessionId).catch(() => []);
196210
+ return (messages.find((m2) => m2.id === placeholderId)?.content ?? "").trim();
196211
+ }
196032
196212
  /**
196033
196213
  * 队列里还有话没送到专家眼前 → 起下一轮,**把队列内容拼进 message**(§6.2.1 第二条路径)。
196034
196214
  *
@@ -198889,8 +199069,11 @@ var init_github = __esm({
198889
199069
  sessionEnv.set("GH_CONFIG_DIR", configDir);
198890
199070
  const appSlug = credentials["app_slug"];
198891
199071
  if (appSlug) sessionEnv.set("GH_APP_SLUG", appSlug);
198892
- const botEmail = credentials["app_bot_email"] ?? "";
198893
- const botName = credentials["app_bot_name"] ?? "";
199072
+ const rawBotEmail = credentials["app_bot_email"] ?? "";
199073
+ const rawBotName = credentials["app_bot_name"] ?? "";
199074
+ const staleBot = Boolean(appSlug && rawBotEmail && !rawBotEmail.includes(`+${appSlug}[bot]@`));
199075
+ const botEmail = staleBot ? "" : rawBotEmail;
199076
+ const botName = staleBot ? appSlug ? `${appSlug}[bot]` : "" : rawBotName;
198894
199077
  const signName = botName || name;
198895
199078
  sessionEnv.set("GIT_AUTHOR_NAME", signName);
198896
199079
  sessionEnv.set("GIT_AUTHOR_EMAIL", botEmail);
@@ -201180,10 +201363,8 @@ async function adoptGithubApp(store, args, deps = {}) {
201180
201363
  await put3("GITHUB_APP_TOKEN_EXPIRES_AT", String(minted.expiresAtMs), false);
201181
201364
  if (args.slug) {
201182
201365
  const bot = await fetchAppBotIdentity(args.slug, { token: minted.token }, deps);
201183
- if (bot) {
201184
- await put3("GITHUB_APP_BOT_EMAIL", bot.email, false);
201185
- await put3("GITHUB_APP_BOT_NAME", bot.login, false);
201186
- }
201366
+ await put3("GITHUB_APP_BOT_EMAIL", bot?.email ?? "", false);
201367
+ await put3("GITHUB_APP_BOT_NAME", bot?.login ?? `${args.slug}[bot]`, false);
201187
201368
  }
201188
201369
  return {
201189
201370
  account: args.slug ? `${args.slug}[bot]` : args.appId,
@@ -201851,7 +202032,7 @@ async function draftActorResolver(registry2) {
201851
202032
  const byId = new Map(actors.map((a) => [a.id, a]));
201852
202033
  return (id) => {
201853
202034
  const a = byId.get(id);
201854
- return a ? { name: a.name, ...a.roles[0] ? { role: a.roles[0] } : {}, ...a.avatar ? { avatar: a.avatar } : {} } : void 0;
202035
+ return a ? actorRefFieldsOf(a) : actorRefFallbackOf(id);
201855
202036
  };
201856
202037
  }
201857
202038
  async function draftRoleStaffingResolver(registry2) {
@@ -203083,18 +203264,27 @@ async function runView(kernel, oplog, engineStore, blobs, name, artifactId, para
203083
203264
  );
203084
203265
  const pendingGate = pendingConcludeAttempt(model, id);
203085
203266
  const rejectionByRevision = /* @__PURE__ */ new Map();
203267
+ const contentEditedByRevision = /* @__PURE__ */ new Map();
203086
203268
  if (engineStore) {
203087
203269
  const snap = await engineStore.transaction((tx) => tx.loadWorkorder(artifact.workspace));
203088
203270
  for (const w2 of snap?.works ?? []) {
203089
203271
  if (w2.rejectedReason) rejectionByRevision.set(w2.id, w2.rejectedReason);
203090
203272
  }
203273
+ for (const a of snap?.artifacts ?? []) {
203274
+ const at = a.contentEditedAt;
203275
+ if (!at) continue;
203276
+ const workId = a.workId;
203277
+ const prev = contentEditedByRevision.get(workId);
203278
+ if (!prev || at > prev) contentEditedByRevision.set(workId, at);
203279
+ }
203091
203280
  }
203092
203281
  const revisionViews = revisions.map((rev) => ({
203093
203282
  ...rev,
203094
203283
  stage: revisionStage(artifact, rev),
203095
203284
  approvals: (model.reviews.get(rev.id) ?? []).filter((v2) => v2.verdict === "approve").length,
203096
203285
  required: pendingGate?.head === rev.id ? pendingGate.gate?.quorum ?? 0 : 0,
203097
- ...rejectionByRevision.has(rev.id) ? { rejection: { reason: rejectionByRevision.get(rev.id) } } : {}
203286
+ ...rejectionByRevision.has(rev.id) ? { rejection: { reason: rejectionByRevision.get(rev.id) } } : {},
203287
+ ...contentEditedByRevision.has(rev.id) ? { contentEditedAt: contentEditedByRevision.get(rev.id) } : {}
203098
203288
  }));
203099
203289
  return {
203100
203290
  artifact,
@@ -206179,10 +206369,26 @@ ${composed}`;
206179
206369
  ...chatExit?.reason ? { reason: chatExit.reason } : {},
206180
206370
  ...chatExit?.detail ? { detail: chatExit.detail } : {}
206181
206371
  });
206182
- itemLedger.recordError(errText2, {
206372
+ const errorOutcome = itemLedger.recordError(errText2, {
206183
206373
  ...chatExit?.reason ? { reason: chatExit.reason } : {},
206184
206374
  ...chatExit?.detail ? { detail: chatExit.detail } : {}
206185
206375
  });
206376
+ if (live) {
206377
+ live.publishServerItem({
206378
+ itemId: errorOutcome.itemId,
206379
+ itemType: "control",
206380
+ startedVersion: errorOutcome.startedVersion,
206381
+ version: errorOutcome.version,
206382
+ ord: errorOutcome.ord,
206383
+ status: "failed",
206384
+ payload: {
206385
+ code: "error",
206386
+ note: errText2,
206387
+ ...chatExit?.reason ? { reason: chatExit.reason } : {},
206388
+ ...chatExit?.detail ? { detail: chatExit.detail } : {}
206389
+ }
206390
+ });
206391
+ }
206186
206392
  }
206187
206393
  finished = true;
206188
206394
  clearInterval(ckptTimer);
@@ -206795,7 +207001,19 @@ ${composed}`;
206795
207001
  fromInstallation: Boolean(installationId),
206796
207002
  /** App 申请了、但这次安装还没被授予——多半是权限升级还等着组织 owner 批准。 */
206797
207003
  awaitingApproval: missingAppPermissions(effective).filter((m2) => meta.permissions[m2.key]).map((m2) => m2.key),
206798
- granted: GITHUB_APP_PERMISSION_LABELS.filter((r) => effective[r.key]),
207004
+ /**
207005
+ * 这次安装**真正被授予**的全部权限,一律用 GitHub 的英文原名 + 级别
207006
+ * (2026-09-10 发起人:「所有权限都用英文原文,不区分是不是八项权限」)。
207007
+ *
207008
+ * 此前这里是 `GITHUB_APP_PERMISSION_LABELS.filter(...)` —— 那张表只有本仓必需的
207009
+ * 八项中文标签,于是这一格实际上是「必备清单打勾」,人在 GitHub 上另外勾的一个都不显示,
207010
+ * 看上去像没生效。中间还试过「八项中文 + 额外项英文」两段式,也被否了:
207011
+ * 同一个列表两套命名,反而要人先分辨「这条为什么是中文那条为什么是英文」。
207012
+ *
207013
+ * **不编中文名**:GitHub 的权限键是开放集合且还在增,写死对照表迟早漏;
207014
+ * 而英文原名与 GitHub 设置页上的字**逐字一致**,对着改权限时不用做心理翻译。
207015
+ */
207016
+ granted: Object.entries(effective).filter(([, level]) => Boolean(level)).map(([key, level]) => ({ key, level: String(level) })).sort((a, b2) => a.key.localeCompare(b2.key)),
206799
207017
  missing,
206800
207018
  settingsUrl: meta.slug ? appPermissionSettingsUrl(meta) : null
206801
207019
  }));
@@ -206871,7 +207089,8 @@ ${composed}`;
206871
207089
  const perms = pending.permissions ?? {};
206872
207090
  const missing = missingAppPermissions(perms);
206873
207091
  return {
206874
- granted: GITHUB_APP_PERMISSION_LABELS.filter((r) => perms[r.key]),
207092
+ // app/permissions 那一处同口径:英文原名 + 级别,不分是不是本仓必需的八项。
207093
+ granted: Object.entries(perms).filter(([, level]) => Boolean(level)).map(([key, level]) => ({ key, level: String(level) })).sort((a, b2) => a.key.localeCompare(b2.key)),
206875
207094
  missing,
206876
207095
  ...pending.slug ? { settingsUrl: appPermissionSettingsUrl({
206877
207096
  slug: pending.slug,
@@ -206914,10 +207133,8 @@ ${composed}`;
206914
207133
  }
206915
207134
  if (pending.slug) {
206916
207135
  const bot = await fetchAppBotIdentity(pending.slug, { token: minted.token });
206917
- if (bot) {
206918
- await put3("GITHUB_APP_BOT_EMAIL", bot.email, false);
206919
- await put3("GITHUB_APP_BOT_NAME", bot.login, false);
206920
- }
207136
+ await put3("GITHUB_APP_BOT_EMAIL", bot?.email ?? "", false);
207137
+ await put3("GITHUB_APP_BOT_NAME", bot?.login ?? `${pending.slug}[bot]`, false);
206921
207138
  }
206922
207139
  pending.done = true;
206923
207140
  delete pending.privateKeyPem;
@@ -207078,7 +207295,16 @@ ${composed}`;
207078
207295
  body: JSON.stringify({ client_id: GH_CLIENT_ID, scope: "repo,read:user,read:org" })
207079
207296
  });
207080
207297
  const d = await dr.json();
207081
- send({ type: "code", code: d.user_code, url: d.verification_uri });
207298
+ const verifyUrl = (() => {
207299
+ try {
207300
+ const u = new URL(d.verification_uri);
207301
+ u.searchParams.set("user_code", d.user_code);
207302
+ return u.toString();
207303
+ } catch {
207304
+ return d.verification_uri;
207305
+ }
207306
+ })();
207307
+ send({ type: "code", code: d.user_code, url: verifyUrl });
207082
207308
  const deadline = Date.now() + d.expires_in * 1e3;
207083
207309
  while (!closed && Date.now() < deadline) {
207084
207310
  await new Promise((r) => setTimeout(r, d.interval * 1e3));
@@ -207193,7 +207419,7 @@ ${composed}`;
207193
207419
  * **接进既有那一拍,不新写一套扫描**——新写的那套迟早和既有判据漂移。
207194
207420
  * 委派域没装配时返回 null,调用方照常。
207195
207421
  */
207196
- settleDelegationFromSweep: (childSessionId, reason, companyId) => delegationService?.settleFromSweep(childSessionId, reason, companyId) ?? Promise.resolve(null),
207422
+ settleDelegationFromSweep: (childSessionId, reason, companyId, opts2) => delegationService?.settleFromSweep(childSessionId, reason, companyId, opts2) ?? Promise.resolve(null),
207197
207423
  close: async () => {
207198
207424
  if (broadcastTimer) clearInterval(broadcastTimer);
207199
207425
  if (pendingSystemNoticesRecoveryTimer) clearInterval(pendingSystemNoticesRecoveryTimer);
@@ -210562,6 +210788,10 @@ function buildRevisionDag(revisions) {
210562
210788
  }))
210563
210789
  };
210564
210790
  }
210791
+ function laterOf(a, b2) {
210792
+ if (!b2) return a;
210793
+ return b2 > a ? b2 : a;
210794
+ }
210565
210795
  var DEFAULT_AGENT_ACTOR, MemoryProjectStateStore, MemoryArtifactStateStore, ProjectsService;
210566
210796
  var init_service4 = __esm({
210567
210797
  "../server/src/domains/projects/service.ts"() {
@@ -211418,7 +211648,11 @@ var init_service4 = __esm({
211418
211648
  if (arts.length === 0) continue;
211419
211649
  const work = worksById.get(acceptedId);
211420
211650
  const folderPath = `wo:${workOrderId}/node:${node2.id}`;
211421
- const folderUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
211651
+ const workUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
211652
+ const folderUpdatedAt = arts.reduce(
211653
+ (acc, a) => laterOf(acc, a.contentEditedAt),
211654
+ workUpdatedAt
211655
+ );
211422
211656
  const creatorId = work?.assigneeActorId ?? null;
211423
211657
  const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
211424
211658
  const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
@@ -211442,7 +211676,7 @@ var init_service4 = __esm({
211442
211676
  for (const a of sortedArts) {
211443
211677
  const artifactId = node2.id;
211444
211678
  const artifactType = a.type ?? node2.type;
211445
- const rowUpdatedAt = folderUpdatedAt;
211679
+ const rowUpdatedAt = laterOf(workUpdatedAt, a.contentEditedAt);
211446
211680
  if (a.contentKind === "manifest" && a.contentRef?.trim()) {
211447
211681
  const content3 = await this.readContentRefText(a.contentRef);
211448
211682
  const files = content3 !== null ? tryParseManifestFiles(content3) : void 0;
@@ -211838,6 +212072,7 @@ var init_service4 = __esm({
211838
212072
  const arts = snap.artifacts.filter((a) => a.workId === acceptedId);
211839
212073
  if (arts.length === 0) continue;
211840
212074
  const work = worksById.get(acceptedId);
212075
+ const workUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
211841
212076
  const documents = arts.slice().sort((a, b2) => a.ordinal - b2.ordinal).map((a) => ({
211842
212077
  // collab 层 artifactId === engine nodeId(见 bridge-readmodel.ts:215)——预览端读的
211843
212078
  // useArtifactView 也走这个 id。
@@ -211849,7 +212084,9 @@ var init_service4 = __esm({
211849
212084
  contentRef: a.contentRef,
211850
212085
  title: node2.title || a.name,
211851
212086
  // 节点标题优先;否则用 engine artifact 的 "output" 兜底
211852
- contentKind: engineContentKind(a.contentKind)
212087
+ contentKind: engineContentKind(a.contentKind),
212088
+ // 文档级时间:被人在预览里就地改写过就跳到改写时刻,否则就是这轮执行的时刻。
212089
+ updatedAt: laterOf(workUpdatedAt, a.contentEditedAt)
211853
212090
  }));
211854
212091
  out.push({
211855
212092
  outputId: `engine:${acceptedId}`,
@@ -211860,7 +212097,9 @@ var init_service4 = __esm({
211860
212097
  documents,
211861
212098
  payload: {},
211862
212099
  createdAt: work?.createdAt ?? node2.createdAt,
211863
- updatedAt: work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt
212100
+ // 节点(= 文件夹行)的时间取「这轮执行的时刻」与「其下任一文档最后被就地改写的时刻」里更晚的那个:
212101
+ // 改过某个文件之后,文件夹不能还显示比自己的孩子更早的时间。
212102
+ updatedAt: documents.reduce((acc, d) => laterOf(acc, d.updatedAt), workUpdatedAt)
211864
212103
  });
211865
212104
  }
211866
212105
  return out;
@@ -222932,7 +223171,8 @@ function toApiNodeOutputDocument(doc) {
222932
223171
  summary: doc.summary,
222933
223172
  ...doc.contentKind !== void 0 ? { content_kind: doc.contentKind } : {},
222934
223173
  ...doc.size !== void 0 ? { size: doc.size } : {},
222935
- ...doc.files !== void 0 ? { files: doc.files.map((f2) => ({ path: f2.path, ...f2.size !== void 0 ? { size: f2.size } : {} })) } : {}
223174
+ ...doc.files !== void 0 ? { files: doc.files.map((f2) => ({ path: f2.path, ...f2.size !== void 0 ? { size: f2.size } : {} })) } : {},
223175
+ ...doc.updatedAt !== void 0 ? { updated_at: doc.updatedAt } : {}
222936
223176
  };
222937
223177
  }
222938
223178
  function toApiNodeOutputValidation(validation) {
@@ -226529,12 +226769,16 @@ function buildWorkorderActivity(input) {
226529
226769
  const reasonKinds = new Set(reasons.map((r) => String(r.kind)));
226530
226770
  const executorId = w2?.assigneeActorId ?? rec.actorId;
226531
226771
  if (isPendingActor(executorId)) break;
226532
- const status = reasonKinds.has("rework") ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : reasonKinds.has("issue") ? ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
226533
- const drivingIssueId = str4(reasons.find((r) => r.kind === "issue")?.issueId);
226534
- if (drivingIssueId) {
226535
- workOfIssue.set(drivingIssueId, workId);
226536
- const ic = plainCommentIssueIds.has(drivingIssueId) ? issueCards.get(drivingIssueId) : void 0;
226537
- const commentAuthor = issueById.get(drivingIssueId)?.authorActorId;
226772
+ const forcedBy = str4(reasons.find((r) => r.kind === "forced")?.actorId);
226773
+ const gapDriven = reasons.some((r) => r.kind === "issue" && r.issueKind === "gap");
226774
+ const status = reasonKinds.has("rework") ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : gapDriven ? ACTIVITY_COPY.gapReworked(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("issue") ? ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : forcedBy ? ACTIVITY_COPY.forcedRerun(nameOf(forcedBy), nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
226775
+ for (const r of reasons) {
226776
+ if (r.kind !== "issue") continue;
226777
+ const issueId = str4(r.issueId);
226778
+ if (!issueId) continue;
226779
+ workOfIssue.set(issueId, workId);
226780
+ const ic = plainCommentIssueIds.has(issueId) ? issueCards.get(issueId) : void 0;
226781
+ const commentAuthor = issueById.get(issueId)?.authorActorId;
226538
226782
  if (ic && commentAuthor) {
226539
226783
  touch(ic, rec);
226540
226784
  ic.executorId = executorId;
@@ -227091,6 +227335,22 @@ var init_activity2 = __esm({
227091
227335
  reworking: (node2) => `\u6B63\u5728\u6839\u636E\u5BA1\u6838\u4E0D\u901A\u8FC7\u539F\u56E0\u4FEE\u6539\u300A${node2}\u300B\u3002`,
227092
227336
  specChanged: (node2) => `\u6B63\u5728\u6839\u636E\u4EFB\u52A1\u8BF4\u660E\u7684\u66F4\u65B0\u8C03\u6574\u300A${node2}\u300B\u3002`,
227093
227337
  issueDriven: (node2) => `\u6B63\u5728\u6839\u636E\u53CD\u9988\u8C03\u6574\u300A${node2}\u300B\u3002`,
227338
+ /**
227339
+ * 缺口解开后**带着答复重开工**的那一轮(`reasons` 里有 `{kind:"issue", issueKind:"gap"}`)。
227340
+ *
227341
+ * 与 {@link issueDriven} 分开:那句说的是「按反馈调整」,而这一轮的事实是**卡点解除、重新开始**
227342
+ * ——中断链上最该看清的一拍。此前两者共用一句,恢复后的那一轮被说回泛泛的「正在根据反馈调整」。
227343
+ * 实测 next 213 次 / 23 单、v2 4 次,最近一次 2026-09-09(`issue` 里第二多的一档)。
227344
+ */
227345
+ gapReworked: (executor, node2) => `\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C${executor} \u5E26\u7740\u7B54\u590D\u91CD\u65B0\u5F00\u5DE5\u300A${node2}\u300B\u3002`,
227346
+ /**
227347
+ * 人点了 UI 的「重新运行」(`kernel-bridge.rerunNode`,ADR 0137)——`reasons` 里那条
227348
+ * `{kind:"forced", actorId}` 就是点的人。
227349
+ *
227350
+ * 此前没有自己的文案、落到 `working` 说成「正在处理《X》。」,**页面上完全看不出这一轮是
227351
+ * 有人手动强推的**。实测两条生产线合计 80 次(next 73 / v2 7,最近一次 2026-09-09)。
227352
+ */
227353
+ forcedRerun: (by, node2) => `${by} \u91CD\u65B0\u8FD0\u884C\u4E86\u300A${node2}\u300B\u3002`,
227094
227354
  /* 自动判定的那一刻**只说事实,不断言谁介入了**。
227095
227355
  `work.timeout` 由引擎发(`engine/activate.ts` 与 `server/run-settlement.ts`),失败后的重试
227096
227356
  也是引擎做的(`activate.ts` 的 `tryToRun`)——管理者根本不知道这两件事,此前却写成
@@ -228289,6 +228549,21 @@ function nodeIdOfWorkId(workId) {
228289
228549
  const nodeId = body2.slice(0, cut);
228290
228550
  return nodeId.startsWith("artifact:") ? nodeId : null;
228291
228551
  }
228552
+ function nodeIdOfIssueId(issueId, model) {
228553
+ if (!issueId) return null;
228554
+ const anchored = model.annotations.get(issueId)?.anchor.target;
228555
+ if (anchored) {
228556
+ if (anchored.startsWith("artifact:")) return anchored;
228557
+ const fromWork = nodeIdOfWorkId(anchored);
228558
+ if (fromWork) return fromWork;
228559
+ }
228560
+ const body2 = issueId.replace(/^(?:ann:rework:)?(?:gap:|esc:|ann:)/, "");
228561
+ if (!body2.startsWith("artifact:")) return null;
228562
+ const cut = body2.lastIndexOf(":");
228563
+ if (cut <= 0) return null;
228564
+ const nodeId = body2.slice(0, cut);
228565
+ return nodeId.startsWith("artifact:") ? nodeId : null;
228566
+ }
228292
228567
  function eventToAuditEntry(rec) {
228293
228568
  const ev = rec.event;
228294
228569
  const artifactId = rec.nodeId ?? nodeIdOfWorkId(rec.workId) ?? "";
@@ -228301,20 +228576,25 @@ function eventToAuditEntry(rec) {
228301
228576
  summary: `${KIND_LABEL2[ev.kind] ?? ev.kind}${artifactId ? ` \xB7 ${artifactId}` : ""}`
228302
228577
  };
228303
228578
  }
228304
- function enrichAudit(entry, model, titleOf, workorderId) {
228305
- if (!entry.artifactId) {
228306
- const workspaceTitle2 = workorderId ? titleOf.get(workorderId) : void 0;
228307
- return { ...entry, ...workspaceTitle2 ? { workspaceTitle: workspaceTitle2 } : {}, visibility: "ok" };
228308
- }
228309
- const a = model.artifacts.get(entry.artifactId);
228310
- if (!a) return { ...entry, visibility: "deleted" };
228311
- const workspaceTitle = titleOf.get(a.workspace);
228312
- return {
228313
- ...entry,
228314
- nodeName: nodeLabel(a),
228315
- ...workspaceTitle ? { workspaceTitle } : {},
228316
- visibility: "ok"
228579
+ function auditAnchorsOf(rec) {
228580
+ const issueId = rec.event.issueId;
228581
+ return { workorderId: rec.workorderId, ...typeof issueId === "string" && issueId ? { issueId } : {} };
228582
+ }
228583
+ function enrichAudit(entry, model, titleOf, anchors = {}) {
228584
+ const withNode = (id, a) => {
228585
+ const workspaceTitle2 = titleOf.get(a.workspace);
228586
+ return { ...entry, artifactId: id, nodeName: nodeLabel(a), ...workspaceTitle2 ? { workspaceTitle: workspaceTitle2 } : {}, visibility: "ok" };
228317
228587
  };
228588
+ if (entry.artifactId) {
228589
+ const a = model.artifacts.get(entry.artifactId);
228590
+ if (!a) return { ...entry, visibility: "deleted" };
228591
+ return withNode(entry.artifactId, a);
228592
+ }
228593
+ const viaIssue = nodeIdOfIssueId(anchors.issueId, model);
228594
+ const viaIssueArtifact = viaIssue ? model.artifacts.get(viaIssue) : void 0;
228595
+ if (viaIssue && viaIssueArtifact) return withNode(viaIssue, viaIssueArtifact);
228596
+ const workspaceTitle = anchors.workorderId ? titleOf.get(anchors.workorderId) : void 0;
228597
+ return { ...entry, ...workspaceTitle ? { workspaceTitle } : {}, visibility: "ok" };
228318
228598
  }
228319
228599
  async function buildAudit(source, q2 = {}, model) {
228320
228600
  const limit = Math.min(Math.max(q2.limit ?? 100, 1), 500);
@@ -228323,8 +228603,8 @@ async function buildAudit(source, q2 = {}, model) {
228323
228603
  const items = [];
228324
228604
  let lastCursor = null;
228325
228605
  let truncated = false;
228326
- const push2 = (entry, cursor, workorderId) => {
228327
- items.push(model && titleOf ? enrichAudit(entry, model, titleOf, workorderId) : entry);
228606
+ const push2 = (entry, cursor, anchors) => {
228607
+ items.push(model && titleOf ? enrichAudit(entry, model, titleOf, anchors) : entry);
228328
228608
  lastCursor = cursor;
228329
228609
  };
228330
228610
  const match = (actor, kind, at) => {
@@ -228341,7 +228621,7 @@ async function buildAudit(source, q2 = {}, model) {
228341
228621
  for (const rec of records) {
228342
228622
  const ev = rec.event;
228343
228623
  if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
228344
- collected.push({ entry: eventToAuditEntry(rec), workorderId: rec.workorderId });
228624
+ collected.push({ entry: eventToAuditEntry(rec), anchors: auditAnchorsOf(rec) });
228345
228625
  }
228346
228626
  } else if (source.oplog) {
228347
228627
  const src = source.oplog.readFilteredOps ? source.oplog.readFilteredOps({ ...q2.actor ? { actor: q2.actor } : {}, ...q2.kind ? { kind: q2.kind } : {} }, void 0, { latest: true }) : source.oplog.readAll();
@@ -228352,7 +228632,7 @@ async function buildAudit(source, q2 = {}, model) {
228352
228632
  }
228353
228633
  const tail = collected.slice(-limit).reverse();
228354
228634
  return {
228355
- items: model && titleOf ? tail.map(({ entry, workorderId }) => enrichAudit(entry, model, titleOf, workorderId)) : tail.map(({ entry }) => entry),
228635
+ items: model && titleOf ? tail.map(({ entry, anchors }) => enrichAudit(entry, model, titleOf, anchors)) : tail.map(({ entry }) => entry),
228356
228636
  cursor: null
228357
228637
  };
228358
228638
  }
@@ -228361,7 +228641,7 @@ async function buildAudit(source, q2 = {}, model) {
228361
228641
  for (const rec of records) {
228362
228642
  const ev = rec.event;
228363
228643
  if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
228364
- push2(eventToAuditEntry(rec), String(rec.seq), rec.workorderId);
228644
+ push2(eventToAuditEntry(rec), String(rec.seq), auditAnchorsOf(rec));
228365
228645
  if (items.length >= limit) {
228366
228646
  truncated = true;
228367
228647
  break;
@@ -229249,11 +229529,7 @@ async function buildResolver(registry2) {
229249
229529
  const byId = new Map(actors.map((a) => [a.id, a]));
229250
229530
  return (id) => {
229251
229531
  const a = byId.get(id);
229252
- return a ? {
229253
- name: a.name,
229254
- ...a.roles[0] ? { role: a.roles[0] } : {},
229255
- ...a.avatar ? { avatar: a.avatar } : {}
229256
- } : void 0;
229532
+ return a ? actorRefFieldsOf(a) : actorRefFallbackOf(id);
229257
229533
  };
229258
229534
  }
229259
229535
  async function buildProjectResolver(artifacts) {
@@ -233656,12 +233932,20 @@ function createChatSessionsDomain(opts) {
233656
233932
  const limitRaw = Number(req.query?.get("limit") ?? "0") || 0;
233657
233933
  const limit = limitRaw > 0 ? Math.min(limitRaw, MAX_SNAPSHOT_ITEMS) : DEFAULT_SNAPSHOT_ITEMS;
233658
233934
  const cursor = opts.liveChat?.cursorFor(chatSessionId) ?? null;
233659
- let turnId = cursor?.turnId ?? null;
233660
- if (!turnId) {
233661
- const turns = await turnsFor(req);
233662
- const active = turns ? await turns.activeTurn(chatSessionId).catch(() => null) : null;
233663
- turnId = active?.id ?? null;
233664
- }
233935
+ const turns = await turnsFor(req);
233936
+ const activeTurnRow = turns ? await turns.activeTurn(chatSessionId).catch(() => null) : null;
233937
+ const turnId = cursor?.turnId ?? activeTurnRow?.id ?? null;
233938
+ const activeTurn = (() => {
233939
+ if (!activeTurnRow) return null;
233940
+ const settled = activeTurnRow.status !== "reserved" && activeTurnRow.status !== "running";
233941
+ if (settled) return { turnId: activeTurnRow.id, phase: activeTurnRow.status };
233942
+ if (opts.liveChat?.isRunning(chatSessionId)) {
233943
+ return { turnId: activeTurnRow.id, phase: "running" };
233944
+ }
233945
+ const startedAt = Date.parse(activeTurnRow.startedAt ?? activeTurnRow.reservedAt);
233946
+ const stale = Number.isFinite(startedAt) && Date.now() - startedAt >= LINK_WINDOWS.chatTurnPhaseStaleAfterMs;
233947
+ return { turnId: activeTurnRow.id, phase: stale ? "interrupted" : "running" };
233948
+ })();
233665
233949
  const snapshotEntries = [];
233666
233950
  let nextSinceVersion = sinceVersion;
233667
233951
  let hasMoreBefore = false;
@@ -233693,6 +233977,7 @@ function createChatSessionsDomain(opts) {
233693
233977
  nextSinceVersion,
233694
233978
  frameCursor: cursor ? { epoch: cursor.epoch, seq: cursor.seq } : { epoch: 0, seq: 0 },
233695
233979
  canAppend: cursor?.canAppend ?? false,
233980
+ activeTurn,
233696
233981
  oldestOrd,
233697
233982
  hasMoreBefore
233698
233983
  };
@@ -233830,7 +234115,7 @@ function createChatSessionsDomain(opts) {
233830
234115
  const byId = new Map(actors.map((a) => [a.id, a]));
233831
234116
  const resolver2 = (id) => {
233832
234117
  const a = byId.get(id);
233833
- return a ? { name: a.name } : void 0;
234118
+ return a ? { name: a.name } : systemActorFallback(id);
233834
234119
  };
233835
234120
  const allSummaries = buildWorkorderSummaries(wkKernel.model, resolver2, (wo) => byWo.get(wo) ?? null, (wo) => held.has(wo), void 0, void 0, (role) => wkKernel.actorForRole(role));
233836
234121
  const summaryById = new Map(allSummaries.map((s2) => [s2.id, s2]));
@@ -256333,6 +256618,7 @@ var init_src11 = __esm({
256333
256618
  init_node_store();
256334
256619
  init_tokens();
256335
256620
  init_collab();
256621
+ init_workorders();
256336
256622
  init_collab();
256337
256623
  init_closure_report();
256338
256624
  init_workorders();
@@ -258049,7 +258335,8 @@ async function startServe(opts) {
258049
258335
  kernel: defaultCompanyKernel,
258050
258336
  // B2:读路径走 oplog 派生投影——同一 oplog 写后重放重建
258051
258337
  oplog,
258052
- actorNames: { resolve: (actorId) => registryActorNames.get(actorId) },
258338
+ // 系统身份(`actor:system*`)不在名册里,缓存必然打空——补同一份展示名,别让它退成裸 id。
258339
+ actorNames: { resolve: (actorId) => registryActorNames.get(actorId) ?? systemActorFallback(actorId)?.name },
258053
258340
  blobs,
258054
258341
  // 项目普通文件上传入 blob 库
258055
258342
  actorDirectory: registryStore,
@@ -258085,7 +258372,7 @@ async function startServe(opts) {
258085
258372
  ...engine2.blobs ? { blobs: engine2.blobs } : {},
258086
258373
  actorDirectory: companyRegistry,
258087
258374
  actorNames: {
258088
- resolve: (actorId) => perCompanyActorNames.get(companyId)?.get(actorId)
258375
+ resolve: (actorId) => perCompanyActorNames.get(companyId)?.get(actorId) ?? systemActorFallback(actorId)?.name
258089
258376
  },
258090
258377
  resolveActorContext: async (actorId) => {
258091
258378
  const config2 = await companyRegistry.latestConfig(actorId).catch(() => null);
@@ -258146,7 +258433,8 @@ async function startServe(opts) {
258146
258433
  ops: [],
258147
258434
  nodeOutputs: persisted.nodeOutputs,
258148
258435
  workspaceBindings: persisted.workspaceBindings,
258149
- actorNames: { resolve: (actorId) => registryActorNames.get(actorId) }
258436
+ // 系统身份(`actor:system*`)不在名册里,缓存必然打空——补同一份展示名,别让它退成裸 id。
258437
+ actorNames: { resolve: (actorId) => registryActorNames.get(actorId) ?? systemActorFallback(actorId)?.name }
258150
258438
  });
258151
258439
  };
258152
258440
  const traceDsn = process.env["OASIS_TRACE_PG_DSN"] ?? pgDsn;
@@ -261209,7 +261497,8 @@ async function startServe(opts) {
261209
261497
  await server.settleDelegationFromSweep(
261210
261498
  chatSessionId,
261211
261499
  `\u5B50\u4F1A\u8BDD\u8FD9\u4E00\u8F6E\u88AB\u6062\u590D\u5668\u6536\u53E3\u4E3A ${status}\uFF08${reason}\uFF09`,
261212
- companyId
261500
+ companyId,
261501
+ { outcome: status === "succeeded" ? "succeeded" : "failed" }
261213
261502
  );
261214
261503
  },
261215
261504
  log: (m2) => console.log(m2),
@@ -270551,7 +270840,7 @@ function shimScript() {
270551
270840
  }
270552
270841
 
270553
270842
  // src/index.ts
270554
- var PKG_VERSION = true ? "2.2.8" : "dev";
270843
+ var PKG_VERSION = true ? "2.2.10" : "dev";
270555
270844
  var LOCAL_BIN = localBin();
270556
270845
  var NPM_PREFIX = npmPrefix();
270557
270846
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.8",
3
+ "version": "2.2.10",
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": "6677c1551a7015b5a37a4fc4c3d7f7e7aaf395e6"
29
+ "sourceHead": "3eac5f26307c5d0cf750e4a7edc6a5f60bdbe63d"
30
30
  }
31
31
  }