oasis_test_v2 2.2.2 → 2.2.3

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 +1385 -324
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1190,6 +1190,9 @@ function deriveLastTurnQuality(lastAssistant) {
1190
1190
  if (status === "error") return "abnormal";
1191
1191
  return lastAssistant && lastAssistant.content.trim().length > 0 ? "clean" : "abnormal";
1192
1192
  }
1193
+ function deriveSessionRunning(lastAssistant) {
1194
+ return lastAssistant?.status === "running";
1195
+ }
1193
1196
  function windowMessagesByTurns(messages, turns) {
1194
1197
  if (turns <= 0) return { messages, hasMoreBefore: false };
1195
1198
  let seen = 0;
@@ -1340,11 +1343,39 @@ function collectDelegationArtifacts(roundSummaries) {
1340
1343
  const artifacts = /* @__PURE__ */ new Map();
1341
1344
  for (const round of roundSummaries) {
1342
1345
  for (const file of round.files ?? []) {
1343
- artifacts.set(file.name, { name: file.name, ...file.blobRef ? { blobRef: file.blobRef } : {}, round: round.round });
1346
+ const seen = artifacts.get(file.name);
1347
+ if (seen) {
1348
+ if (!seen.blobRef && file.blobRef) seen.blobRef = file.blobRef;
1349
+ continue;
1350
+ }
1351
+ artifacts.set(file.name, {
1352
+ name: file.name,
1353
+ ...file.blobRef ? { blobRef: file.blobRef } : {},
1354
+ round: round.round,
1355
+ // settledSeq 透传自这个名字**第一次出现**的那一轮。未收口的轮次(pending 续跑那一支)
1356
+ // 与本字段落地前的存量行都没有值——前端凭 `settledSeq` 是否缺失走两条路径:
1357
+ // 有 → 按 settledSeq 各自定位对应 `chat-msg:delegation-done|<id>:<seq>`;
1358
+ // 无 → 该委派整条退回「单委派单锚点」降级(`ann:36bfa04b`)。
1359
+ ...typeof round.settledSeq === "number" ? { settledSeq: round.settledSeq } : {}
1360
+ });
1344
1361
  }
1345
1362
  }
1346
1363
  return [...artifacts.values()];
1347
1364
  }
1365
+ function delegationOutboxFingerprints(roundSummaries) {
1366
+ const out = /* @__PURE__ */ new Map();
1367
+ for (const round of roundSummaries) {
1368
+ for (const file of round.files ?? []) {
1369
+ if (typeof file.size !== "number" || !file.mtime) continue;
1370
+ out.set(file.name, { size: file.size, mtime: file.mtime });
1371
+ }
1372
+ }
1373
+ return out;
1374
+ }
1375
+ function touchAnchorMessageId(messageId) {
1376
+ const hash2 = messageId.indexOf("#");
1377
+ return hash2 >= 0 ? messageId.slice(0, hash2) : messageId;
1378
+ }
1348
1379
  var DelegationLabelConflictError, DELEGATION_FILE_LIMITS, DELEGATION_INBOUND_DIR, DELEGATION_OUTBOUND_DIR, DELEGATION_OUTPUTS_DIR, DELEGATION_RETURN_RECENT_ROUNDS;
1349
1380
  var init_delegation = __esm({
1350
1381
  "../contract/src/delegation.ts"() {
@@ -1537,6 +1568,16 @@ var init_http_api = __esm({
1537
1568
  }
1538
1569
  });
1539
1570
 
1571
+ // ../contract/src/file-preview.ts
1572
+ var OFFICE_PREVIEW_MAX_BYTES, FILE_CONTENT_MAX_BYTES;
1573
+ var init_file_preview = __esm({
1574
+ "../contract/src/file-preview.ts"() {
1575
+ "use strict";
1576
+ OFFICE_PREVIEW_MAX_BYTES = 20 * 1024 * 1024;
1577
+ FILE_CONTENT_MAX_BYTES = 50 * 1024 * 1024;
1578
+ }
1579
+ });
1580
+
1540
1581
  // ../contract/src/index.ts
1541
1582
  var init_src = __esm({
1542
1583
  "../contract/src/index.ts"() {
@@ -1596,6 +1637,7 @@ var init_src = __esm({
1596
1637
  init_knowledge();
1597
1638
  init_knowledge_governance();
1598
1639
  init_http_api();
1640
+ init_file_preview();
1599
1641
  }
1600
1642
  });
1601
1643
 
@@ -4036,7 +4078,7 @@ var init_events = __esm({
4036
4078
  // ../engine/src/views.ts
4037
4079
  function workState(w2, hasOutput2) {
4038
4080
  if (w2.status) return w2.status;
4039
- if (w2.retryAt) return "retry";
4081
+ if (w2.retryAt && w2.deadAt) return "retry";
4040
4082
  if (w2.deadAt) return "dead";
4041
4083
  if (w2.endedAt) {
4042
4084
  if (w2.outcome === "failed") return "failed";
@@ -4458,6 +4500,36 @@ function lastPostIsFromNode(state, issue2, node2) {
4458
4500
  function repliedInWork(state, work, issueId) {
4459
4501
  return repliesSorted(state, issueId).some((r) => r.createdAt >= REPLY_ORIGIN_SINCE ? r.viaWorkId === work.id : r.authorActorId === work.assigneeActorId && r.createdAt >= work.createdAt);
4460
4502
  }
4503
+ function recordHandledNotification(state, work, issue2, acknowledged = false) {
4504
+ if (issue2.aboutNodeId !== work.nodeId || work.endedAt || work.deadAt) return;
4505
+ const gap = issue2.gapId ? state.issue(issue2.gapId) : void 0;
4506
+ const ids2 = [issue2.id, ...gap?.kind === "gap" && gap.resolvedAt && gap.raisedByNodeId === work.nodeId ? [gap.id] : []];
4507
+ state.updateWork(work.id, {
4508
+ frozenIssueIds: [.../* @__PURE__ */ new Set([...work.frozenIssueIds ?? [], ...ids2])],
4509
+ ...acknowledged ? { handledIssueIds: [.../* @__PURE__ */ new Set([...work.handledIssueIds ?? [], ...ids2])] } : {}
4510
+ });
4511
+ }
4512
+ function notificationsHandledInWork(state, work) {
4513
+ if (work.conclusion != null) return false;
4514
+ const node2 = state.node(work.nodeId);
4515
+ const accepted = node2?.latestAcceptId ? state.work(node2.latestAcceptId) : void 0;
4516
+ if (!node2 || !accepted || node2.latestAcceptId !== node2.latestProposedWorkId || accepted.nodeVersion !== work.nodeVersion) return false;
4517
+ const previousInputs = new Set(state.workInputsOf(accepted.id).map((i) => i.upstreamWorkId));
4518
+ const currentInputs = new Set(state.workInputsOf(work.id).map((i) => i.upstreamWorkId));
4519
+ if (previousInputs.size !== currentInputs.size || [...currentInputs].some((id) => !previousInputs.has(id))) return false;
4520
+ const ids2 = work.frozenIssueIds ?? [];
4521
+ const handledNow = (work.handledIssueIds ?? []).length > 0 || ids2.some((id) => repliedInWork(state, work, id));
4522
+ return handledNow && ids2.length > 0 && ids2.every((id) => {
4523
+ const issue2 = state.issue(id);
4524
+ if (!issue2) return false;
4525
+ if (issue2.kind === "gap") return issue2.resolvedAt !== null && ((work.handledIssueIds ?? []).includes(id) || ids2.some((relatedId) => {
4526
+ const related = state.issue(relatedId);
4527
+ return related?.kind === "comment" && related.gapId === id && repliedInWork(state, work, relatedId) && lastPostIsFromNode(state, related, node2);
4528
+ }) || state.mainWorksOf(work.nodeId).some((prior) => prior.id !== work.id && prior.status === "success" && ((prior.handledIssueIds ?? []).includes(id) || prior.acceptedAt && (prior.frozenIssueIds ?? []).includes(id))));
4529
+ if (issue2.kind !== "comment" && issue2.kind !== "change_request") return false;
4530
+ return issue2.resolvedAt !== null && (work.handledIssueIds ?? []).includes(id) || issue2.kind === "comment" && repliedInWork(state, work, id) && lastPostIsFromNode(state, issue2, node2);
4531
+ });
4532
+ }
4461
4533
  function threadLength(state, issueId) {
4462
4534
  return state.repliesOf(issueId).length + 1;
4463
4535
  }
@@ -4559,6 +4631,7 @@ var init_kill = __esm({
4559
4631
 
4560
4632
  // ../engine/src/activate.ts
4561
4633
  function scan(state, ctx) {
4634
+ if (state.workorder.dispatchPaused) return { events: [], terminated: false };
4562
4635
  const events = [];
4563
4636
  const now = ctx.at;
4564
4637
  const policy = ctx.policy ?? CODE_DEFAULT_POLICY;
@@ -5651,6 +5724,7 @@ ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS last_activity_at timestamptz;
5651
5724
  -- \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
5652
5725
  -- 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
5653
5726
  ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS frozen_issue_ids jsonb;
5727
+ ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS handled_issue_ids jsonb;
5654
5728
  -- \u56DE\u4FE1\u8F6E\u6307\u5411\u54EA\u6761 comment issue\uFF08\u7EAF\u6570\u636E\uFF09
5655
5729
  ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS reply_to_issue_id text;
5656
5730
  -- \u2605 work lane\uFF08\u6B63\u5411\u5224\u636E\uFF0C\u53D6\u4EE3 "reply_to_issue_id IS NULL"\uFF09\uFF1A\u5B58\u91CF\u884C\u6309\u6709\u65E0 reply_to_issue_id \u56DE\u586B\u3002
@@ -5800,7 +5874,7 @@ ALTER TABLE ${q2}.workorders DROP COLUMN IF EXISTS resume_marker;
5800
5874
  ALTER TABLE ${q2}.events DROP COLUMN IF EXISTS effect_lease_until;
5801
5875
  -- \u5B58\u91CF\u56DE\u586B\uFF1Astatus \u5217=null \u7684\u884C\u6309\u65F6\u95F4\u6233/\u4EA7\u51FA\u56DE\u586B\uFF08\u4E0E views.ts \u56DE\u9000\u6D3E\u751F\u540C\u8BED\u4E49\uFF0C\u56DE\u586B\u540E\u72B6\u6001\u5373\u5B58\u50A8\u6001\uFF09
5802
5876
  UPDATE ${q2}.works SET status = CASE
5803
- WHEN retry_at IS NOT NULL THEN 'retry'
5877
+ WHEN retry_at IS NOT NULL AND dead_at IS NOT NULL THEN 'retry'
5804
5878
  WHEN dead_at IS NOT NULL THEN 'dead'
5805
5879
  WHEN ended_at IS NOT NULL AND outcome = 'failed' THEN 'failed'
5806
5880
  WHEN ended_at IS NOT NULL AND (conclusion IS NOT NULL OR
@@ -11289,6 +11363,7 @@ var init_store_postgres = __esm({
11289
11363
  proposalReason: w2.proposal_reason ?? null,
11290
11364
  lastActivityAt: iso(w2.last_activity_at),
11291
11365
  frozenIssueIds: w2.frozen_issue_ids ?? null,
11366
+ handledIssueIds: w2.handled_issue_ids ?? null,
11292
11367
  agentHandoffAttemptId: w2.agent_handoff_attempt_id ?? null,
11293
11368
  businessHandoffStatus: w2.business_handoff_status ?? null,
11294
11369
  businessHandoffRecordedAt: iso(w2.business_handoff_recorded_at),
@@ -11494,8 +11569,8 @@ var init_store_postgres = __esm({
11494
11569
  (id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
11495
11570
  outcome_detail,session_ref,continues_work_id,parent_work_id,reply_to_issue_id,output_version_no,
11496
11571
  node_version,conclusion,proposal_reason,last_activity_at,frozen_issue_ids,agent_handoff_attempt_id,business_handoff_status,business_handoff_recorded_at,
11497
- acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane)
11498
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33)
11572
+ acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane,handled_issue_ids)
11573
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34)
11499
11574
  ON CONFLICT (id) DO NOTHING`,
11500
11575
  [
11501
11576
  w2.id,
@@ -11530,13 +11605,14 @@ var init_store_postgres = __esm({
11530
11605
  w2.rejectedReason,
11531
11606
  w2.overrideBy,
11532
11607
  w2.overrideReason,
11533
- w2.lane
11608
+ w2.lane,
11609
+ w2.handledIssueIds ? JSON.stringify(w2.handledIssueIds) : null
11534
11610
  ]
11535
11611
  );
11536
11612
  break;
11537
11613
  }
11538
11614
  case "work.update":
11539
- await this.patch("works", "id", m2.id, m2.patch, { outcomeDetail: true });
11615
+ await this.patch("works", "id", m2.id, m2.patch, { outcomeDetail: true, frozenIssueIds: true, handledIssueIds: true });
11540
11616
  break;
11541
11617
  case "artifact.replace":
11542
11618
  await this.c.query(`DELETE FROM ${this.s}.work_artifacts WHERE work_id = $1`, [m2.workId]);
@@ -11866,7 +11942,7 @@ function newNode(id, workorderId, at, version2) {
11866
11942
  updatedAt: at
11867
11943
  };
11868
11944
  }
11869
- function markNodeForRetry(state, nodeId, at) {
11945
+ function markNodeForRetry(state, nodeId, at, resetRejectedBudget = false) {
11870
11946
  const node2 = state.node(nodeId);
11871
11947
  if (!node2) return;
11872
11948
  const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
@@ -11874,7 +11950,7 @@ function markNodeForRetry(state, nodeId, at) {
11874
11950
  state.updateWork(lw.id, { retryAt: at, status: "retry" });
11875
11951
  }
11876
11952
  if (lw && !lw.deadAt && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success") {
11877
- if (lw.acceptanceState === "rejected") state.updateWork(lw.id, { retryAt: at });
11953
+ if (resetRejectedBudget && lw.acceptanceState === "rejected") state.updateWork(lw.id, { retryAt: at });
11878
11954
  for (const req of state.requirementsOf(node2.id)) {
11879
11955
  if (!req.latestReviewId) continue;
11880
11956
  const r = state.review(req.latestReviewId);
@@ -12006,6 +12082,9 @@ var init_plan = __esm({
12006
12082
  const changed = node2.assigneeActorId !== e.assigneeActorId;
12007
12083
  const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
12008
12084
  const settled = lw !== null && lw !== void 0 && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success";
12085
+ if (changed && e.resetRejectedBudget === true && lw && !lw.deadAt && settled && lw.acceptanceState === "rejected") {
12086
+ state.updateWork(lw.id, { retryAt: ctx.at });
12087
+ }
12009
12088
  state.updateNode(e.nodeId, {
12010
12089
  assigneeActorId: e.assigneeActorId,
12011
12090
  assigneeRole: e.assigneeRole,
@@ -12035,7 +12114,7 @@ var init_plan = __esm({
12035
12114
  name: "plan/node-retry",
12036
12115
  kind: "plan.node_retry",
12037
12116
  apply(e, state, ctx) {
12038
- markNodeForRetry(state, e.nodeId, ctx.at);
12117
+ markNodeForRetry(state, e.nodeId, ctx.at, e.resetRejectedBudget === true);
12039
12118
  }
12040
12119
  };
12041
12120
  planUpdateFields = {
@@ -12168,7 +12247,13 @@ var init_workorder = __esm({
12168
12247
  apply(e, state, ctx) {
12169
12248
  state.updateWorkorder({ dispatchPaused: false, pausedBy: null, pausedReason: null, updatedAt: ctx.at });
12170
12249
  for (const node2 of state.liveNodes()) {
12171
- planNodeRetry.apply({ kind: "plan.node_retry", workorderId: e.workorderId, nodeId: node2.id, by: e.by }, state, ctx);
12250
+ planNodeRetry.apply({
12251
+ kind: "plan.node_retry",
12252
+ workorderId: e.workorderId,
12253
+ nodeId: node2.id,
12254
+ by: e.by,
12255
+ resetRejectedBudget: e.resetRejectedBudget
12256
+ }, state, ctx);
12172
12257
  }
12173
12258
  }
12174
12259
  };
@@ -12182,8 +12267,8 @@ var init_workorder = __esm({
12182
12267
  });
12183
12268
 
12184
12269
  // ../engine/src/handlers/work.ts
12185
- function latestProposedOf(state, nodeId) {
12186
- const successes = state.mainWorksOf(nodeId).filter((w2) => w2.status === "success").sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
12270
+ function latestProposedOf(state, nodeId, deliveriesOnly) {
12271
+ const successes = state.mainWorksOf(nodeId).filter((w2) => w2.status === "success" && (!deliveriesOnly || state.artifactsOf(w2.id).length > 0 || w2.conclusion != null)).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
12187
12272
  return successes.length > 0 ? successes[successes.length - 1].id : null;
12188
12273
  }
12189
12274
  var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workContentEdited, workConclude, nodeLatestProposedRebased, workTimeout;
@@ -12198,6 +12283,7 @@ var init_work = __esm({
12198
12283
  name: "work/create",
12199
12284
  kind: "work.create",
12200
12285
  apply(e, state, ctx) {
12286
+ if (e.pauseGateVersion === 1 && state.workorder.dispatchPaused) return;
12201
12287
  if (state.work(e.workId)) return;
12202
12288
  const node2 = state.node(e.nodeId);
12203
12289
  if (!node2) return;
@@ -12223,6 +12309,7 @@ var init_work = __esm({
12223
12309
  replyToIssueId: e.replyToIssueId,
12224
12310
  outputVersionNo: null,
12225
12311
  conclusion: null,
12312
+ handledIssueIds: null,
12226
12313
  agentHandoffAttemptId: null,
12227
12314
  businessHandoffStatus: null,
12228
12315
  businessHandoffRecordedAt: null,
@@ -12262,6 +12349,7 @@ var init_work = __esm({
12262
12349
  outputVersionNo: null,
12263
12350
  conclusion: null,
12264
12351
  frozenIssueIds: frozen.length > 0 ? frozen : null,
12352
+ handledIssueIds: null,
12265
12353
  agentHandoffAttemptId: null,
12266
12354
  businessHandoffStatus: null,
12267
12355
  businessHandoffRecordedAt: null,
@@ -12296,6 +12384,7 @@ var init_work = __esm({
12296
12384
  // 会话侧也要杀(旧 agent 可能还挂着)。cancelPreviousWork 找不到/会话已退则 no-op。
12297
12385
  // 回信 work 不杀前会话(apply 不 kill 主链,节点已完结、无在跑会话),只派发。
12298
12386
  async effect(e, ctx) {
12387
+ if (e.pauseGateVersion === 1 && !await ctx.canDispatchWork(e.workorderId, e.workId)) return;
12299
12388
  if (!e.replyToIssueId) await ctx.cancelPreviousWork(e.nodeId, e.workId);
12300
12389
  await ctx.dispatchWork(e.workId, e.resumeEligible !== void 0 ? { resumeEligible: e.resumeEligible } : void 0);
12301
12390
  }
@@ -12326,7 +12415,7 @@ var init_work = __esm({
12326
12415
  if (node2.latestProposedWorkId) {
12327
12416
  const ls = state.work(node2.latestProposedWorkId);
12328
12417
  if (!ls || ls.status !== "success") {
12329
- state.updateNode(e.nodeId, { latestProposedWorkId: latestProposedOf(state, e.nodeId) });
12418
+ state.updateNode(e.nodeId, { latestProposedWorkId: latestProposedOf(state, e.nodeId, e.semanticsVersion === 2) });
12330
12419
  }
12331
12420
  }
12332
12421
  killNodeReviews(state, e.nodeId, ctx.at);
@@ -12356,17 +12445,34 @@ var init_work = __esm({
12356
12445
  }
12357
12446
  const businessFailed = w2.businessHandoffStatus === "not_delivered";
12358
12447
  const artifactCount = state.artifactsOf(e.workId).length;
12359
- const pureReply = e.semanticsVersion === 2 && artifactCount === 0 && (w2.conclusion === null || w2.conclusion === void 0) && (w2.frozenIssueIds ?? []).some((issueId) => state.issue(issueId)?.kind === "comment" && repliedInWork(state, w2, issueId));
12360
- const succeeded = e.outcome === "completed" && !businessFailed && (e.semanticsVersion === 2 ? artifactCount > 0 || pureReply : hasOutput(w2, artifactCount));
12448
+ const pureReply = (e.semanticsVersion ?? 1) >= 2 && artifactCount === 0 && (w2.conclusion === null || w2.conclusion === void 0) && (w2.frozenIssueIds ?? []).some((issueId) => state.issue(issueId)?.kind === "comment" && repliedInWork(state, w2, issueId));
12449
+ const succeeded = e.outcome === "completed" && !businessFailed && ((e.semanticsVersion ?? 1) >= 2 ? artifactCount > 0 || (e.semanticsVersion === 3 ? notificationsHandledInWork(state, w2) : pureReply) : hasOutput(w2, artifactCount));
12361
12450
  const status = succeeded ? "success" : "failed";
12362
- const zeroOutputReason = e.semanticsVersion === 2 && !succeeded && e.outcome === "completed" && !businessFailed ? artifactCount === 0 && w2.conclusion ? "conclusion without artifacts (invariant violation)" : "zero output" : null;
12451
+ if (e.semanticsVersion === 3 && artifactCount === 0 && e.outcome === "completed") {
12452
+ const node2 = state.node(w2.nodeId);
12453
+ if (node2) for (const id of w2.frozenIssueIds ?? []) {
12454
+ const issue2 = state.issue(id);
12455
+ if (issue2?.kind === "comment" && !issue2.resolvedAt && repliedInWork(state, w2, id) && lastPostIsFromNode(state, issue2, node2)) {
12456
+ state.emit({
12457
+ kind: "issue.resolve",
12458
+ workorderId: e.workorderId,
12459
+ issueId: id,
12460
+ state: "resolved",
12461
+ by: w2.assigneeActorId,
12462
+ note: null,
12463
+ viaWorkId: w2.id
12464
+ });
12465
+ }
12466
+ }
12467
+ }
12468
+ const zeroOutputReason = (e.semanticsVersion ?? 1) >= 2 && !succeeded && e.outcome === "completed" && !businessFailed ? artifactCount === 0 && w2.conclusion ? "conclusion without artifacts (invariant violation)" : e.semanticsVersion === 3 && (w2.frozenIssueIds ?? []).length > 0 ? "notifications incomplete or delivery requirements changed" : "zero output" : null;
12363
12469
  state.updateWork(e.workId, {
12364
12470
  endedAt: ctx.at,
12365
12471
  outcome: businessFailed ? "failed" : e.outcome,
12366
12472
  outcomeDetail: businessFailed ? { ...e.detail ?? {}, reason: "agent business handoff not delivered" } : zeroOutputReason ? { ...e.detail ?? {}, reason: e.detail?.reason ?? zeroOutputReason } : e.detail,
12367
12473
  status
12368
12474
  });
12369
- if (status === "success" && !(e.semanticsVersion === 2 && artifactCount === 0)) {
12475
+ if (status === "success" && !((e.semanticsVersion ?? 1) >= 2 && artifactCount === 0)) {
12370
12476
  state.updateNode(w2.nodeId, { latestProposedWorkId: e.workId });
12371
12477
  }
12372
12478
  }
@@ -12390,7 +12496,7 @@ var init_work = __esm({
12390
12496
  } : {}
12391
12497
  });
12392
12498
  if (flipsToFailed && state.node(w2.nodeId)?.latestProposedWorkId === e.workId) {
12393
- state.updateNode(w2.nodeId, { latestProposedWorkId: latestProposedOf(state, w2.nodeId) });
12499
+ state.updateNode(w2.nodeId, { latestProposedWorkId: latestProposedOf(state, w2.nodeId, e.semanticsVersion === 2) });
12394
12500
  }
12395
12501
  }
12396
12502
  };
@@ -12508,6 +12614,7 @@ var init_issue = __esm({
12508
12614
  "../engine/src/handlers/issue.ts"() {
12509
12615
  "use strict";
12510
12616
  init_plan();
12617
+ init_letters();
12511
12618
  issueCreate = {
12512
12619
  name: "issue/create",
12513
12620
  kind: "issue.create",
@@ -12578,6 +12685,11 @@ var init_issue = __esm({
12578
12685
  // 缺失(人 / chat 会话 / 存量事件)落 null,判据按"不是节点发言"处理。
12579
12686
  viaWorkId: e.viaNodeId ? state.node(e.viaNodeId)?.latestWorkId ?? null : null
12580
12687
  });
12688
+ const workId = e.viaNodeId ? state.node(e.viaNodeId)?.latestWorkId : null;
12689
+ const work = workId ? state.work(workId) : void 0;
12690
+ if (e.handlingVersion === 1 && work && work.assigneeActorId === e.authorActorId && ctx.actorId === e.authorActorId) {
12691
+ recordHandledNotification(state, work, issue2);
12692
+ }
12581
12693
  }
12582
12694
  };
12583
12695
  issueResolve = {
@@ -12585,7 +12697,13 @@ var init_issue = __esm({
12585
12697
  kind: "issue.resolve",
12586
12698
  apply(e, state, ctx) {
12587
12699
  const issue2 = state.issue(e.issueId);
12588
- if (!issue2 || issue2.resolvedAt !== null) return;
12700
+ if (!issue2) return;
12701
+ const workId = e.viaNodeId ? state.node(e.viaNodeId)?.latestWorkId : null;
12702
+ const work = workId ? state.work(workId) : void 0;
12703
+ if (e.acknowledged && work && work.assigneeActorId === e.by && ctx.actorId === e.by && e.note?.trim() && issue2.kind !== "escalation" && (issue2.kind !== "gap" || issue2.resolvedAt !== null)) {
12704
+ recordHandledNotification(state, work, issue2, true);
12705
+ }
12706
+ if (issue2.resolvedAt !== null) return;
12589
12707
  const humanResolution = e.by.startsWith("actor:human:") && e.by === ctx.actorId;
12590
12708
  if (e.semanticsVersion === 2 && issue2.kind === "escalation" && !humanResolution) {
12591
12709
  throw new Error("\u5173\u95ED\u5347\u7EA7\u9700\u8981\u4EBA\u660E\u786E\u786E\u8BA4\uFF1Bagent \u8BF7\u63D0\u4EA4\u5173\u95ED\u6388\u6743\u7533\u8BF7");
@@ -12600,7 +12718,7 @@ var init_issue = __esm({
12600
12718
  resolvedNote: e.note,
12601
12719
  resolvedViaWorkId: e.viaWorkId
12602
12720
  });
12603
- if (issue2.kind === "gap" && (e.semanticsVersion !== 2 || humanResolution)) {
12721
+ if (issue2.kind === "gap" && e.closeRelatedEscalations !== false && (e.semanticsVersion !== 2 || humanResolution)) {
12604
12722
  for (const other of state.allIssues()) {
12605
12723
  if (other.kind === "escalation" && other.resolvedAt === null && other.workorderId === issue2.workorderId && other.gapId === issue2.id) {
12606
12724
  state.updateIssue(other.id, {
@@ -12618,7 +12736,7 @@ var init_issue = __esm({
12618
12736
  }
12619
12737
  if (issue2.kind === "gap" || issue2.kind === "escalation") {
12620
12738
  const nodeId = issue2.raisedByNodeId ?? issue2.aboutNodeId;
12621
- if (nodeId) markNodeForRetry(state, nodeId, ctx.at);
12739
+ if (nodeId) markNodeForRetry(state, nodeId, ctx.at, e.resetRejectedBudget === true);
12622
12740
  }
12623
12741
  }
12624
12742
  };
@@ -12656,6 +12774,7 @@ var init_review = __esm({
12656
12774
  name: "review/create",
12657
12775
  kind: "review.create",
12658
12776
  apply(e, state, ctx) {
12777
+ if (e.pauseGateVersion === 1 && state.workorder.dispatchPaused) return;
12659
12778
  const work = state.work(e.targetWorkId);
12660
12779
  if (!work) return;
12661
12780
  const dup = state.reviewsOf(e.targetWorkId).some(
@@ -12691,6 +12810,7 @@ var init_review = __esm({
12691
12810
  }
12692
12811
  },
12693
12812
  async effect(e, ctx) {
12813
+ if (e.pauseGateVersion === 1 && !await ctx.canDispatchReview(e.workorderId, e.reviewId)) return;
12694
12814
  if (e.reviewerIsAgent) await ctx.dispatchReview(e.reviewId, e.nodeId, e.reviewerActorId);
12695
12815
  else await ctx.notify([e.reviewerActorId], { kind: "review-requested", reviewId: e.reviewId });
12696
12816
  }
@@ -12997,11 +13117,28 @@ var init_handlers = __esm({
12997
13117
  });
12998
13118
 
12999
13119
  // ../engine/src/bus.ts
13000
- function versionEscalationResolution(event) {
13001
- if (event.kind === "work.accept" || event.kind === "issue.resolve") {
13002
- return { ...event, semanticsVersion: 2 };
13120
+ function versionCommandEvent(event) {
13121
+ if (event.kind === "work.create" || event.kind === "review.create") {
13122
+ return { ...event, pauseGateVersion: 1 };
13123
+ }
13124
+ if (event.kind === "issue.reply") return { ...event, handlingVersion: 1 };
13125
+ if (event.kind === "work.response" && event.semanticsVersion === 2) {
13126
+ return { ...event, semanticsVersion: 3 };
13127
+ }
13128
+ switch (event.kind) {
13129
+ case "work.accept":
13130
+ case "work.kill":
13131
+ case "work.handoff_recorded":
13132
+ return { ...event, semanticsVersion: 2 };
13133
+ case "issue.resolve":
13134
+ return { ...event, semanticsVersion: 2, resetRejectedBudget: true };
13135
+ case "plan.node_retry":
13136
+ case "workorder.resumed":
13137
+ case "plan.assign_actor":
13138
+ return { ...event, resetRejectedBudget: true };
13139
+ default:
13140
+ return event;
13003
13141
  }
13004
- return event;
13005
13142
  }
13006
13143
  function emptySnapshotPlaceholder(record8) {
13007
13144
  return {
@@ -13209,7 +13346,7 @@ var init_bus = __esm({
13209
13346
  workorderId: input.workorderId,
13210
13347
  actorId: input.actorId,
13211
13348
  handActorId: input.handActorId ?? null,
13212
- event: versionEscalationResolution(input.event),
13349
+ event: versionCommandEvent(input.event),
13213
13350
  causedBySeq: null,
13214
13351
  origin: "command",
13215
13352
  dedupKey: input.dedupKey ?? null,
@@ -13234,6 +13371,32 @@ var init_bus = __esm({
13234
13371
  }
13235
13372
  }
13236
13373
  }
13374
+ /** 同一命令的一组最终修改一起入队。scan-on-empty 在末项之前始终看到 pending,不能派发中间状态。 */
13375
+ async submitBatchAndProcess(inputs) {
13376
+ if (!inputs.length) return;
13377
+ const seqs = await this.store.transaction(async (tx) => {
13378
+ for (const wid of [...new Set(inputs.map((i) => i.workorderId))].sort()) await tx.lockWorkorder(wid);
13379
+ return tx.appendEvents(inputs.map((input) => ({
13380
+ companyId: input.companyId,
13381
+ workorderId: input.workorderId,
13382
+ actorId: input.actorId,
13383
+ handActorId: input.handActorId ?? null,
13384
+ event: versionCommandEvent(input.event),
13385
+ causedBySeq: null,
13386
+ origin: "command",
13387
+ dedupKey: input.dedupKey ?? null,
13388
+ createdAt: this.clock.now(),
13389
+ ...eventAnchors(input.event)
13390
+ })));
13391
+ });
13392
+ if (this.running) {
13393
+ const last = /* @__PURE__ */ new Map();
13394
+ inputs.forEach((input, i) => last.set(input.workorderId, seqs[i]));
13395
+ for (const [wid, seq] of last) await this.waitForSettled(wid, seq);
13396
+ } else {
13397
+ for (let i = 0; i < 2e3; i++) if (!await this.drainOne()) break;
13398
+ }
13399
+ }
13237
13400
  /* ═══════════════════════ 调度器主循环 ═══════════════════════ */
13238
13401
  /**
13239
13402
  * 启动调度器——永不返回。
@@ -13469,7 +13632,7 @@ var init_bus = __esm({
13469
13632
  workorderId: record8.workorderId,
13470
13633
  actorId: SYSTEM_ACTOR,
13471
13634
  handActorId: null,
13472
- event: versionEscalationResolution(event),
13635
+ event: versionCommandEvent(event),
13473
13636
  causedBySeq: record8.seq,
13474
13637
  origin: "apply",
13475
13638
  dedupKey: null,
@@ -13500,6 +13663,18 @@ var init_bus = __esm({
13500
13663
  // ★ 注入 workorderId:派发侧读模型没追上时,据它指名重读那张工单(见 EngineIO.dispatchWork 注释)。
13501
13664
  dispatchWork: (id, opts) => this.io.dispatchWork(id, { ...opts, workorderId: record8.workorderId }),
13502
13665
  dispatchReview: (id, nodeId, reviewerActorId) => this.io.dispatchReview(id, nodeId, reviewerActorId),
13666
+ canDispatchWork: async (workorderId, workId) => {
13667
+ const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
13668
+ if (!snap || snap.workorder.dispatchPaused) return false;
13669
+ const work = snap.works.find((row) => row.id === workId);
13670
+ return work?.status === "running" && !work.deadAt && !work.endedAt;
13671
+ },
13672
+ canDispatchReview: async (workorderId, reviewId) => {
13673
+ const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
13674
+ if (!snap || snap.workorder.dispatchPaused) return false;
13675
+ const review = snap.reviews.find((row) => row.id === reviewId);
13676
+ return review?.status === "running" && !review.cancelledAt && !review.endedAt;
13677
+ },
13503
13678
  cancelSession: (ref2) => this.io.cancelSession(ref2),
13504
13679
  // ★ work.create 的「kill 旧会话」:apply 已把该节点上一个 latest_work 置 deadAt(会话可能还挂着),
13505
13680
  // 这里按 store 找它 cancel。找不到 / 不是刚被 kill / 会话已退 → no-op。这是设计「节点重新运行 = 自动
@@ -13566,7 +13741,7 @@ var init_bus = __esm({
13566
13741
  workorderId: record8.workorderId,
13567
13742
  actorId: b2.actorId ?? SYSTEM_ACTOR,
13568
13743
  handActorId: null,
13569
- event: versionEscalationResolution(b2.event),
13744
+ event: versionCommandEvent(b2.event),
13570
13745
  causedBySeq: record8.seq,
13571
13746
  origin: "effect",
13572
13747
  dedupKey: b2.dedupKey ?? null,
@@ -13604,7 +13779,7 @@ var init_bus = __esm({
13604
13779
  workorderId,
13605
13780
  actorId: SYSTEM_ACTOR,
13606
13781
  handActorId: null,
13607
- event: versionEscalationResolution(event),
13782
+ event: versionCommandEvent(event),
13608
13783
  causedBySeq: null,
13609
13784
  origin: "apply",
13610
13785
  dedupKey: null,
@@ -13656,7 +13831,7 @@ var init_bus = __esm({
13656
13831
  workorderId: record8.workorderId,
13657
13832
  actorId: SYSTEM_ACTOR,
13658
13833
  handActorId: null,
13659
- event: versionEscalationResolution(event),
13834
+ event: versionCommandEvent(event),
13660
13835
  causedBySeq: record8.seq,
13661
13836
  origin: "apply",
13662
13837
  dedupKey: null,
@@ -14502,6 +14677,7 @@ async function assembleContext(args) {
14502
14677
  const siblingParts = args.part !== void 0 ? (artifact.parts ?? []).filter((p2) => p2.name !== args.part) : [];
14503
14678
  const consumers = consumersOf(model, artifactId).filter((c) => c.type !== DELIVERY_SUMMARY_ARTIFACT_TYPE);
14504
14679
  const staleSet = staleSetOf(model, artifactId);
14680
+ const acceptedHead = concludedHead(model, artifactId);
14505
14681
  const head = artifact.currentRev;
14506
14682
  const headRev = head !== null ? model.revisions.get(head) : null;
14507
14683
  const headSpread = headRev !== null ? await spreadRevisionContent(blobs, headRev, files, "deliverable") : null;
@@ -14817,10 +14993,19 @@ ${manifestDiff(baseC, newC)}
14817
14993
  } else if (head === null) {
14818
14994
  whyLines.push(typeDef?.contentType === "code" ? `**\u672C\u4EA7\u7269\u5728 Oasis \u91CC\u8FD8\u6CA1\u6709\u5DF2\u4EA4\u4ED8\u7684\u7248\u672C**\u2014\u2014\u4F46**\u8FD9\u4E0D\u7B49\u4E8E git \u91CC\u6CA1\u6709\u4E1C\u897F**\uFF1A\u4E0A\u4E00\u8F6E\u53EF\u80FD\u63A8\u4E86\u5206\u652F\u5374\u6CA1\u6765\u5F97\u53CA\u4EA4\u4ED8\u3002**\u4EE5\u4F60 fetch \u5230\u7684\u5206\u652F\u5B9E\u51B5\u4E3A\u51C6**\uFF08\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u7B2C 1 \u6B65\uFF09\u3002` : canvasMode ? `**\u5168\u65B0\u4EA7\u7269**\uFF1A\u8FD9\u662F\u9996\u7248\u2014\u2014\u672C\u4EA7\u7269\u8FD8\u6CA1\u6709\u4EFB\u4F55\u63D0\u4EA4\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002` : `**\u5168\u65B0\u4EA7\u7269**\uFF1A\`deliverable/\` \u4E3A\u7A7A\uFF0C\u8FD8\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u3002`);
14819
14995
  whatLines.push(typeDef?.contentType === "code" ? `\u6309\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u7B2C 1 \u6B65\u843D\u5230\u5DE5\u5355\u5206\u652F\u3001**\u5148\u770B\u6E05\u5B83\u4E0A\u9762\u5DF2\u7ECF\u6709\u4EC0\u4E48**\uFF0C\u518D\u63A5\u7740\u505A\u5B8C\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002` : `\u4EA7\u51FA**\u9996\u7248**\u5185\u5BB9\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002`);
14996
+ } else if (acceptedHead) {
14997
+ whyLines.push("**\u672C\u8F6E\u5DE5\u4F5C**\uFF1A\u6309\u5F53\u524D\u4EFB\u52A1\u8981\u6C42\u3001\u901A\u77E5\u53CA\u6062\u590D\u8BF4\u660E\u6838\u5BF9\u5DF2\u6709\u4EA4\u4ED8\u3002");
14998
+ whatLines.push("\u82E5\u6709\u771F\u5B9E\u7684\u65B0\u4FEE\u6539\u8981\u6C42\uFF0C\u5B8C\u6210\u4FEE\u6539\u540E\u518D\u4EA4\u4ED8\uFF1B\u82E5\u672C\u8F6E\u4EC5\u5904\u7406\u901A\u77E5\u4E14\u65E0\u9700\u6539\u7A3F\uFF0C\u9010\u6761\u660E\u786E\u6807\u8BB0\u5DF2\u5904\u7406\u540E\u6536\u5DE5\uFF0C\u4E0D\u5FC5\u91CD\u65B0 propose / conclude\u3002");
14820
14999
  } else {
14821
15000
  whyLines.push(`**\u7EE7\u7EED\u5B8C\u5584**\uFF1A\u4E0A\u6B21\u672A conclude\u3002`);
14822
15001
  whatLines.push(`**\u63A5\u7740\u628A\u5B83\u505A\u5B8C**\u3001\u771F\u6B63\u5B8C\u6574\u4E86\u518D conclude\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002`);
14823
15002
  }
15003
+ if (acceptedHead) {
15004
+ whyLines.unshift(`**\u5DF2\u6709\u4EA4\u4ED8\u5DF2\u9A8C\u6536**\uFF1A${acceptedHead}\u3002\u6267\u884C\u8F6E\u6B21\u4E0E\u4EA4\u4ED8\u7248\u672C\u5206\u5F00\u8BA1\u6570\uFF0C\u672C\u8F6E\u88AB\u5524\u8D77\u4E0D\u4F1A\u64A4\u9500\u8BE5\u9A8C\u6536\u3002`);
15005
+ for (const gap of freshGaps) {
15006
+ whatLines.push(`\u6838\u5BF9\u7F3A\u53E3 ${gap.gapId} \u7684\u89E3\u6CD5\uFF1B\u786E\u8BA4\u65E0\u9700\u4FEE\u6539\u65F6\uFF1A\`oasis resolve ${gap.gapId} --as acknowledged --note "<\u6838\u5BF9\u4F9D\u636E\u53CA\u65E0\u9700\u4FEE\u6539\u7684\u539F\u56E0>"\`\u3002`);
15007
+ }
15008
+ }
14824
15009
  if (pendingConcludeAttempt(model, artifactId) !== null) {
14825
15010
  whyLines.push(
14826
15011
  ``,
@@ -18050,7 +18235,7 @@ function bridgeLoadReadModel(input) {
18050
18235
  const art = nodeToArtifact(node2, edgesByTarget.get(node2.id) ?? [], requirementsByNode.get(node2.id) ?? []);
18051
18236
  const lw = node2.latestWorkId ? workById.get(node2.latestWorkId) : void 0;
18052
18237
  if (lw) {
18053
- const st = lw.status ?? (lw.retryAt ? "retry" : lw.deadAt ? "dead" : lw.endedAt ? lw.outcome === "failed" ? "failed" : "success" : "running");
18238
+ const st = lw.status ?? (lw.retryAt && lw.deadAt ? "retry" : lw.deadAt ? "dead" : lw.endedAt ? lw.outcome === "failed" ? "failed" : "success" : "running");
18054
18239
  const hasConclusionWithoutResponse = lw.conclusion !== null && lw.conclusion !== void 0 && st === "running" && !lw.endedAt;
18055
18240
  let uiSt;
18056
18241
  if (st === "retry") uiSt = null;
@@ -18553,22 +18738,18 @@ ${res.candidates.map((c) => ` - ${c.type}:${c.title ?? "(\u65E0title)"} \u2192
18553
18738
  return { ...plan, ops };
18554
18739
  }
18555
18740
  function buildConstituents(model, plan, schema, roleIndex) {
18556
- const already = new Set(
18557
- plan.ops.filter((o) => o.action === "unlink").map((o) => `${o.artifactId} ${o.to}`)
18558
- );
18741
+ const inputs = new Map([...model.artifacts].map(([id, art]) => [id, new Set(art.inputs.map((i) => i.to))]));
18559
18742
  const out = [];
18560
18743
  for (const c of buildConstituentsRaw(model, plan, schema, roleIndex)) {
18744
+ if (c.kind === "spawn_artifact") inputs.set(c.artifactId, new Set(c.payload.inputs.map((i) => i.to)));
18745
+ if (c.kind === "link_input") inputs.get(c.artifactId)?.add(c.payload.to);
18746
+ if (c.kind === "unlink_input") inputs.get(c.artifactId)?.delete(c.payload.to);
18561
18747
  if (c.kind === "seal" && c.payload.reason === "cancelled") {
18562
- for (const edge of edgesTouching(model, c.artifactId)) {
18563
- const key = `${edge.artifactId} ${edge.to}`;
18564
- if (already.has(key)) continue;
18565
- already.add(key);
18566
- out.push({
18567
- artifactId: edge.artifactId,
18568
- kind: "unlink_input",
18569
- target: edge.artifactId,
18570
- payload: { to: edge.to }
18571
- });
18748
+ for (const [id, upstreams] of inputs) {
18749
+ for (const to of upstreams) if (id === c.artifactId || to === c.artifactId) {
18750
+ out.push({ artifactId: id, kind: "unlink_input", target: id, payload: { to } });
18751
+ upstreams.delete(to);
18752
+ }
18572
18753
  }
18573
18754
  }
18574
18755
  out.push(c);
@@ -18907,6 +19088,10 @@ function filterReadModelByWorkspace(model, workspace) {
18907
19088
  pinMeta: new Map([...model.pinMeta].filter(([artId]) => myArtIds.has(artId)))
18908
19089
  };
18909
19090
  }
19091
+ function assertWorkorderDispatchActive(snap) {
19092
+ if (!snap?.workorder.dispatchPaused) return;
19093
+ throw new KernelError("\u5DE5\u5355\u5DF2\u6682\u505C\u2014\u2014\u8BF7\u5148\u901A\u8FC7\u5DE5\u5355\u64CD\u4F5C\u6062\u590D\u8FD0\u884C\uFF0C\u518D\u6267\u884C\u8282\u70B9\u7EA7\u8C03\u5EA6\u64CD\u4F5C", "workorder-paused");
19094
+ }
18910
19095
  async function mapWithConcurrency(items, limit, fn) {
18911
19096
  if (items.length === 0) return [];
18912
19097
  const out = new Array(items.length);
@@ -19542,6 +19727,7 @@ var init_kernel_bridge = __esm({
19542
19727
  }
19543
19728
  if (!workId) {
19544
19729
  const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
19730
+ assertWorkorderDispatchActive(snap);
19545
19731
  const nodeRow = snap?.nodes.find((n) => n.id === args.artifactId);
19546
19732
  const headId = nodeRow?.latestWorkId ?? null;
19547
19733
  if (headId) {
@@ -19963,6 +20149,30 @@ var init_kernel_bridge = __esm({
19963
20149
  });
19964
20150
  }
19965
20151
  async resolveAnnotation(args) {
20152
+ if (args.resolution === "acknowledged" && args.viaNode) {
20153
+ const gap = (this.model.gaps.get(args.viaNode) ?? []).find((g2) => g2.gapId === args.annotationId);
20154
+ if (gap) {
20155
+ if (!gap.resolved || !args.note?.trim()) throw new Error("\u53EA\u80FD\u786E\u8BA4\u5DF2\u89E3\u51B3\u7684\u7F3A\u53E3\uFF0C\u4E14\u9700\u8BF4\u660E\u4E3A\u4F55\u65E0\u9700\u6539\u7A3F");
20156
+ const wid2 = this.wo(args.viaNode);
20157
+ await this.commit({
20158
+ companyId: "",
20159
+ workorderId: wid2,
20160
+ actorId: args.actor,
20161
+ event: {
20162
+ kind: "issue.resolve",
20163
+ workorderId: wid2,
20164
+ issueId: gap.gapId,
20165
+ state: "resolved",
20166
+ by: args.actor,
20167
+ note: args.note,
20168
+ viaWorkId: null,
20169
+ viaNodeId: args.viaNode,
20170
+ acknowledged: true
20171
+ }
20172
+ });
20173
+ return;
20174
+ }
20175
+ }
19966
20176
  const ann = this.model.annotations.get(args.annotationId);
19967
20177
  if (!ann) throw new Error(`annotation not found: ${args.annotationId}`);
19968
20178
  if (isHumanChangeRequest(ann) && args.actor.startsWith("actor:agent:") && args.resolution === "resolved" && !args.via) {
@@ -19981,7 +20191,9 @@ var init_kernel_bridge = __esm({
19981
20191
  state: args.resolution === "wontfix" ? "closed" : "resolved",
19982
20192
  by: args.actor,
19983
20193
  note: args.note ?? null,
19984
- viaWorkId: args.via ?? null
20194
+ viaWorkId: args.via ?? null,
20195
+ ...args.viaNode ? { viaNodeId: args.viaNode } : {},
20196
+ ...args.resolution === "acknowledged" ? { acknowledged: true } : {}
19985
20197
  }
19986
20198
  });
19987
20199
  }
@@ -20047,7 +20259,7 @@ var init_kernel_bridge = __esm({
20047
20259
  for (const [artifactId, gaps] of this.model.gaps) {
20048
20260
  if (gaps.some((g2) => g2.gapId === args.gapId && !g2.resolved)) {
20049
20261
  const wid = this.wo(artifactId);
20050
- await this.commit({
20262
+ const events = [{
20051
20263
  companyId: "",
20052
20264
  workorderId: wid,
20053
20265
  actorId: args.actor,
@@ -20060,16 +20272,31 @@ var init_kernel_bridge = __esm({
20060
20272
  note: args.note ?? null,
20061
20273
  viaWorkId: null
20062
20274
  }
20275
+ }];
20276
+ if (args.rework) events.push({
20277
+ companyId: "",
20278
+ workorderId: wid,
20279
+ actorId: args.actor,
20280
+ event: {
20281
+ kind: "issue.create",
20282
+ workorderId: wid,
20283
+ issueId: `ann:rework:${args.gapId}`,
20284
+ issueKind: "comment",
20285
+ body: args.note ?? "\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C\u8BF7\u6838\u5BF9\u89E3\u6CD5",
20286
+ aboutNodeId: artifactId,
20287
+ aboutWorkId: null,
20288
+ raisedByNodeId: null,
20289
+ authorActorId: args.actor,
20290
+ handActorId: null,
20291
+ recipients: [],
20292
+ attachments: [],
20293
+ gapId: args.gapId,
20294
+ source: "gap-recovery"
20295
+ }
20063
20296
  });
20064
- if (args.rework) {
20065
- await this.annotate({
20066
- artifactId,
20067
- actor: args.actor,
20068
- kind: "comment",
20069
- annotationId: `ann:rework:${args.gapId}`,
20070
- body: args.note ?? "\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C\u8BF7\u6309\u7B54\u590D\u91CD\u505A"
20071
- });
20072
- }
20297
+ await this.bus.submitBatchAndProcess(events);
20298
+ this.markWorkorderDirty(wid);
20299
+ await this.refreshTracked();
20073
20300
  return;
20074
20301
  }
20075
20302
  }
@@ -20118,6 +20345,57 @@ var init_kernel_bridge = __esm({
20118
20345
  }
20119
20346
  });
20120
20347
  }
20348
+ /** 人确认恢复卡后,只关闭卡中列出的缺口和关联上报。其他问题仍由各自责任人处理。 */
20349
+ async resolveRecovery(args) {
20350
+ if (!args.actor.startsWith("actor:human:")) throw new Error("\u6062\u590D\u51B3\u5B9A\u9700\u8981\u4EBA\u660E\u786E\u786E\u8BA4");
20351
+ if (!args.reason.trim()) throw new Error("\u6062\u590D\u51B3\u5B9A\u5FC5\u987B\u8BF4\u660E\u91C7\u7528\u7684\u65B9\u6848");
20352
+ const wid = this.wo(args.artifactId);
20353
+ const snapshot = await this.store.transaction((tx) => tx.loadWorkorder(wid));
20354
+ const gap = snapshot?.issues.find((i) => i.id === args.gapId && i.kind === "gap" && i.aboutNodeId === args.artifactId);
20355
+ const escalation = snapshot?.issues.find((i) => i.id === args.escalationId && i.kind === "escalation" && i.aboutNodeId === args.artifactId);
20356
+ if (!gap || !escalation || escalation.gapId !== gap.id) throw new Error("\u6062\u590D\u5361\u7684\u7F3A\u53E3\u4E0E\u4E0A\u62A5\u5173\u8054\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u786E\u8BA4");
20357
+ const events = [];
20358
+ for (const issue2 of [gap, escalation]) if (!issue2.resolvedAt) events.push({
20359
+ companyId: "",
20360
+ workorderId: wid,
20361
+ actorId: args.actor,
20362
+ event: {
20363
+ kind: "issue.resolve",
20364
+ workorderId: wid,
20365
+ issueId: issue2.id,
20366
+ state: "resolved",
20367
+ by: args.actor,
20368
+ note: args.reason,
20369
+ viaWorkId: null,
20370
+ closeRelatedEscalations: false
20371
+ }
20372
+ });
20373
+ if (!gap.resolvedAt) events.push({
20374
+ companyId: "",
20375
+ workorderId: wid,
20376
+ actorId: args.actor,
20377
+ event: {
20378
+ kind: "issue.create",
20379
+ workorderId: wid,
20380
+ issueId: `ann:recovery:${gap.id}`,
20381
+ issueKind: "comment",
20382
+ body: args.reason,
20383
+ aboutNodeId: args.artifactId,
20384
+ aboutWorkId: null,
20385
+ raisedByNodeId: null,
20386
+ authorActorId: args.actor,
20387
+ handActorId: null,
20388
+ recipients: [],
20389
+ attachments: [],
20390
+ gapId: gap.id,
20391
+ source: "gap-recovery"
20392
+ }
20393
+ });
20394
+ await this.bus.submitBatchAndProcess(events);
20395
+ this.markWorkorderDirty(wid);
20396
+ await this.refreshTracked();
20397
+ await this.releaseIfStillHeld(args.artifactId, args.actor);
20398
+ }
20121
20399
  async resolveEscalation(args) {
20122
20400
  if (!args.actor.startsWith("actor:human:")) {
20123
20401
  throw new Error("\u5173\u95ED\u5347\u7EA7\u9700\u8981\u4EBA\u660E\u786E\u786E\u8BA4\uFF1Bagent \u8BF7\u63D0\u4EA4\u5173\u95ED\u6388\u6743\u7533\u8BF7");
@@ -20322,6 +20600,7 @@ var init_kernel_bridge = __esm({
20322
20600
  async rerunNode(args) {
20323
20601
  const wid = this.wo(args.artifactId);
20324
20602
  const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
20603
+ assertWorkorderDispatchActive(snap);
20325
20604
  const node2 = snap?.nodes.find((n) => n.id === args.artifactId);
20326
20605
  if (!node2 || node2.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
20327
20606
  if (!node2.assigneeActorId) throw new KernelError("\u8282\u70B9\u672A\u6307\u6D3E\u6267\u884C\u4EBA\u2014\u2014\u5148\u6307\u6D3E\u518D\u91CD\u65B0\u8FD0\u884C", "no-assignee");
@@ -20371,6 +20650,7 @@ var init_kernel_bridge = __esm({
20371
20650
  async rereviewNode(args) {
20372
20651
  const wid = this.wo(args.artifactId);
20373
20652
  const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
20653
+ assertWorkorderDispatchActive(snap);
20374
20654
  const node2 = snap?.nodes.find((n) => n.id === args.artifactId);
20375
20655
  if (!node2 || node2.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
20376
20656
  const lw = node2.latestWorkId ? snap.works.find((w2) => w2.id === node2.latestWorkId) : null;
@@ -20573,12 +20853,9 @@ var init_kernel_bridge = __esm({
20573
20853
  throw new Error(`resolveEscalation \u6388\u6743\u5361\u7F3A escalationId\uFF1A${args.draftId}`);
20574
20854
  }
20575
20855
  const reason = typeof card2.commandArgs?.["reason"] === "string" ? card2.commandArgs["reason"] : void 0;
20576
- await this.resolveEscalation({
20577
- artifactId,
20578
- actor: args.actor,
20579
- escalationId,
20580
- ...reason !== void 0 ? { reason } : {}
20581
- });
20856
+ const gapId = typeof card2.commandArgs?.["gapId"] === "string" ? card2.commandArgs["gapId"] : void 0;
20857
+ if (gapId) await this.resolveRecovery({ artifactId, actor: args.actor, escalationId, gapId, reason: reason ?? "" });
20858
+ else await this.resolveEscalation({ artifactId, actor: args.actor, escalationId, ...reason !== void 0 ? { reason } : {} });
20582
20859
  } else {
20583
20860
  throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u4E0D\u652F\u6301\u6267\u884C ${card2.command}`);
20584
20861
  }
@@ -20622,11 +20899,29 @@ var init_kernel_bridge = __esm({
20622
20899
  if (preview2.changeClass === "C" && !_actor.startsWith("actor:human:")) {
20623
20900
  throw new Error("C \u7C7B\u6539\u52A8\uFF08\u975E\u5355\u8C03\u6539\u56FE\uFF09\u5FC5\u987B\u7531\u4EBA\u786E\u8BA4\uFF0Cagent \u4E0D\u80FD\u76F4\u63A5\u843D\u5730\u2014\u2014\u8BF7\u8D70 preview \u2192 draft \u2192 \u4EBA apply-draft");
20624
20901
  }
20625
- const wid = plan.workspace ?? plan.ops.find((o) => o.action === "spawn" && o.workspace)?.workspace ?? "ws:default";
20902
+ const wid = ws ?? plan.ops.find((o) => o.action === "spawn" && o.workspace)?.workspace ?? "ws:default";
20626
20903
  let applied = 0;
20627
20904
  const unsupported = [];
20628
20905
  const reviewerStaffing = [];
20629
- await this.ensureWorkorder(wid, "", _actor);
20906
+ const events = [];
20907
+ const artifacts = new Map([...this.model.artifacts].map(([id, art]) => [id, { ...art, fields: { ...art.fields }, inputs: [...art.inputs] }]));
20908
+ const woFor = (id) => {
20909
+ const art = artifacts.get(id);
20910
+ if (!art) throw new Error(`artifact not found: ${id}`);
20911
+ return art.workspace;
20912
+ };
20913
+ const queue = async (input) => {
20914
+ if (input.event.kind === "plan.update_spec") {
20915
+ const event = input.event;
20916
+ const previous3 = events.find((e) => e.workorderId === input.workorderId && e.event.kind === "plan.update_spec" && e.event.nodeId === event.nodeId);
20917
+ if (previous3?.event.kind === "plan.update_spec") {
20918
+ previous3.event = { ...previous3.event, ...event };
20919
+ return;
20920
+ }
20921
+ }
20922
+ events.push(input);
20923
+ };
20924
+ const enqueue = async (event) => queue({ companyId: "", workorderId: event.workorderId, actorId: _actor, event });
20630
20925
  for (const op of plan.ops) {
20631
20926
  switch (op.action) {
20632
20927
  case "spawn": {
@@ -20658,7 +20953,7 @@ var init_kernel_bridge = __esm({
20658
20953
  // ★ executionEnv 同走 fields jsonb(与路径 A 对称——两条 spawn 路径必须一致,前车之鉴:类型默认评审闸)。
20659
20954
  ...op.executionEnv !== void 0 ? { executionEnv: op.executionEnv } : {}
20660
20955
  } : opFields;
20661
- await this.commit({
20956
+ await queue({
20662
20957
  companyId: "",
20663
20958
  workorderId: wid,
20664
20959
  actorId: _actor,
@@ -20688,27 +20983,64 @@ var init_kernel_bridge = __esm({
20688
20983
  activateWorkorder: false
20689
20984
  }
20690
20985
  });
20986
+ artifacts.set(id, {
20987
+ id,
20988
+ type: op.type,
20989
+ title: op.title ?? op.type,
20990
+ description: op.description ?? "",
20991
+ workspace: wid,
20992
+ owner: spawnOwner,
20993
+ fields: mergedFields ?? {},
20994
+ reviewRoles: opReviewRoles,
20995
+ reviewers: opReviewers ?? [],
20996
+ inputs: (op.inputs ?? []).map((i) => ({ to: i.to, required: i.required ?? true, pinned: null }))
20997
+ });
20691
20998
  applied++;
20692
20999
  break;
20693
21000
  }
20694
- case "link":
20695
- await this.linkInput({ artifactId: op.artifactId, to: op.to, actor: _actor });
21001
+ case "link": {
21002
+ await enqueue({ kind: "plan.add_edge", workorderId: woFor(op.artifactId), fromNodeId: op.to, toNodeId: op.artifactId, edgeKind: "data", required: op.required ?? true });
21003
+ const art = artifacts.get(op.artifactId);
21004
+ if (!art.inputs.some((i) => i.to === op.to)) art.inputs.push({ to: op.to, required: op.required ?? true, pinned: null });
20696
21005
  applied++;
20697
21006
  break;
20698
- case "unlink":
20699
- await this.unlinkInput({ artifactId: op.artifactId, to: op.to, actor: _actor });
21007
+ }
21008
+ case "unlink": {
21009
+ await enqueue({ kind: "plan.delete_edge", workorderId: woFor(op.artifactId), fromNodeId: op.to, toNodeId: op.artifactId });
21010
+ const art = artifacts.get(op.artifactId);
21011
+ art.inputs = art.inputs.filter((i) => i.to !== op.to);
20700
21012
  applied++;
20701
21013
  break;
21014
+ }
20702
21015
  case "assign":
20703
- await this.assignOwner({ artifactId: op.artifactId, owner: op.owner, actor: _actor });
21016
+ await enqueue({ kind: "plan.assign_actor", workorderId: woFor(op.artifactId), nodeId: op.artifactId, assigneeActorId: op.owner, assigneeRole: null });
20704
21017
  applied++;
20705
21018
  break;
20706
21019
  case "seal":
20707
- await this.seal({ artifactId: op.artifactId, reason: op.reason, actor: _actor });
21020
+ if (op.reason === "cancelled") for (const art of artifacts.values()) {
21021
+ for (const edge of art.inputs.filter((i) => art.id === op.artifactId || i.to === op.artifactId)) {
21022
+ await enqueue({ kind: "plan.delete_edge", workorderId: woFor(art.id), fromNodeId: edge.to, toNodeId: art.id });
21023
+ }
21024
+ art.inputs = art.inputs.filter((i) => art.id !== op.artifactId && i.to !== op.artifactId);
21025
+ }
21026
+ await enqueue({ kind: "plan.delete_node", workorderId: woFor(op.artifactId), nodeId: op.artifactId, sealReason: op.reason });
20708
21027
  applied++;
20709
21028
  break;
20710
21029
  case "annotate":
20711
- await this.annotate({ artifactId: op.artifactId, body: op.body, actor: _actor });
21030
+ await enqueue({
21031
+ kind: "issue.create",
21032
+ workorderId: woFor(op.artifactId),
21033
+ issueId: `ann:${(0, import_node_crypto3.randomUUID)()}`,
21034
+ issueKind: "comment",
21035
+ body: op.body,
21036
+ aboutNodeId: op.artifactId,
21037
+ aboutWorkId: null,
21038
+ raisedByNodeId: null,
21039
+ authorActorId: _actor,
21040
+ handActorId: null,
21041
+ recipients: [],
21042
+ attachments: []
21043
+ });
20712
21044
  applied++;
20713
21045
  break;
20714
21046
  case "edit": {
@@ -20716,7 +21048,7 @@ var init_kernel_bridge = __esm({
20716
21048
  const opReviewers = op.reviewers;
20717
21049
  const editReviewRoles = op.reviewRoles;
20718
21050
  if (artId && (opReviewers || editReviewRoles !== void 0)) {
20719
- const art = this.model.artifacts.get(artId);
21051
+ const art = artifacts.get(artId);
20720
21052
  const effectiveRoles = editReviewRoles !== void 0 ? editReviewRoles === null ? void 0 : editReviewRoles : art?.reviewRoles;
20721
21053
  const effectiveExplicit = opReviewers ?? (art?.reviewers ?? []).map((r) => ({ actor: r.actor, source: r.source }));
20722
21054
  const { reviewers: nextReqs, staffingIssues } = resolveNodeReviewers({
@@ -20730,13 +21062,13 @@ var init_kernel_bridge = __esm({
20730
21062
  reviewerStaffing.push({ nodeId: artId, type: art?.type ?? "", role: issue2.role, from: issue2.from });
20731
21063
  console.warn(`[edit] ${artId} \u7684\u300C${issue2.role}\u300D\u8BC4\u5BA1\u95F8\u5728\u5185\u6838 roleIndex \u91CC\u6CA1\u6709\u6301\u6709\u8005\u2014\u2014\u8BE5\u6876\u6682\u7F3A\u3002`);
20732
21064
  }
20733
- await this.commit({
21065
+ await queue({
20734
21066
  companyId: "",
20735
- workorderId: this.wo(artId),
21067
+ workorderId: woFor(artId),
20736
21068
  actorId: _actor,
20737
21069
  event: {
20738
21070
  kind: "plan.update_review_requirements",
20739
- workorderId: this.wo(artId),
21071
+ workorderId: woFor(artId),
20740
21072
  nodeId: artId,
20741
21073
  reviewers: nextReqs
20742
21074
  }
@@ -20745,15 +21077,15 @@ var init_kernel_bridge = __esm({
20745
21077
  const opTitle = op.title;
20746
21078
  const opDescription = op.description;
20747
21079
  if (artId && (opTitle !== void 0 || opDescription !== void 0)) {
20748
- await this.commit({
21080
+ await queue({
20749
21081
  companyId: "",
20750
- workorderId: this.wo(artId),
21082
+ workorderId: woFor(artId),
20751
21083
  actorId: _actor,
20752
21084
  event: {
20753
21085
  kind: "plan.update_spec",
20754
- workorderId: this.wo(artId),
21086
+ workorderId: woFor(artId),
20755
21087
  nodeId: artId,
20756
- spec: opDescription ?? this.model.artifacts.get(artId)?.description ?? null,
21088
+ spec: opDescription ?? artifacts.get(artId)?.description ?? null,
20757
21089
  ...opTitle !== void 0 ? { title: opTitle } : {},
20758
21090
  policy: "auto"
20759
21091
  }
@@ -20761,21 +21093,30 @@ var init_kernel_bridge = __esm({
20761
21093
  }
20762
21094
  const fields = op.fields;
20763
21095
  if (artId && fields) {
20764
- const editDef = this.schema.get(this.model.artifacts.get(artId)?.type ?? "");
21096
+ const editDef = this.schema.get(artifacts.get(artId)?.type ?? "");
20765
21097
  const validated = editDef?.fieldDefs ? validateArtifactFields(fields, editDef.fieldDefs, "edit") : fields;
20766
- await this.commit({
21098
+ await queue({
20767
21099
  companyId: "",
20768
- workorderId: this.wo(artId),
21100
+ workorderId: woFor(artId),
20769
21101
  actorId: _actor,
20770
21102
  event: {
20771
21103
  kind: "plan.update_fields",
20772
- workorderId: this.wo(artId),
21104
+ workorderId: woFor(artId),
20773
21105
  nodeId: artId,
20774
21106
  fields: validated ?? {},
20775
21107
  merge: true
20776
21108
  }
20777
21109
  });
20778
21110
  }
21111
+ const previous3 = artifacts.get(artId);
21112
+ if (previous3) artifacts.set(artId, {
21113
+ ...previous3,
21114
+ ...opTitle !== void 0 ? { title: opTitle } : {},
21115
+ ...opDescription !== void 0 ? { description: opDescription } : {},
21116
+ ...fields ? { fields: { ...previous3.fields, ...fields } } : {},
21117
+ ...editReviewRoles !== void 0 ? { reviewRoles: editReviewRoles ?? void 0 } : {},
21118
+ ...opReviewers ? { reviewers: opReviewers } : {}
21119
+ });
20779
21120
  applied++;
20780
21121
  break;
20781
21122
  }
@@ -20784,11 +21125,16 @@ var init_kernel_bridge = __esm({
20784
21125
  if (!escOp.escalationId) {
20785
21126
  throw new Error(`resolveEscalation op \u7F3A escalationId\uFF08${escOp.artifactId}\uFF09\u2014\u2014\u4E0D\u652F\u6301\u6574\u8282\u70B9\u5168\u6E05\uFF0C\u8BF7\u6307\u660E\u5173\u54EA\u4E00\u6761`);
20786
21127
  }
20787
- await this.resolveEscalation({
20788
- artifactId: escOp.artifactId,
20789
- actor: _actor,
20790
- escalationId: escOp.escalationId,
20791
- ...escOp.reason !== void 0 ? { reason: escOp.reason } : {}
21128
+ const escalation = (this.model.escalations.get(escOp.artifactId) ?? []).find((e) => e.escalationId === escOp.escalationId);
21129
+ if (!escalation) throw new Error(`escalation not found: ${escOp.escalationId}`);
21130
+ await enqueue({
21131
+ kind: "issue.resolve",
21132
+ workorderId: woFor(escOp.artifactId),
21133
+ issueId: escOp.escalationId,
21134
+ state: "resolved",
21135
+ by: _actor,
21136
+ note: escOp.reason ?? null,
21137
+ viaWorkId: null
20792
21138
  });
20793
21139
  applied++;
20794
21140
  break;
@@ -20798,7 +21144,7 @@ var init_kernel_bridge = __esm({
20798
21144
  break;
20799
21145
  }
20800
21146
  }
20801
- await this.commit({
21147
+ await queue({
20802
21148
  companyId: "",
20803
21149
  workorderId: wid,
20804
21150
  actorId: _actor,
@@ -20813,17 +21159,22 @@ var init_kernel_bridge = __esm({
20813
21159
  activateWorkorder: true
20814
21160
  }
20815
21161
  });
21162
+ if (unsupported.length) throw new KernelError(`\u6574\u6279\u672A\u843D\u5730\uFF1B\u4EE5\u4E0B op \u672C\u5F15\u64CE\u4E0D\u652F\u6301\uFF1A${unsupported.join("\u3001")}`, "unsupported-op");
21163
+ await this.ensureWorkorder(wid, "", _actor);
21164
+ await this.bus.submitBatchAndProcess(events);
21165
+ for (const event of events) {
21166
+ this.trackedWorkorders.add(event.workorderId);
21167
+ this.markWorkorderDirty(event.workorderId);
21168
+ }
21169
+ await this.refreshTracked();
20816
21170
  await this.syncFromBus(wid);
21171
+ for (const id of new Set(plan.ops.filter((op) => op.action === "resolveEscalation").map((op) => op.artifactId))) {
21172
+ await this.releaseIfStillHeld(id, _actor);
21173
+ }
20817
21174
  if (typeof plan.resolvesProposal === "string") {
20818
21175
  this.draftCards.delete(plan.resolvesProposal);
20819
21176
  await this.refreshModel();
20820
21177
  }
20821
- if (unsupported.length > 0) {
20822
- throw new KernelError(
20823
- `\u5DF2\u843D\u5730 ${applied} \u6761 op\uFF1B\u4EE5\u4E0B op \u672C\u5F15\u64CE\u4E0D\u652F\u6301\u3001\u672A\u6267\u884C\uFF1A${unsupported.join("\u3001")}\uFF08pin \u5DF2\u5E9F\u5F03\u2014\u2014\u4E0B\u6E38\u7248\u672C\u7531\u9A8C\u6536\u4E0A\u6E38\u81EA\u52A8\u63A8\u8FDB\uFF09`,
20824
- "unsupported-op"
20825
- );
20826
- }
20827
21178
  return {
20828
21179
  interventionId: `intervention:${(0, import_node_crypto3.randomUUID)()}`,
20829
21180
  applied,
@@ -20875,67 +21226,6 @@ var init_chat_system_message = __esm({
20875
21226
  }
20876
21227
  });
20877
21228
 
20878
- // ../server/src/build-info.ts
20879
- function findRepoRoot() {
20880
- let dir;
20881
- try {
20882
- dir = path.dirname((0, import_node_url2.fileURLToPath)(__esm_import_meta_url));
20883
- } catch {
20884
- return null;
20885
- }
20886
- for (let i = 0; i < 12; i++) {
20887
- if (fs.existsSync(path.join(dir, ".git"))) return dir;
20888
- const parent = path.dirname(dir);
20889
- if (parent === dir) break;
20890
- dir = parent;
20891
- }
20892
- return null;
20893
- }
20894
- function git(root, args) {
20895
- try {
20896
- const out = (0, import_node_child_process.execFileSync)("git", args, {
20897
- cwd: root,
20898
- encoding: "utf8",
20899
- timeout: 3e3,
20900
- stdio: ["ignore", "pipe", "ignore"]
20901
- });
20902
- const v2 = out.trim();
20903
- return v2 || null;
20904
- } catch {
20905
- return null;
20906
- }
20907
- }
20908
- function getBuildInfo() {
20909
- if (cached) return cached;
20910
- const envSha = process.env["OASIS_GIT_SHA"]?.trim();
20911
- const envBranch = process.env["OASIS_GIT_BRANCH"]?.trim();
20912
- let gitSha = envSha || null;
20913
- let gitBranch = envBranch || null;
20914
- if (!gitSha || !gitBranch) {
20915
- const root = findRepoRoot();
20916
- if (root) {
20917
- if (!gitSha) gitSha = git(root, ["rev-parse", "HEAD"]);
20918
- if (!gitBranch) {
20919
- const b2 = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
20920
- gitBranch = b2 === "HEAD" ? null : b2;
20921
- }
20922
- }
20923
- }
20924
- cached = { gitSha, gitBranch, startedAt: STARTED_AT };
20925
- return cached;
20926
- }
20927
- var import_node_child_process, fs, path, import_node_url2, STARTED_AT, cached;
20928
- var init_build_info = __esm({
20929
- "../server/src/build-info.ts"() {
20930
- "use strict";
20931
- import_node_child_process = require("node:child_process");
20932
- fs = __toESM(require("node:fs"), 1);
20933
- path = __toESM(require("node:path"), 1);
20934
- import_node_url2 = require("node:url");
20935
- STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
20936
- }
20937
- });
20938
-
20939
21229
  // ../server/src/facades/api/router.ts
20940
21230
  var ApiError, Router;
20941
21231
  var init_router = __esm({
@@ -21081,6 +21371,32 @@ async function saveArtifactContent(kernel, blobs, actor, input) {
21081
21371
  }
21082
21372
  return { ...view, contentRef, text: input.text };
21083
21373
  }
21374
+ async function readArtifactFile(kernel, blobs, artifactId, path41, expectedRef) {
21375
+ await kernel.refreshModel();
21376
+ const artifact = kernel.model.artifacts.get(artifactId);
21377
+ const output = artifact?.currentRev ? kernel.model.revisions.get(artifact.currentRev) : void 0;
21378
+ if (!artifact || !output) throw new ApiError(404, "NOT_FOUND", "\u6B64\u4EA7\u7269\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u539F\u6587\u4EF6");
21379
+ if (expectedRef && output.contentRef !== expectedRef) throw new ApiError(409, "CONTENT_CONFLICT", "\u6587\u4EF6\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00");
21380
+ let hash2 = output.contentRef;
21381
+ let name = artifact.title || artifactId;
21382
+ if (output.contentKind === "manifest") {
21383
+ if (!path41) throw new ApiError(400, "PATH_REQUIRED", "\u8BF7\u9009\u62E9\u6587\u4EF6\u5939\u4E2D\u7684\u5177\u4F53\u6587\u4EF6");
21384
+ const entry = parseManifest(await blobs.get(output.contentRef)).entries.find((e) => e.path === path41);
21385
+ if (!entry) throw new ApiError(404, "NOT_FOUND", "\u5305\u5185\u6587\u4EF6\u4E0D\u5B58\u5728");
21386
+ hash2 = entry.hash;
21387
+ name = entry.path;
21388
+ if (entry.size !== void 0 && entry.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
21389
+ } else if (output.contentKind !== "inline-blob") {
21390
+ throw new ApiError(404, "NOT_FOUND", "\u6B64\u4EA7\u7269\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u539F\u6587\u4EF6");
21391
+ }
21392
+ const meta = await blobs.meta?.(hash2);
21393
+ if (meta && meta.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
21394
+ try {
21395
+ return { bytes: await blobs.get(hash2), name, contentType: meta?.contentType };
21396
+ } catch {
21397
+ throw new ApiError(404, "NOT_FOUND", "\u5F53\u524D\u516C\u53F8\u4E2D\u627E\u4E0D\u5230\u8BE5\u6587\u4EF6\u6B63\u6587");
21398
+ }
21399
+ }
21084
21400
  var init_artifact_content2 = __esm({
21085
21401
  "../server/src/artifact-content.ts"() {
21086
21402
  "use strict";
@@ -21089,6 +21405,110 @@ var init_artifact_content2 = __esm({
21089
21405
  }
21090
21406
  });
21091
21407
 
21408
+ // ../server/src/file-http.ts
21409
+ async function serveFileRequest(req, res, url, engine) {
21410
+ try {
21411
+ if (req.method !== "GET" || url.pathname !== "/api/files/content") throw new ApiError(404, "NOT_FOUND", "\u6587\u4EF6\u63A5\u53E3\u4E0D\u5B58\u5728");
21412
+ const { bytes: bytes2, name, contentType } = await readArtifactFile(
21413
+ engine.kernel,
21414
+ engine.blobs,
21415
+ url.searchParams.get("artifact") ?? "",
21416
+ url.searchParams.get("path"),
21417
+ url.searchParams.get("ref")
21418
+ );
21419
+ if (bytes2.length > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
21420
+ res.writeHead(200, {
21421
+ "content-type": contentType ?? sniffContentType(bytes2) ?? "application/octet-stream",
21422
+ "content-length": String(bytes2.length),
21423
+ "cache-control": "private, no-store",
21424
+ "x-content-type-options": "nosniff",
21425
+ "content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(name.split(/[\\/]/).pop() || "file").replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`
21426
+ }).end(Buffer.from(bytes2));
21427
+ } catch (error2) {
21428
+ if (res.destroyed) return;
21429
+ const status = error2 instanceof ApiError ? error2.status : 500;
21430
+ if (!req.complete) {
21431
+ res.setHeader("connection", "close");
21432
+ req.resume();
21433
+ }
21434
+ res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store" }).end(JSON.stringify({
21435
+ error: {
21436
+ code: error2 instanceof ApiError ? error2.code : "FILE_READ_FAILED",
21437
+ message: error2 instanceof ApiError ? error2.message : "\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"
21438
+ }
21439
+ }));
21440
+ }
21441
+ }
21442
+ var init_file_http = __esm({
21443
+ "../server/src/file-http.ts"() {
21444
+ "use strict";
21445
+ init_src();
21446
+ init_artifact_content2();
21447
+ init_router();
21448
+ }
21449
+ });
21450
+
21451
+ // ../server/src/build-info.ts
21452
+ function findRepoRoot() {
21453
+ let dir;
21454
+ try {
21455
+ dir = path.dirname((0, import_node_url2.fileURLToPath)(__esm_import_meta_url));
21456
+ } catch {
21457
+ return null;
21458
+ }
21459
+ for (let i = 0; i < 12; i++) {
21460
+ if (fs.existsSync(path.join(dir, ".git"))) return dir;
21461
+ const parent = path.dirname(dir);
21462
+ if (parent === dir) break;
21463
+ dir = parent;
21464
+ }
21465
+ return null;
21466
+ }
21467
+ function git(root, args) {
21468
+ try {
21469
+ const out = (0, import_node_child_process.execFileSync)("git", args, {
21470
+ cwd: root,
21471
+ encoding: "utf8",
21472
+ timeout: 3e3,
21473
+ stdio: ["ignore", "pipe", "ignore"]
21474
+ });
21475
+ const v2 = out.trim();
21476
+ return v2 || null;
21477
+ } catch {
21478
+ return null;
21479
+ }
21480
+ }
21481
+ function getBuildInfo() {
21482
+ if (cached) return cached;
21483
+ const envSha = process.env["OASIS_GIT_SHA"]?.trim();
21484
+ const envBranch = process.env["OASIS_GIT_BRANCH"]?.trim();
21485
+ let gitSha = envSha || null;
21486
+ let gitBranch = envBranch || null;
21487
+ if (!gitSha || !gitBranch) {
21488
+ const root = findRepoRoot();
21489
+ if (root) {
21490
+ if (!gitSha) gitSha = git(root, ["rev-parse", "HEAD"]);
21491
+ if (!gitBranch) {
21492
+ const b2 = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
21493
+ gitBranch = b2 === "HEAD" ? null : b2;
21494
+ }
21495
+ }
21496
+ }
21497
+ cached = { gitSha, gitBranch, startedAt: STARTED_AT };
21498
+ return cached;
21499
+ }
21500
+ var import_node_child_process, fs, path, import_node_url2, STARTED_AT, cached;
21501
+ var init_build_info = __esm({
21502
+ "../server/src/build-info.ts"() {
21503
+ "use strict";
21504
+ import_node_child_process = require("node:child_process");
21505
+ fs = __toESM(require("node:fs"), 1);
21506
+ path = __toESM(require("node:path"), 1);
21507
+ import_node_url2 = require("node:url");
21508
+ STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
21509
+ }
21510
+ });
21511
+
21092
21512
  // ../server/src/command-policy.ts
21093
21513
  function classifyCommand(command) {
21094
21514
  return COMMAND_POLICY[command] ?? { risk: "direct" };
@@ -21115,7 +21535,7 @@ var init_command_policy = __esm({
21115
21535
  // 重复关同一条是幂等的(kernel.resolveEscalation 内部按 escalationId 去重)
21116
21536
  // 「关错=静默停摆」的唯一防线是人在卡上一眼看出「关的是哪一条」——escalationId 与 reason 摘要必须上卡。
21117
21537
  // a 里 escalationId/artifactId 由 server.ts 起草时冻进 commandArgs(已四级解析),保证是真值、非 `?`。
21118
- effect: (a) => `\u5173\u95ED\u4E0A\u62A5 ${shortId(a.escalationId)}\uFF08\u8282\u70B9 ${shortId(a.artifactId)}${typeof a.reason === "string" ? `\uFF0C\u7406\u7531\uFF1A${a.reason.slice(0, 60)}` : ""}\uFF09`
21538
+ effect: (a) => typeof a.gapId === "string" ? `\u91C7\u7528\u65B9\u6848\uFF1A${String(a.reason ?? "")}\uFF1B\u89E3\u51B3\u7F3A\u53E3 ${a.gapId}\uFF1B\u5173\u95ED\u5173\u8054\u4E0A\u62A5 ${String(a.escalationId)}\uFF1B\u7EE7\u7EED\u6267\u884C\uFF1A\u662F\uFF08\u4ECD\u987B\u6EE1\u8DB3\u5176\u4ED6\u963B\u585E\u6761\u4EF6\uFF09` : `\u5173\u95ED\u4E0A\u62A5 ${shortId(a.escalationId)}\uFF08\u8282\u70B9 ${shortId(a.artifactId)}${typeof a.reason === "string" ? `\uFF0C\u7406\u7531\uFF1A${a.reason}` : ""}\uFF09`
21119
21539
  },
21120
21540
  gc: {
21121
21541
  risk: "forbidden",
@@ -28452,6 +28872,16 @@ var init_chat_item_ledger = __esm({
28452
28872
  version: writeVersion
28453
28873
  });
28454
28874
  row.ord = created.ord;
28875
+ try {
28876
+ this.deps.onPersisted?.({
28877
+ itemId: id,
28878
+ ord: created.ord,
28879
+ version: writeVersion,
28880
+ role: input.role,
28881
+ text: input.text
28882
+ });
28883
+ } catch {
28884
+ }
28455
28885
  }, `create:${input.kind}`);
28456
28886
  return id;
28457
28887
  }
@@ -28580,17 +29010,21 @@ async function openAssistantRunningRow(deps) {
28580
29010
  const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
28581
29011
  if (typeof deps.chatStore.appendMessage !== "function") return void 0;
28582
29012
  const id = (0, import_node_crypto16.randomUUID)();
29013
+ const at = now();
28583
29014
  try {
28584
29015
  await deps.chatStore.appendMessage({
28585
29016
  id,
28586
29017
  sessionId: deps.chatSessionId,
28587
29018
  role: "assistant",
28588
29019
  content: "",
28589
- createdAt: now(),
29020
+ createdAt: at,
28590
29021
  status: "running",
28591
29022
  ...deps.runId ? { runId: deps.runId } : {},
28592
29023
  ...deps.turnId ? { turnId: deps.turnId } : {}
28593
29024
  });
29025
+ if (typeof deps.chatStore.updateSession === "function") {
29026
+ await deps.chatStore.updateSession(deps.chatSessionId, { touchedAt: at }).catch(() => void 0);
29027
+ }
28594
29028
  return id;
28595
29029
  } catch {
28596
29030
  return void 0;
@@ -141794,7 +142228,7 @@ function buildNodeTimeline(artifactId, snap, runIdByTarget) {
141794
142228
  if (w2.nodeId !== artifactId) continue;
141795
142229
  if (isReplyWork(w2)) continue;
141796
142230
  nodeWorkIds.add(w2.id);
141797
- const status = w2.status ?? (w2.retryAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
142231
+ const status = w2.status ?? (w2.retryAt && w2.deadAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
141798
142232
  items.push({
141799
142233
  type: "work",
141800
142234
  id: w2.id,
@@ -159475,6 +159909,58 @@ var init_live_chat = __esm({
159475
159909
  }
159476
159910
  }
159477
159911
  }
159912
+ /**
159913
+ * 把**用户自己那条消息**推上 v3 流(ADR-0510 D2 的收口)。
159914
+ *
159915
+ * ## 为什么需要
159916
+ *
159917
+ * 用户点发送时前端先画一条「乐观条」,它**没有服务端展示序**(D2:不许前端发明号),
159918
+ * 渲染时钉在列表末尾。这条规矩对「刚发完、还没有任何回复」是对的。
159919
+ *
159920
+ * 但服务端落账本时**立刻**给了这条 user item 一个 ord(它是这一轮第一个建的行,号最小),
159921
+ * 而这个号此前**只走快照**——`emitV3` 只服务 provider 事件(`server.ts` 那句注释:
159922
+ * 「不走 v3 流帧(那条只服务 assistant)」)。于是有个几秒的窗口:
159923
+ * agent 的正文带着更大的号从直播流到了、进入「有号」段,而用户那条还在「无号」段被钉在最后
159924
+ * ⇒ **agent 的回复显示在用户消息上面**,等下一次快照才跳回去。
159925
+ * 2026-09-09 用户现场原话:「过几秒钟页面刷新一下,agent 回复内容会回到用户消息下面」。
159926
+ *
159927
+ * 修法就是把这条也推上去:前端按 itemId(`oasis-user-optim:<csid>`,与乐观桶位同名)覆盖,
159928
+ * 拿到真号立刻归位,窗口从「到下一次快照」压到「一次 INSERT 往返」。
159929
+ *
159930
+ * ## 为什么不在前端算
159931
+ *
159932
+ * 试过的三种都被证伪(见 `chat-items-merge.ts` 里那段注释)。这次也不能用「记下创建时的最大号」:
159933
+ * 空对话首次发送时那个最大号是 0,随后到达的历史(号更大)会全部排到乐观条**后面**——
159934
+ * 正是 ADR D9 点名必须验的「空桶先发送」那一档。号只能由服务端给。
159935
+ *
159936
+ * ## 拿不到轮就静默跳过
159937
+ *
159938
+ * 本方法由账本的落库链回调触发,而那一刻这条会话的 live 轮可能还没 `start`
159939
+ * (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
159940
+ * 前端退回「等下一次快照」,与修复前一致,不会更坏。
159941
+ */
159942
+ publishUserItem(chatSessionId, item) {
159943
+ const turn = this.turns.get(chatSessionId);
159944
+ if (!turn) return false;
159945
+ const payload = { role: "user", text: item.text };
159946
+ if (item.clientSubmitId) payload.clientSubmitId = item.clientSubmitId;
159947
+ if (item.attachments?.length) payload.attachments = item.attachments;
159948
+ const frame = {
159949
+ protocolVersion: 3,
159950
+ streamId: turn.liveStreamId,
159951
+ turnId: this.wireTurnId(turn),
159952
+ itemId: item.itemId,
159953
+ itemType: "message",
159954
+ operation: "set_text",
159955
+ itemVersion: item.version,
159956
+ ...item.ord !== null ? { ord: item.ord } : {},
159957
+ offset: 0,
159958
+ payload
159959
+ };
159960
+ this.flushPendingV3(turn);
159961
+ this.publishV3(turn, this.assignV3Seq(turn, frame));
159962
+ return true;
159963
+ }
159478
159964
  emitV3TurnTerminal(turn, status) {
159479
159965
  if (status !== "done" && status !== "error") return;
159480
159966
  const operation = status === "done" ? "turn_completed" : "turn_failed";
@@ -194349,8 +194835,15 @@ function isTextual(contentType) {
194349
194835
  const t = contentType.toLowerCase();
194350
194836
  return t.startsWith("text/") || t.includes("json") || t.includes("xml") || t.includes("yaml") || t.includes("javascript");
194351
194837
  }
194352
- function toPrepared(rel, source, read) {
194353
- const base = { rel, source, size: read.size, contentType: read.contentType };
194838
+ function toPrepared(rel, source, read, src) {
194839
+ const base = {
194840
+ rel,
194841
+ source,
194842
+ size: read.size,
194843
+ contentType: read.contentType,
194844
+ ...src ? { srcSize: src.size } : {},
194845
+ ...src?.mtime ? { srcMtime: src.mtime } : {}
194846
+ };
194354
194847
  if (!isTextual(read.contentType)) return { ...base, base64: read.contentBase64 };
194355
194848
  return { ...base, text: Buffer.from(read.contentBase64, "base64").toString("utf8") };
194356
194849
  }
@@ -194387,7 +194880,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
194387
194880
  if (!lenient) throw error2;
194388
194881
  skipped.push({ source, code: error2.code, message: error2.message });
194389
194882
  };
194390
- if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0 };
194883
+ if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
194391
194884
  if (!port) {
194392
194885
  const error2 = new DelegationFileError(
194393
194886
  "FILE_UNREADABLE",
@@ -194396,7 +194889,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
194396
194889
  );
194397
194890
  if (!lenient) throw error2;
194398
194891
  for (const source of wanted) skipped.push({ source, code: error2.code, message: error2.message });
194399
- return { prepared: [], skipped, totalBytes: 0 };
194892
+ return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
194400
194893
  }
194401
194894
  let accepted = wanted;
194402
194895
  if (wanted.length > limits.maxFiles) {
@@ -194474,10 +194967,10 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
194474
194967
  used.add(rel);
194475
194968
  prepared.push(toPrepared(rel, source, read));
194476
194969
  }
194477
- return { prepared, skipped, totalBytes };
194970
+ return { prepared, skipped, totalBytes, unchanged: [] };
194478
194971
  }
194479
- async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS) {
194480
- const out = { prepared: [], skipped: [], totalBytes: 0 };
194972
+ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS, known) {
194973
+ const out = { prepared: [], skipped: [], totalBytes: 0, unchanged: [] };
194481
194974
  if (!port) return out;
194482
194975
  if (port.locate) {
194483
194976
  const located = await port.locate(child).catch((err) => ({
@@ -194537,7 +195030,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
194537
195030
  for (const entry of level.entries) {
194538
195031
  const childPath = `${dir}/${entry.name}`;
194539
195032
  if (entry.type === "dir") queue.push(childPath);
194540
- else files.push({ rel: childPath, size: entry.size });
195033
+ else files.push({ rel: childPath, size: entry.size, ...entry.mtime ? { mtime: entry.mtime } : {} });
194541
195034
  }
194542
195035
  }
194543
195036
  for (const dir of queue) {
@@ -194553,6 +195046,12 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
194553
195046
  out.skipped.push({ source: file.rel, code: "PATH_REJECTED", message: "\u8DEF\u5F84\u4E0D\u5408\u6CD5\uFF0C\u6CA1\u642C" });
194554
195047
  continue;
194555
195048
  }
195049
+ const target = `${DELEGATION_OUTBOUND_DIR}/${dirName}/${relative5}`;
195050
+ const seen = known?.get(target);
195051
+ if (seen && file.mtime && seen.size === file.size && seen.mtime === file.mtime) {
195052
+ out.unchanged.push(target);
195053
+ continue;
195054
+ }
194556
195055
  if (out.prepared.length >= limits.maxFiles) {
194557
195056
  out.skipped.push({ source: file.rel, code: "FILE_QUOTA_EXCEEDED", message: `\u8D85\u8FC7\u4E00\u6B21 ${limits.maxFiles} \u4E2A\u7684\u4E0A\u9650\uFF0C\u6CA1\u642C` });
194558
195057
  continue;
@@ -194571,7 +195070,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
194571
195070
  continue;
194572
195071
  }
194573
195072
  out.totalBytes += read.size;
194574
- out.prepared.push(toPrepared(`${DELEGATION_OUTBOUND_DIR}/${dirName}/${relative5}`, file.rel, read));
195073
+ out.prepared.push(toPrepared(target, file.rel, read, { size: file.size, ...file.mtime ? { mtime: file.mtime } : {} }));
194575
195074
  }
194576
195075
  return out;
194577
195076
  }
@@ -194628,6 +195127,31 @@ function digestOf(text5) {
194628
195127
  const line = text5.trim().replace(/\s+/g, " ");
194629
195128
  return line.length > DIGEST_CHARS ? `${line.slice(0, DIGEST_CHARS)}\u2026` : line;
194630
195129
  }
195130
+ function childRoundMessageIds(childSessionId, round) {
195131
+ const suffix = round <= 1 ? "" : `:${round}`;
195132
+ return {
195133
+ inputId: deterministicUuid(`delegation-input:${childSessionId}${suffix}`),
195134
+ placeholderId: deterministicUuid(`delegation-placeholder:${childSessionId}${suffix}`)
195135
+ };
195136
+ }
195137
+ async function seedChildRoundRows(chatStore, childSessionId, round, prompt2, now) {
195138
+ const ids2 = childRoundMessageIds(childSessionId, round);
195139
+ await chatStore.appendMessageOnce({
195140
+ id: ids2.inputId,
195141
+ sessionId: childSessionId,
195142
+ role: "user",
195143
+ content: prompt2,
195144
+ createdAt: now
195145
+ });
195146
+ await chatStore.appendMessageOnce({
195147
+ id: ids2.placeholderId,
195148
+ sessionId: childSessionId,
195149
+ role: "assistant",
195150
+ content: "",
195151
+ status: "running",
195152
+ createdAt: now
195153
+ });
195154
+ }
194631
195155
  var import_node_crypto32, DelegationError, TITLE_TASK_CHARS, DIGEST_CHARS, PENDING_QUEUE_OWNER, DISPATCH_LEASE_MS, CONSECUTIVE_FAIL_LIMIT, DelegationService;
194632
195156
  var init_service3 = __esm({
194633
195157
  "../server/src/domains/delegations/service.ts"() {
@@ -195053,21 +195577,7 @@ var init_service3 = __esm({
195053
195577
  createdAt: now
195054
195578
  });
195055
195579
  }
195056
- await chatStore.appendMessageOnce({
195057
- id: deterministicUuid(`delegation-input:${record8.childSessionId}`),
195058
- sessionId: record8.childSessionId,
195059
- role: "user",
195060
- content: task,
195061
- createdAt: now
195062
- });
195063
- await chatStore.appendMessageOnce({
195064
- id: deterministicUuid(`delegation-placeholder:${record8.childSessionId}`),
195065
- sessionId: record8.childSessionId,
195066
- role: "assistant",
195067
- content: "",
195068
- status: "running",
195069
- createdAt: now
195070
- });
195580
+ await seedChildRoundRows(chatStore, record8.childSessionId, 1, task, now);
195071
195581
  }
195072
195582
  /**
195073
195583
  * 去程文件:读父会话工作区、翻成 §8.2 的同步错误码。
@@ -195227,7 +195737,7 @@ var init_service3 = __esm({
195227
195737
  settledAt: failedAt
195228
195738
  }).catch(() => null);
195229
195739
  const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
195230
- await chatStore?.updateMessage(deterministicUuid(`delegation-placeholder:${record8.childSessionId}`), {
195740
+ await chatStore?.updateMessage(childRoundMessageIds(record8.childSessionId, record8.roundSummaries.length + 1).placeholderId, {
195231
195741
  content: `\u6D3E\u53D1\u6CA1\u80FD\u8D77\u6765\uFF1A${message}`,
195232
195742
  status: "error",
195233
195743
  completedAt: failedAt
@@ -195276,7 +195786,7 @@ var init_service3 = __esm({
195276
195786
  const pending = await store.countPendingTouches(fresh.id).catch(() => 0);
195277
195787
  const stopLoss = consecutiveFails >= CONSECUTIVE_FAIL_LIMIT;
195278
195788
  const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
195279
- await chatStore?.updateMessage(deterministicUuid(`delegation-placeholder:${fresh.childSessionId}`), {
195789
+ await chatStore?.updateMessage(childRoundMessageIds(fresh.childSessionId, round).placeholderId, {
195280
195790
  content: text5 || (failureKind ? `\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u5B8C\uFF1A${error2}` : ""),
195281
195791
  status: failureKind ? "error" : "done",
195282
195792
  completedAt: at
@@ -195288,11 +195798,13 @@ var init_service3 = __esm({
195288
195798
  });
195289
195799
  return;
195290
195800
  }
195291
- const settled = await store.appendRoundSummary(fresh.id, newSummary, {
195801
+ const settledSeqAtRound = fresh.settledSeq + 1;
195802
+ const settledSummary = { ...newSummary, settledSeq: settledSeqAtRound };
195803
+ const settled = await store.appendRoundSummary(fresh.id, settledSummary, {
195292
195804
  consecutiveFails,
195293
195805
  state: failureKind ? "failed" : "done",
195294
195806
  failureKind: failureKind ?? null,
195295
- settledSeq: fresh.settledSeq + 1,
195807
+ settledSeq: settledSeqAtRound,
195296
195808
  settledAt: at
195297
195809
  }).catch(() => null);
195298
195810
  if (settled) await this.notifySettled(settled, companyId);
@@ -195321,17 +195833,19 @@ var init_service3 = __esm({
195321
195833
  const record8 = await store.getByChildSession(childSessionId).catch(() => null);
195322
195834
  if (!record8 || record8.state !== "running") return record8;
195323
195835
  const at = this.now();
195836
+ const settledSeqAtRound = record8.settledSeq + 1;
195324
195837
  const newSummary = {
195325
195838
  round: (record8.roundSummaries[record8.roundSummaries.length - 1]?.round ?? 0) + 1,
195326
195839
  text: `\u8FD9\u4E00\u8F6E\u88AB\u65F6\u949F\u95ED\u5408\uFF1A${reason}`,
195327
- at
195840
+ at,
195841
+ settledSeq: settledSeqAtRound
195328
195842
  };
195329
195843
  const settled = await store.appendRoundSummary(record8.id, newSummary, {
195330
195844
  state: "failed",
195331
195845
  // 退出帧丢失后被时钟闭合 = `interrupted`(§9),不是 `not-started`——它确实起来过。
195332
195846
  failureKind: "interrupted",
195333
195847
  consecutiveFails: record8.consecutiveFails + 1,
195334
- settledSeq: record8.settledSeq + 1,
195848
+ settledSeq: settledSeqAtRound,
195335
195849
  settledAt: at
195336
195850
  }).catch(() => null);
195337
195851
  if (settled) await this.notifySettled(settled, companyId);
@@ -195417,6 +195931,15 @@ var init_service3 = __esm({
195417
195931
  onDispatched: async () => {
195418
195932
  await queue.confirm();
195419
195933
  await store.markTouchesConsumed(record8.id, touches.map((touch) => touch.messageId), this.now());
195934
+ await seedChildRoundRows(
195935
+ chatStore,
195936
+ record8.childSessionId,
195937
+ record8.roundSummaries.length + 1,
195938
+ body2,
195939
+ this.now()
195940
+ ).catch((err) => {
195941
+ console.warn(`[delegation] \u5B50\u4F1A\u8BDD\u8865\u8F6E\u6B21\u884C\u5931\u8D25\uFF08${record8.id} round=${record8.roundSummaries.length + 1}\uFF09: ${String(err)}`);
195942
+ });
195420
195943
  },
195421
195944
  /**
195422
195945
  * **没送到就把正文放回可读态**(见 `dispatchExpert` 的 `onNotDispatched`)。
@@ -195515,6 +196038,10 @@ var init_service3 = __esm({
195515
196038
  const inbound = await this.readInboundFiles(parent, files);
195516
196039
  const inboundBundle = toBundleFiles(inbound.prepared);
195517
196040
  let delivered = false;
196041
+ let deliveryReasonCode;
196042
+ if (!this.options.appendToChild) {
196043
+ deliveryReasonCode = "no-append-port";
196044
+ }
195518
196045
  if (this.options.appendToChild) {
195519
196046
  const result = await this.options.appendToChild(record8.childSessionId, {
195520
196047
  // 文件是静默落盘的,append 这条路没有 TASK.md 可改——**必须自己在正文里说一声**,
@@ -195530,6 +196057,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
195530
196057
  ...Object.keys(inboundBundle.binaryFiles).length ? { binaryFiles: inboundBundle.binaryFiles } : {}
195531
196058
  }).catch(() => ({ accepted: false, reason: "append-threw" }));
195532
196059
  delivered = result.accepted === true;
196060
+ if (!delivered) deliveryReasonCode = result.reason ?? "rejected";
195533
196061
  }
195534
196062
  if (!delivered) {
195535
196063
  if (chatStore.enqueuePendingMessage) {
@@ -195588,6 +196116,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
195588
196116
  }
195589
196117
  return {
195590
196118
  delivered,
196119
+ ...delivered ? {} : { deliveryReasonCode: deliveryReasonCode ?? "rejected" },
195591
196120
  startedRound,
195592
196121
  pendingInputs: await this.pendingInputs(store, record8.id),
195593
196122
  // 起了新的一轮 = 快照与文件都随那一轮重新送过去了,没有「等下一轮再刷新」这回事。
@@ -195710,6 +196239,34 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
195710
196239
  }
195711
196240
  });
195712
196241
 
196242
+ // ../server/src/domains/delegations/continue-outcome.ts
196243
+ function describeContinueOutcome(input) {
196244
+ if (input.delivered) return { outcome: "inserted" };
196245
+ if (input.startedRound) return { outcome: "started-round" };
196246
+ const code2 = input.deliveryReasonCode ?? "rejected";
196247
+ return {
196248
+ outcome: "queued",
196249
+ // 认不出的码**原样带出来**,不折成「未知原因」——排障时那串字符本身就是线索。
196250
+ reason: REASON_TEXT[code2] ?? `\u672A\u80FD\u63D2\u5165\uFF08${code2}\uFF09`,
196251
+ reasonCode: code2
196252
+ };
196253
+ }
196254
+ var REASON_TEXT;
196255
+ var init_continue_outcome = __esm({
196256
+ "../server/src/domains/delegations/continue-outcome.ts"() {
196257
+ "use strict";
196258
+ REASON_TEXT = {
196259
+ "no-live-turn": "\u4E13\u5BB6\u6B64\u523B\u6CA1\u6709\u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E\uFF08\u591A\u534A\u662F\u521A\u6536\u5C3E\u3001\u4E0B\u4E00\u8F6E\u8FD8\u6CA1\u8D77\u6765\uFF09",
196260
+ unsupported: "\u8FD9\u4E2A\u4E13\u5BB6\u7528\u7684\u6A21\u578B\u4E0D\u652F\u6301\u4E2D\u9014\u63D2\u8BDD\uFF08\u8981\u7B49\u5B83\u8FD9\u4E00\u8F6E\u8DD1\u5B8C\uFF09",
196261
+ "session-closing": "\u4E13\u5BB6\u90A3\u4E00\u8F6E\u6B63\u5728\u6536\u5C3E\uFF0C\u8FD9\u53E5\u8BDD\u6765\u665A\u4E86\u4E00\u6B65",
196262
+ "turn-finished": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u4E13\u5BB6\u90A3\u4E00\u8F6E\u5DF2\u7ECF\u6536\u5C3E\u4E86",
196263
+ rejected: "\u4E13\u5BB6\u90A3\u4E00\u4FA7\u62D2\u6536\u4E86\u8FD9\u6B21\u63D2\u5165",
196264
+ "append-threw": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u51FA\u9519\uFF08\u7F51\u7EDC\u6216\u8282\u70B9\u5F02\u5E38\uFF09",
196265
+ "no-append-port": "\u8FD9\u5957\u90E8\u7F72\u6CA1\u6709\u63A5\u4E2D\u9014\u63D2\u5165\u901A\u9053"
196266
+ };
196267
+ }
196268
+ });
196269
+
195713
196270
  // ../server/src/domains/delegations/routes.ts
195714
196271
  function trustedDelegationInvocationId(body2, turn) {
195715
196272
  const scope = turn?.artifactId ?? turn?.dispatchId;
@@ -195778,8 +196335,16 @@ function delegationRoutes(service) {
195778
196335
  const text5 = requireString(raw["text"], "--text");
195779
196336
  const files = requireStringArray(raw["files"], "--file");
195780
196337
  try {
195781
- await service.continueDelegation(req.params.id, text5, callerOf(req), files);
195782
- return { status: 204, body: void 0 };
196338
+ const res = await service.continueDelegation(req.params.id, text5, callerOf(req), files);
196339
+ const view = describeContinueOutcome(res);
196340
+ const body2 = {
196341
+ outcome: view.outcome,
196342
+ ...view.reason ? { reason: view.reason } : {},
196343
+ ...view.reasonCode ? { reasonCode: view.reasonCode } : {},
196344
+ pendingInputs: res.pendingInputs,
196345
+ ...res.attachedFiles.length ? { attachedFiles: res.attachedFiles } : {}
196346
+ };
196347
+ return { status: 200, body: body2 };
195783
196348
  } catch (error2) {
195784
196349
  rethrow(error2);
195785
196350
  }
@@ -195807,6 +196372,7 @@ var init_routes2 = __esm({
195807
196372
  "use strict";
195808
196373
  import_node_crypto33 = require("node:crypto");
195809
196374
  init_router();
196375
+ init_continue_outcome();
195810
196376
  init_service3();
195811
196377
  }
195812
196378
  });
@@ -196178,6 +196744,9 @@ function delegationReturnText(terminal, opts) {
196178
196744
  if (opts.movedPaths.length > 0) {
196179
196745
  lines.push("", "\u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6\uFF08\u5DF2\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...opts.movedPaths.map((p2) => `- ${p2}`));
196180
196746
  }
196747
+ if ((opts.unchangedPaths?.length ?? 0) > 0) {
196748
+ lines.push("", `\u53E6\u6709 ${opts.unchangedPaths.length} \u4E2A\u6587\u4EF6\u4E0E\u4E0A\u6B21\u4EA4\u56DE\u65F6\u9010\u5B57\u4E00\u81F4\uFF0C\u672C\u8F6E\u6CA1\u6709\u91CD\u4F20\uFF08\u4ECD\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u8DEF\u5F84\u4E0D\u53D8\uFF09\u3002`);
196749
+ }
196181
196750
  const locationFailures = opts.skipped.filter((s2) => s2.code !== void 0 && isLocationFailureCode(s2.code));
196182
196751
  const contentSkipped = opts.skipped.filter((s2) => s2.code === void 0 || !isLocationFailureCode(s2.code));
196183
196752
  if (locationFailures.length > 0) {
@@ -196217,17 +196786,21 @@ async function deliverDelegationReturn(deps, terminal) {
196217
196786
  };
196218
196787
  }
196219
196788
  const dirName = delegationDirName(record8.label, record8.expertActorId, record8.id);
196789
+ const known = delegationOutboxFingerprints(record8.roundSummaries);
196220
196790
  const outbound = await prepareOutboundFiles(
196221
196791
  deps.workdir,
196222
196792
  // 子会话与父会话同公司(§6.1.2 隔离二),所以这里的 companyId 取父会话那个。
196223
196793
  { chatSessionId: record8.childSessionId, ...parentSession.companyId ? { companyId: parentSession.companyId } : {} },
196224
- dirName
196794
+ dirName,
196795
+ void 0,
196796
+ known
196225
196797
  ).catch((err) => {
196226
196798
  log3(`[delegation-return] \u4EA7\u7269\u642C\u8FD0\u5931\u8D25\uFF08delegation=${record8.id}\uFF09: ${String(err)}`);
196227
196799
  return {
196228
196800
  prepared: [],
196229
196801
  skipped: [{ source: "outputs/", code: "INTERNAL", message: "\u642C\u8FD0\u5931\u8D25" }],
196230
196802
  totalBytes: 0,
196803
+ unchanged: [],
196231
196804
  listDirCode: "INTERNAL"
196232
196805
  };
196233
196806
  });
@@ -196247,8 +196820,18 @@ async function deliverDelegationReturn(deps, terminal) {
196247
196820
  const text5 = delegationReturnText(terminal, {
196248
196821
  movedPaths: bundleFiles.paths,
196249
196822
  skipped: outbound.skipped,
196250
- recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS
196823
+ recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS,
196824
+ unchangedPaths: outbound.unchanged
196251
196825
  });
196826
+ const movedFileRefs = outbound.prepared.map((f2) => ({
196827
+ name: f2.rel,
196828
+ ...typeof f2.srcSize === "number" ? { size: f2.srcSize } : {},
196829
+ ...f2.srcMtime ? { mtime: f2.srcMtime } : {}
196830
+ }));
196831
+ const fingerprints = {};
196832
+ for (const f2 of movedFileRefs) {
196833
+ if (typeof f2.size === "number" || f2.mtime) fingerprints[f2.name] = { ...typeof f2.size === "number" ? { size: f2.size } : {}, ...f2.mtime ? { mtime: f2.mtime } : {} };
196834
+ }
196252
196835
  const source = delegationReturnSource(record8);
196253
196836
  const messageId = deriveSystemMessageId(source);
196254
196837
  let written;
@@ -196279,6 +196862,8 @@ async function deliverDelegationReturn(deps, terminal) {
196279
196862
  const bundle = bundleFiles.paths.length ? {
196280
196863
  files: bundleFiles.files,
196281
196864
  binaryFiles: bundleFiles.binaryFiles,
196865
+ // queued 那一档要靠它把指纹带到 flush 落账那一刻(`PendingChatAttachment.srcSize`)。
196866
+ fingerprints,
196282
196867
  taskLines: [
196283
196868
  "",
196284
196869
  "## \u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6",
@@ -196313,6 +196898,8 @@ async function deliverDelegationReturn(deps, terminal) {
196313
196898
  via: delivery.via,
196314
196899
  movedFiles: bundleFiles.paths.length,
196315
196900
  movedPaths: bundleFiles.paths,
196901
+ movedFileRefs,
196902
+ unchangedPaths: outbound.unchanged,
196316
196903
  skippedFiles: outbound.skipped.length
196317
196904
  };
196318
196905
  }
@@ -196324,6 +196911,7 @@ var init_return_flow = __esm({
196324
196911
  init_chat_system_message();
196325
196912
  init_chat_broadcast();
196326
196913
  init_files();
196914
+ init_src();
196327
196915
  }
196328
196916
  });
196329
196917
 
@@ -196467,8 +197055,10 @@ function assembleDelegationsDomain(deps) {
196467
197055
  );
196468
197056
  const targetRound = record8.roundSummaries[record8.roundSummaries.length - 1]?.round;
196469
197057
  let movedPaths = null;
197058
+ let movedRefs = [];
196470
197059
  if (outcome.status === "delivered" && (outcome.via === "turn" || outcome.via === "append") && outcome.movedPaths.length > 0) {
196471
197060
  movedPaths = outcome.movedPaths;
197061
+ movedRefs = outcome.movedFileRefs;
196472
197062
  }
196473
197063
  if (movedPaths === null || targetRound === void 0) return;
196474
197064
  const store = await deps.resolveStore(companyId).catch(() => null);
@@ -196481,7 +197071,7 @@ function assembleDelegationsDomain(deps) {
196481
197071
  `[delegation-return] \u4EA7\u7269\u6E05\u5355\u843D\u8D26\u672A\u6210 (immediate) delegation=${record8.id} round=${targetRound} status=resolve-failed moved=${movedPaths.length} paths=${pathsField}`
196482
197072
  );
196483
197073
  } else {
196484
- outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedPaths);
197074
+ outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedRefs.length ? movedRefs : movedPaths);
196485
197075
  if (outcomeCode === "ok") return;
196486
197076
  deps.metrics?.recordFilesRegisterFailure?.(outcomeCode);
196487
197077
  warn(
@@ -196495,6 +197085,11 @@ function assembleDelegationsDomain(deps) {
196495
197085
  chatSessionId: record8.parentSessionId,
196496
197086
  delegationRef: { delegationId: record8.id, round: targetRound },
196497
197087
  paths: movedPaths,
197088
+ // 补账那一趟同样要带指纹,否则「首次登记失败 → 补账成功」的路径会留下无指纹条目。
197089
+ fingerprints: Object.fromEntries(movedRefs.map((f2) => [f2.name, {
197090
+ ...typeof f2.size === "number" ? { size: f2.size } : {},
197091
+ ...f2.mtime ? { mtime: f2.mtime } : {}
197092
+ }])),
196498
197093
  reason: `immediate-register-failed:${outcomeCode}`
196499
197094
  }).catch((err) => {
196500
197095
  warn(
@@ -196517,16 +197112,20 @@ async function registerDelegationFilesOnFlush(deps, row, companyId) {
196517
197112
  const warn = deps.log ?? ((m2) => console.warn(m2));
196518
197113
  const ref2 = row.delegationRef;
196519
197114
  if (!ref2) return "no-ref";
196520
- const paths = (row.attachments ?? []).map((a) => a.path).filter((p2) => !!p2);
196521
- if (paths.length === 0) return "no-files";
197115
+ const rows = (row.attachments ?? []).filter((a) => !!a.path);
197116
+ if (rows.length === 0) return "no-files";
196522
197117
  const store = await deps.resolveStore(companyId).catch(() => null);
196523
197118
  if (!store) return "failed";
196524
- const files = paths.map((name) => ({ name }));
197119
+ const files = rows.map((a) => ({
197120
+ name: a.path,
197121
+ ...typeof a.srcSize === "number" ? { size: a.srcSize } : {},
197122
+ ...a.srcMtime ? { mtime: a.srcMtime } : {}
197123
+ }));
196525
197124
  const outcome = await registerRoundFiles(store, ref2.delegationId, ref2.round, files);
196526
197125
  if (outcome !== "ok") {
196527
197126
  deps.metrics?.recordFilesRegisterFailure?.(outcome);
196528
197127
  warn(
196529
- `[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${paths.length}`
197128
+ `[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${files.length}`
196530
197129
  );
196531
197130
  }
196532
197131
  return outcome;
@@ -196549,6 +197148,31 @@ var init_assembly = __esm({
196549
197148
  }
196550
197149
  });
196551
197150
 
197151
+ // ../server/src/domains/delegations/child-live-turn.ts
197152
+ function registerDelegatedChildTurn(liveChat, childSessionId, session) {
197153
+ const appendInput = session.appendInput;
197154
+ if (typeof appendInput !== "function" || session.canAppendInput === false) return;
197155
+ const ctrl = liveChat.start(childSessionId, {
197156
+ runtimeSessionId: session.id,
197157
+ ...session.runId ? { runId: session.runId } : {},
197158
+ kill: () => {
197159
+ void session.kill?.();
197160
+ },
197161
+ appendInput: (input) => appendInput.call(session, input),
197162
+ // **每次现读**,不快照:一轮跑到收尾时 stdin 会先关,那之后 runtime 自己会回
197163
+ // `session-closing`,判断权本就该留在它那儿(同 `/api/chat` 那条路的写法)。
197164
+ get canAppendInput() {
197165
+ return session.canAppendInput !== false;
197166
+ }
197167
+ });
197168
+ void session.done.then(() => ctrl.finish("done"), () => ctrl.finish("error"));
197169
+ }
197170
+ var init_child_live_turn = __esm({
197171
+ "../server/src/domains/delegations/child-live-turn.ts"() {
197172
+ "use strict";
197173
+ }
197174
+ });
197175
+
196552
197176
  // ../connectors/src/_base/wrapper-assets.ts
196553
197177
  function isUsableWrapper(scriptPath) {
196554
197178
  if (!(0, import_node_fs7.existsSync)(scriptPath)) return false;
@@ -201298,7 +201922,7 @@ function authorizedCommandReceipt(command, args, actor) {
201298
201922
  case "promote":
201299
201923
  return { message: "promoted to persistent" };
201300
201924
  case "resolveEscalation":
201301
- return { message: `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
201925
+ return { message: optStr(args, "gapId") ? `\u5DF2\u786E\u8BA4\u6062\u590D\u65B9\u6848\uFF0C\u89E3\u51B3\u7F3A\u53E3 ${str(args, "gapId")}\u3001\u5173\u95ED\u5173\u8054\u4E0A\u62A5 ${str(args, "escalationId")}\uFF1B\u8C03\u5EA6\u5668\u5C06\u5728\u5176\u4ED6\u963B\u585E\u89E3\u9664\u540E\u7EE7\u7EED\u6267\u884C\u3002` : `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
201302
201926
  default:
201303
201927
  throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u65E0\u56DE\u6267\u6784\u9020 ${command}`);
201304
201928
  }
@@ -201337,7 +201961,11 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
201337
201961
  );
201338
201962
  }
201339
201963
  }
201964
+ const linkedGapId = escalationById(kernel.model, escalationId)?.gapId;
201965
+ const linkedGap = linkedGapId ? (kernel.model.gaps.get(artifactId) ?? []).find((g2) => g2.gapId === linkedGapId) : void 0;
201966
+ if (linkedGap && !why?.trim()) throw new Error("\u8BF7\u8BF4\u660E\u91C7\u7528\u4EC0\u4E48\u6062\u590D\u65B9\u6848\uFF0C\u518D\u63D0\u4EA4\u7F3A\u53E3\u4E0E\u4E0A\u62A5\u7684\u8054\u5408\u786E\u8BA4");
201340
201967
  const normalizedArgs = {
201968
+ ...linkedGap ? { gapId: linkedGap.gapId, continueExecution: true } : {},
201341
201969
  escalationId,
201342
201970
  // ← 已四级解析出的具体一条(绝非 undefined、绝非「全清」)
201343
201971
  artifactId,
@@ -201935,10 +202563,10 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
201935
202563
  const note = optStr(args, "note");
201936
202564
  const via = optStr(args, "via");
201937
202565
  const target = kernel.model.annotations.get(annotationId);
201938
- if (target && target.state !== "open") {
202566
+ if (target && target.state !== "open" && resolution !== "acknowledged") {
201939
202567
  return { message: `annotation \u5DF2\u5173\u95ED\uFF08${target.state}\uFF09\u2014\u2014\u65E0\u9700\u518D\u5904\u7406${isReactivatedLetter(target, kernel.model.convergenceUnified) ? "\uFF1B\u8FD9\u5C01\u4FE1\u5728\u590D\u6D3B\u4E2D\uFF0C\u56DE\u5E16\u7B54\u590D\u7528 `oasis reply`\uFF0C\u6216\u6539\u5185\u5BB9 propose\uFF08\u7533\u62A5\u4F1A\u5E26\u4E0A\u5B83\uFF09" : ""}` };
201940
202568
  }
201941
- await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {} });
202569
+ await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {}, ...ctx.sessionArtifactId ? { viaNode: ctx.sessionArtifactId } : {} });
201942
202570
  return { message: `annotation \u2192 ${resolution}${via ? `\uFF08via ${via}\uFF09` : ""}${isAgent(actor) && target?.author.startsWith("actor:human:") ? "\uFF1B\u4F5C\u8005\u4F1A\u6536\u5230\u901A\u77E5\uFF0C\u82E5\u4E0D\u8BA4\u53EF\u4F1A\u5728\u4FE1\u91CC\u56DE\u5E16\u5524\u4F60" : ""}` };
201943
202571
  }
201944
202572
  case "gap": {
@@ -202489,7 +203117,15 @@ async function startOasisServer(opts) {
202489
203117
  resolveStore: opts.delegationStoreFor,
202490
203118
  resolveChatSessions: resolveChatSessionsForDelegation,
202491
203119
  resolveActors: async (companyId) => (await actorsDomain2.resolveCtx(companyId)).service,
202492
- dispatchChat,
203120
+ // **派发完顺手把子会话这一轮登记进 live 注册表**(v2 bug 0088)。缺这一步时下面那行
203121
+ // `appendToChild` 恒回 `no-live-turn`——注册表里从来没有以 `childSessionId` 为键的轮,
203122
+ // 于是每一条 `--continue` 转达都落队列,专家要等本轮跑完才看见「手上这段作废」。
203123
+ // 理由与两条纪律见 `child-live-turn.ts` 的文件头。
203124
+ dispatchChat: async (request2) => {
203125
+ const session = await dispatchChat(request2);
203126
+ registerDelegatedChildTurn(liveChat, request2.chatSessionId, session);
203127
+ return session;
203128
+ },
202493
203129
  appendToChild: (childSessionId, text5, extra) => liveChat.append(childSessionId, text5, extra),
202494
203130
  // 用 lambda 转一层:`broadcastDepsFor` 在本文件里声明得更靠后(回流是异步发生的,
202495
203131
  // 那时它早就赋过值了)。直接把值递进去会撞 TDZ。
@@ -202730,16 +203366,25 @@ async function startOasisServer(opts) {
202730
203366
  ...store?.enqueuePendingMessage ? {
202731
203367
  enqueuePending: async (input) => {
202732
203368
  const bundle = input.bundle;
203369
+ const fp = (p2) => {
203370
+ const f2 = bundle?.fingerprints?.[p2];
203371
+ return {
203372
+ ...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
203373
+ ...f2?.mtime ? { srcMtime: f2.mtime } : {}
203374
+ };
203375
+ };
202733
203376
  const attachments = bundle ? [
202734
203377
  ...Object.entries(bundle.files ?? {}).map(([p2, text5]) => ({
202735
203378
  name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
202736
203379
  text: text5,
202737
- path: p2
203380
+ path: p2,
203381
+ ...fp(p2)
202738
203382
  })),
202739
203383
  ...Object.entries(bundle.binaryFiles ?? {}).map(([p2, b64]) => ({
202740
203384
  name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
202741
203385
  bytesBase64: b64,
202742
- path: p2
203386
+ path: p2,
203387
+ ...fp(p2)
202743
203388
  }))
202744
203389
  ] : [];
202745
203390
  const { randomUUID: randomUUID40 } = await import("node:crypto");
@@ -202769,7 +203414,15 @@ async function startOasisServer(opts) {
202769
203414
  // ——生产 `deliverToAgent:512` 会开轮/append,用户听到空消息且下一场 UPDATE 再把已完成
202770
203415
  // 的行改回 delivering,违背「只补账、不投递」语义。
202771
203416
  enqueueRegisterPending: async (input) => {
202772
- const attachments = input.paths.map((p2) => ({ name: p2.slice(p2.lastIndexOf("/") + 1) || p2, path: p2 }));
203417
+ const attachments = input.paths.map((p2) => {
203418
+ const f2 = input.fingerprints?.[p2];
203419
+ return {
203420
+ name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
203421
+ path: p2,
203422
+ ...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
203423
+ ...f2?.mtime ? { srcMtime: f2.mtime } : {}
203424
+ };
203425
+ });
202773
203426
  const { randomUUID: randomUUID40 } = await import("node:crypto");
202774
203427
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
202775
203428
  try {
@@ -203203,6 +203856,22 @@ async function startOasisServer(opts) {
203203
203856
  }));
203204
203857
  return;
203205
203858
  }
203859
+ if (url.pathname.startsWith("/api/files/")) {
203860
+ const company = opts.resolveCompanyContext ? await opts.resolveCompanyContext(actor, req.headers, url.pathname) : void 0;
203861
+ if (company && company.kind !== "ok") {
203862
+ res.writeHead(403, { "content-type": "application/json" }).end(JSON.stringify({
203863
+ error: { code: "FORBIDDEN", message: "\u65E0\u6743\u8BFB\u53D6\u8BE5\u516C\u53F8\u7684\u6587\u4EF6" }
203864
+ }));
203865
+ return;
203866
+ }
203867
+ await serveFileRequest(
203868
+ req,
203869
+ res,
203870
+ url,
203871
+ await resolveEngine(company?.kind === "ok" ? company.companyId : void 0)
203872
+ );
203873
+ return;
203874
+ }
203206
203875
  if (url.pathname === "/api/whoami" && req.method === "GET") {
203207
203876
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ actor }));
203208
203877
  return;
@@ -204909,6 +205578,22 @@ ${composed}`;
204909
205578
  const itemLedger = new ChatItemLedger({
204910
205579
  ...itemStore ? { items: itemStore } : {},
204911
205580
  versionSeed,
205581
+ /* 用户那条消息一落库就推上 v3 流(ADR-0510 D2 的收口)。
205582
+ 不推的话:乐观条没有服务端号、被钉在列表末尾,而 agent 的正文带着更大的号从
205583
+ 直播流到达、进「有号」段 ⇒ **回复显示在用户消息上面**,直到下一次快照才跳回去
205584
+ (2026-09-09 用户现场)。这里把窗口从「等下一次快照」压到「一次 INSERT 往返」。
205585
+ 只推 user:assistant 的每一条本来就走 `emitV3`。 */
205586
+ onPersisted: (info) => {
205587
+ if (info.role !== "user") return;
205588
+ opts.liveChat?.publishUserItem(persistTarget?.id ?? session.id, {
205589
+ itemId: info.itemId,
205590
+ ord: info.ord,
205591
+ version: info.version,
205592
+ text: info.text,
205593
+ ...clientSubmitId ? { clientSubmitId } : {},
205594
+ ...effectiveAttachments.length ? { attachments: effectiveAttachments } : {}
205595
+ });
205596
+ },
204912
205597
  sessionId: persistTarget?.id ?? session.id,
204913
205598
  // 优先账本 id(`chat_session_turns.id`,跨进程唯一、重启不失忆);账本没接入才兜底。
204914
205599
  turnId: heldTurn?.id ?? fallbackTurnId(session.runId),
@@ -206219,6 +206904,7 @@ var init_server3 = __esm({
206219
206904
  init_src();
206220
206905
  init_src();
206221
206906
  init_src();
206907
+ init_file_http();
206222
206908
  init_build_info();
206223
206909
  init_artifact_content2();
206224
206910
  init_command_policy();
@@ -206260,6 +206946,7 @@ var init_server3 = __esm({
206260
206946
  init_workorder_terminal();
206261
206947
  init_knowledge2();
206262
206948
  init_assembly();
206949
+ init_child_live_turn();
206263
206950
  init_src8();
206264
206951
  init_continuation();
206265
206952
  githubAppPending = new PendingAppCreations();
@@ -209346,6 +210033,27 @@ function foldSingleNodeTasks(items) {
209346
210033
  }
209347
210034
  return folded;
209348
210035
  }
210036
+ function fillFolderSizes(items) {
210037
+ const folders = items.filter((i) => i.kind === "folder" && i.path);
210038
+ if (folders.length === 0) return;
210039
+ const files = items.filter((i) => i.kind !== "folder" && i.path);
210040
+ for (const folder of folders) {
210041
+ const prefix = `${folder.path}/`;
210042
+ let sum = 0;
210043
+ let seen = 0;
210044
+ let complete = true;
210045
+ for (const file of files) {
210046
+ if (!file.path.startsWith(prefix)) continue;
210047
+ seen += 1;
210048
+ if (file.size == null) {
210049
+ complete = false;
210050
+ break;
210051
+ }
210052
+ sum += file.size;
210053
+ }
210054
+ folder.size = seen > 0 && complete ? sum : null;
210055
+ }
210056
+ }
209349
210057
  function engineContentKind(k2) {
209350
210058
  return k2 === "manifest" ? "manifest" : k2 === "external" ? "external-pin" : "inline-blob";
209351
210059
  }
@@ -210243,6 +210951,7 @@ var init_service4 = __esm({
210243
210951
  const projectByWorkOrder = new Map(bindings.map((b2) => [b2.workOrderId, b2.projectId]));
210244
210952
  const workorderSummaries = await this.listProjectWorkorders(projectId2);
210245
210953
  const workorderTitleById = new Map(workorderSummaries.map((w2) => [w2.id, w2.title]));
210954
+ const workorderOwnerById = new Map(workorderSummaries.map((w2) => [w2.id, w2.owner?.id ?? null]));
210246
210955
  const engineRows = await this.collectEngineFilesForProject(
210247
210956
  projectId2,
210248
210957
  workorderSummaries.map((w2) => w2.id),
@@ -210261,6 +210970,8 @@ var init_service4 = __esm({
210261
210970
  const taskId = record8.createdFromWorkOrderId ?? artifact?.workspace ?? null;
210262
210971
  const taskTitle = taskId ? workorderTitleById.get(taskId) ?? null : null;
210263
210972
  const creatorId = revision.author || null;
210973
+ const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
210974
+ const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
210264
210975
  const folderPath = taskId ? `wo:${taskId}/node:${record8.artifactId}` : null;
210265
210976
  if (folderPath) items.push({
210266
210977
  kind: "folder",
@@ -210273,13 +210984,14 @@ var init_service4 = __esm({
210273
210984
  size: null,
210274
210985
  taskId,
210275
210986
  taskTitle,
210276
- creatorKind: null,
210277
- creatorId: null,
210278
- creatorName: null,
210987
+ creatorKind,
210988
+ creatorId,
210989
+ creatorName,
210279
210990
  updatedAt: record8.updatedAt
210280
210991
  });
210281
210992
  const content3 = revision.contentKind === "manifest" ? await this.readContentRefText(revision.contentRef) : null;
210282
210993
  const files2 = content3 !== null ? tryParseManifestFiles(content3) : void 0;
210994
+ const wholeSize = files2?.length ? null : content3 !== null ? Buffer.byteLength(content3, "utf8") : await this.contentByteSize(revision.contentRef, revision.contentKind);
210283
210995
  for (const file of files2?.length ? files2 : [null]) {
210284
210996
  items.push({
210285
210997
  kind: "artifact",
@@ -210289,15 +211001,15 @@ var init_service4 = __esm({
210289
211001
  filePath: file?.path ?? null,
210290
211002
  depth: folderPath ? 2 : 0,
210291
211003
  type: record8.type,
210292
- size: file?.size ?? null,
211004
+ size: file ? file.size ?? null : wholeSize,
210293
211005
  taskId,
210294
211006
  taskTitle,
210295
211007
  // 内容形态/引用只挂在**整份产物**那一行;装箱单摊出来的成员行有自己的后缀,
210296
211008
  // 按后缀走图标即可,挂上 manifest 反而会把 `Dockerfile` 这种无后缀成员画成压缩包。
210297
211009
  ...file ? {} : { contentKind: revision.contentKind, contentRef: revision.contentRef },
210298
- creatorKind: creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null,
211010
+ creatorKind,
210299
211011
  creatorId,
210300
- creatorName: creatorId ? this.resolveActorName(creatorId) : null,
211012
+ creatorName,
210301
211013
  updatedAt: record8.updatedAt
210302
211014
  });
210303
211015
  }
@@ -210309,6 +211021,7 @@ var init_service4 = __esm({
210309
211021
  if (foldedTasks.has(item.taskId)) continue;
210310
211022
  taskFolders.add(item.taskId);
210311
211023
  const path41 = `wo:${item.taskId}`;
211024
+ const ownerId = workorderOwnerById.get(item.taskId) ?? null;
210312
211025
  items.push({
210313
211026
  kind: "folder",
210314
211027
  id: `folder:${path41}`,
@@ -210320,9 +211033,9 @@ var init_service4 = __esm({
210320
211033
  size: null,
210321
211034
  taskId: item.taskId,
210322
211035
  taskTitle: item.taskTitle,
210323
- creatorKind: null,
210324
- creatorId: null,
210325
- creatorName: null,
211036
+ creatorKind: ownerId ? ownerId.startsWith("actor:human:") ? "human" : "agent" : null,
211037
+ creatorId: ownerId,
211038
+ creatorName: ownerId ? this.resolveActorName(ownerId) : null,
210326
211039
  updatedAt: item.updatedAt
210327
211040
  });
210328
211041
  }
@@ -210348,6 +211061,7 @@ var init_service4 = __esm({
210348
211061
  updatedAt: file.uploadedAt
210349
211062
  });
210350
211063
  }
211064
+ fillFolderSizes(items);
210351
211065
  items.sort((a, b2) => {
210352
211066
  const pathA = a.path ?? "";
210353
211067
  const pathB = b2.path ?? "";
@@ -210391,6 +211105,9 @@ var init_service4 = __esm({
210391
211105
  const work = worksById.get(acceptedId);
210392
211106
  const folderPath = `wo:${workOrderId}/node:${node2.id}`;
210393
211107
  const folderUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
211108
+ const creatorId = work?.assigneeActorId ?? null;
211109
+ const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
211110
+ const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
210394
211111
  out.push({
210395
211112
  kind: "folder",
210396
211113
  id: `folder:${folderPath}`,
@@ -210402,14 +211119,11 @@ var init_service4 = __esm({
210402
211119
  size: null,
210403
211120
  taskId: workOrderId,
210404
211121
  taskTitle,
210405
- creatorKind: null,
210406
- creatorId: null,
210407
- creatorName: null,
211122
+ creatorKind,
211123
+ creatorId,
211124
+ creatorName,
210408
211125
  updatedAt: folderUpdatedAt
210409
211126
  });
210410
- const creatorId = work?.assigneeActorId ?? null;
210411
- const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
210412
- const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
210413
211127
  const sortedArts = arts.slice().sort((a, b2) => a.ordinal - b2.ordinal);
210414
211128
  for (const a of sortedArts) {
210415
211129
  const artifactId = node2.id;
@@ -210448,7 +211162,7 @@ var init_service4 = __esm({
210448
211162
  filePath: null,
210449
211163
  depth: 2,
210450
211164
  type: artifactType || "",
210451
- size: null,
211165
+ size: await this.contentByteSize(a.contentRef, engineContentKind(a.contentKind)),
210452
211166
  contentKind: engineContentKind(a.contentKind),
210453
211167
  ...a.contentRef ? { contentRef: a.contentRef } : {},
210454
211168
  taskId: workOrderId,
@@ -210867,6 +211581,19 @@ var init_service4 = __esm({
210867
211581
  }
210868
211582
  return { ...withKind, size: Buffer.byteLength(content3, "utf8") };
210869
211583
  }
211584
+ /**
211585
+ * 一份产物正文的字节数——**与任务页「相关产物」共用同一条判据**(见 {@link enrichNodeOutputDocument}):
211586
+ * 不看 `contentKind` 名义,只看正文能不能从 BlobStore 读到;读不到就是 null。
211587
+ *
211588
+ * 于是 git 提交号 / Figma version 这类真·外部钉、未纳管、`empty` 都诚实无大小(前端显「—」),
211589
+ * 而 `inline-blob` 的任务书、PRD、ADR 会报出真实字节数——此前 files-view 这条路径**恒发 null**,
211590
+ * 同一份任务书在任务页有 8.0 KB、在项目页却是「—」。
211591
+ */
211592
+ async contentByteSize(contentRef, contentKind) {
211593
+ if (contentKind === "empty" || !contentRef?.trim()) return null;
211594
+ const content3 = await this.readContentRefText(contentRef);
211595
+ return content3 === null ? null : Buffer.byteLength(content3, "utf8");
211596
+ }
210870
211597
  /** 正文读取的唯一入口:只认 BlobStore;没配 / 没纳管 / 读失败一律 null(调用方据此诚实缺 size)。 */
210871
211598
  async readContentRefText(contentRef) {
210872
211599
  if (!this.blobs) return null;
@@ -224806,6 +225533,146 @@ var init_node_store = __esm({
224806
225533
  }
224807
225534
  });
224808
225535
 
225536
+ // ../server/src/domains/collab/review-activity.ts
225537
+ function reviewCardId(targetWorkId, reviewerActorId) {
225538
+ return `${REVIEW_CARD_PREFIX}${encodeURIComponent(targetWorkId)}:${encodeURIComponent(reviewerActorId)}`;
225539
+ }
225540
+ function activityReviews(snap, events) {
225541
+ const rows = /* @__PURE__ */ new Map();
225542
+ for (const r of snap.reviews) rows.set(r.id, { ...r });
225543
+ const authoritative = new Set(snap.reviews.filter((r) => r.status).map((r) => r.id));
225544
+ for (const rec of [...events].sort((a, b2) => a.seq - b2.seq)) {
225545
+ const e = rec.event;
225546
+ if (e.kind === "review.create" && !rows.has(e.reviewId)) {
225547
+ rows.set(e.reviewId, {
225548
+ id: e.reviewId,
225549
+ workorderId: snap.workorder.id,
225550
+ nodeId: e.nodeId,
225551
+ targetWorkId: e.targetWorkId,
225552
+ reviewerActorId: e.reviewerActorId,
225553
+ reviewGroup: e.reviewGroup ?? e.reviewerActorId,
225554
+ createdAt: rec.createdAt,
225555
+ startedAt: null,
225556
+ endedAt: null,
225557
+ cancelledAt: null,
225558
+ verdict: null,
225559
+ note: null,
225560
+ handActorId: null,
225561
+ decidedAt: null,
225562
+ sessionRef: null
225563
+ });
225564
+ }
225565
+ if (e.kind === "review.create" && !authoritative.has(e.reviewId)) {
225566
+ const r = rows.get(e.reviewId);
225567
+ Object.assign(r, {
225568
+ nodeId: e.nodeId,
225569
+ targetWorkId: e.targetWorkId,
225570
+ reviewerActorId: e.reviewerActorId,
225571
+ reviewGroup: e.reviewGroup ?? r.reviewGroup ?? e.reviewerActorId,
225572
+ createdAt: r.createdAt ?? rec.createdAt
225573
+ });
225574
+ }
225575
+ if (!e.kind.startsWith("review.")) continue;
225576
+ const candidates = "reviewId" in e && e.reviewId ? [rows.get(e.reviewId)] : e.kind === "review.response" ? [...rows.values()].filter((r) => r.targetWorkId === e.targetWorkId && r.reviewerActorId === e.reviewerActorId && (!e.reviewGroup || r.reviewGroup === e.reviewGroup)) : [];
225577
+ const matches = e.kind === "review.response" && !e.reviewId ? latestActivityReviews(candidates.filter((r) => !!r && r.createdAt <= rec.createdAt), events.filter((r) => r.seq <= rec.seq)) : candidates;
225578
+ for (const r of matches) {
225579
+ if (!r || authoritative.has(r.id)) continue;
225580
+ if (e.kind === "review.started") r.startedAt = rec.createdAt;
225581
+ if (e.kind === "review.timeout") {
225582
+ r.endedAt = rec.createdAt;
225583
+ }
225584
+ if (e.kind === "review.kill") r.cancelledAt = rec.createdAt;
225585
+ if (e.kind === "review.response") {
225586
+ r.verdict = e.verdict;
225587
+ r.note = e.note ?? null;
225588
+ r.decidedAt = rec.createdAt;
225589
+ r.endedAt = rec.createdAt;
225590
+ r.handActorId = e.handActorId ?? null;
225591
+ }
225592
+ }
225593
+ }
225594
+ const createdSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
225595
+ return [...rows.values()].filter((r) => r.nodeId && r.targetWorkId && r.reviewerActorId && r.createdAt).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || (createdSeq.get(a.id) ?? 0) - (createdSeq.get(b2.id) ?? 0) || a.id.localeCompare(b2.id));
225596
+ }
225597
+ function reviewCardTarget(cardId, reviews) {
225598
+ if (cardId.startsWith(REVIEW_CARD_PREFIX)) {
225599
+ const encoded = cardId.slice(REVIEW_CARD_PREFIX.length);
225600
+ const separator = encoded.indexOf(":");
225601
+ if (separator < 1 || separator === encoded.length - 1) return void 0;
225602
+ try {
225603
+ return {
225604
+ targetWorkId: decodeURIComponent(encoded.slice(0, separator)),
225605
+ reviewerActorId: decodeURIComponent(encoded.slice(separator + 1))
225606
+ };
225607
+ } catch {
225608
+ return void 0;
225609
+ }
225610
+ }
225611
+ if (cardId.startsWith("review:")) {
225612
+ const review = reviews.find((r) => r.id === cardId.slice(7));
225613
+ if (review) return { targetWorkId: review.targetWorkId, reviewerActorId: review.reviewerActorId };
225614
+ }
225615
+ return void 0;
225616
+ }
225617
+ function latestActivityReviews(reviews, events) {
225618
+ const createSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
225619
+ const rows = [...reviews].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || (createSeq.get(a.id) ?? 0) - (createSeq.get(b2.id) ?? 0) || a.id.localeCompare(b2.id));
225620
+ const latest = /* @__PURE__ */ new Map();
225621
+ for (const r of rows) latest.set(JSON.stringify([r.reviewGroup, r.reviewerActorId]), r);
225622
+ return [...latest.values()];
225623
+ }
225624
+ function reviewLabel(r) {
225625
+ const state = reviewState(r);
225626
+ if (state === "accept") return "\u5BA1\u6838\u901A\u8FC7";
225627
+ if (state === "reject") return "\u5BA1\u6838\u4E0D\u901A\u8FC7";
225628
+ if (state === "dead") return "\u5BA1\u6838\u5DF2\u505C\u6B62";
225629
+ if (state === "failed") return "\u5BA1\u6838\u8FD0\u884C\u5931\u8D25";
225630
+ if (state === "retry") return "\u6B63\u5728\u91CD\u8BD5\u5BA1\u6838";
225631
+ return r.reviewerActorId.startsWith("actor:human:") ? "\u5F85\u5BA1\u6838" : "\u6B63\u5728\u5BA1\u6838";
225632
+ }
225633
+ function reviewSummary(snap, reviews, events) {
225634
+ const first = reviews[0];
225635
+ const work = snap.works.find((w2) => w2.id === first.targetWorkId);
225636
+ const node2 = snap.nodes.find((n) => n.id === first.nodeId);
225637
+ const name = node2?.title ?? first.nodeId;
225638
+ const latest = latestActivityReviews(reviews, events);
225639
+ const requirements = snap.requirements.filter((r) => r.nodeId === first.nodeId && r.reviewerActorId === first.reviewerActorId);
225640
+ const judgement = judgeWork({ requirements: requirements.length ? requirements : reviews, reviews });
225641
+ const pending = latest.filter((r) => !r.cancelledAt && !r.verdict && reviewState(r) === "running");
225642
+ if (work?.acceptanceState === "accepted" || work?.acceptedAt || work?.acceptanceState !== "rejected" && judgement === "passed") {
225643
+ return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
225644
+ }
225645
+ if (work?.acceptanceState === "rejected" || judgement === "rejected") {
225646
+ return { phase: "done", status: "\u5BA1\u6838\u4E0D\u901A\u8FC7\u3002", latest, pending: [] };
225647
+ }
225648
+ const endedWork = !!(work?.cancelledAt || work?.deadAt);
225649
+ if (endedWork && latest.every((r) => !!r.cancelledAt && reviewState(r) !== "retry")) {
225650
+ return { phase: "done", status: `\u300A${name}\u300B\u7684\u672C\u6B21\u5BA1\u6838\u5DF2\u53D6\u6D88\u3002`, latest, pending: [] };
225651
+ }
225652
+ const interrupted = latest.some((r) => ["failed", "dead"].includes(reviewState(r)));
225653
+ if (interrupted) return { phase: "stuck", status: latest.length === 1 ? reviewState(latest[0]) === "dead" ? events.some((r) => r.event.kind === "review.kill" && r.event.reviewId === latest[0].id) ? `\u300A${name}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7EED\u5931\u8D25\uFF0C\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u672A\u5728\u65F6\u9650\u5185\u5B8C\u6210\u3002` : `\u300A${name}\u300B\u7684\u90E8\u5206\u5BA1\u6838\u6267\u884C\u4E2D\u65AD\uFF0C\u5C1A\u672A\u5B8C\u6210\u5168\u90E8\u5BA1\u6838\u3002`, latest, pending };
225654
+ if (latest.some((r) => r.reviewerActorId.startsWith("actor:agent:") && ["running", "retry"].includes(reviewState(r)))) {
225655
+ return { phase: "running", status: `\u6B63\u5728\u5BA1\u6838 \u300A${name}\u300B\u3002`, latest, pending };
225656
+ }
225657
+ return { phase: "not_started", status: `\u8BF7\u786E\u8BA4\u300A${name}\u300B\u662F\u5426\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending };
225658
+ }
225659
+ var REVIEW_CARD_PREFIX, REVIEW_TRACE_EVENT_KINDS;
225660
+ var init_review_activity = __esm({
225661
+ "../server/src/domains/collab/review-activity.ts"() {
225662
+ "use strict";
225663
+ init_src4();
225664
+ REVIEW_CARD_PREFIX = "review-work:";
225665
+ REVIEW_TRACE_EVENT_KINDS = [
225666
+ "review.create",
225667
+ "review.started",
225668
+ "review.response",
225669
+ "review.timeout",
225670
+ "review.kill",
225671
+ "plan.node_retry"
225672
+ ];
225673
+ }
225674
+ });
225675
+
224809
225676
  // ../server/src/domains/collab/workorder-manager.ts
224810
225677
  function resolveWorkorderManager(snap) {
224811
225678
  const nodes = snap.nodes;
@@ -224844,8 +225711,29 @@ function buildWorkorderActivity(input) {
224844
225711
  if (!snap) return { workorderId, cards: [], truncated: false };
224845
225712
  const nodeById = new Map(snap.nodes.map((n) => [n.id, n]));
224846
225713
  const workById = new Map(snap.works.map((w2) => [w2.id, w2]));
224847
- const reviewById = new Map(snap.reviews.map((r) => [r.id, r]));
224848
225714
  const issueById = new Map(snap.issues.map((i) => [i.id, i]));
225715
+ const activeWorks = /* @__PURE__ */ new Set();
225716
+ const gapOrigins = /* @__PURE__ */ new Map();
225717
+ const knownStarts = new Set(events.flatMap((r) => r.event.kind === "work.create" || r.event.kind === "work.started" ? [r.event.workId] : []));
225718
+ for (const rec of events) {
225719
+ const event = rec.event;
225720
+ if (event.kind === "work.create" || event.kind === "work.started") activeWorks.add(event.workId);
225721
+ if (event.kind === "work.response" || event.kind === "work.timeout" || event.kind === "work.kill") activeWorks.delete(event.workId);
225722
+ if (event.kind !== "issue.create" || event.issueKind !== "gap") continue;
225723
+ const nodeId = event.raisedByNodeId ?? event.aboutNodeId;
225724
+ const matches = (id) => {
225725
+ const work = workById.get(id);
225726
+ return work?.nodeId === nodeId && work.assigneeActorId === event.authorActorId;
225727
+ };
225728
+ if (event.aboutWorkId && matches(event.aboutWorkId)) {
225729
+ gapOrigins.set(event.issueId, [event.aboutWorkId]);
225730
+ } else if (!event.aboutWorkId) {
225731
+ const candidates = [...activeWorks].filter(matches);
225732
+ const at = Date.parse(rec.createdAt);
225733
+ const unobserved = snap.works.some((work) => !knownStarts.has(work.id) && matches(work.id) && Date.parse(work.createdAt) <= at && (!work.endedAt || Date.parse(work.endedAt) >= at) && (!work.deadAt || Date.parse(work.deadAt) >= at) && (!work.cancelledAt || Date.parse(work.cancelledAt) >= at));
225734
+ if (candidates.length === 1 && !unobserved) gapOrigins.set(event.issueId, candidates);
225735
+ }
225736
+ }
224849
225737
  const nodesWithInEdges = new Set(snap.edges.map((e) => e.toNodeId));
224850
225738
  const rootBriefNodeIds = new Set(
224851
225739
  snap.nodes.filter((n) => n.type === "brief" && !nodesWithInEdges.has(n.id)).map((n) => n.id)
@@ -224896,7 +225784,6 @@ function buildWorkorderActivity(input) {
224896
225784
  const groupIndexOf = /* @__PURE__ */ new Map();
224897
225785
  const cardOfWork = /* @__PURE__ */ new Map();
224898
225786
  const groupKey = (nodeId) => `${nodeId}#${groupIndexOf.get(nodeId) ?? 0}`;
224899
- const reviewCards = /* @__PURE__ */ new Map();
224900
225787
  const issueCards = /* @__PURE__ */ new Map();
224901
225788
  const workOfIssue = /* @__PURE__ */ new Map();
224902
225789
  const plainCommentIssueIds = /* @__PURE__ */ new Set();
@@ -225118,72 +226005,6 @@ function buildWorkorderActivity(input) {
225118
226005
  }
225119
226006
  break;
225120
226007
  }
225121
- /* ── 审核(review)──────────────────────────────────────────── */
225122
- case "review.create": {
225123
- const reviewId = String(ev.reviewId ?? "");
225124
- const nodeId = String(ev.nodeId ?? "");
225125
- const targetWorkId = String(ev.targetWorkId ?? "");
225126
- const reviewerId = str4(ev.reviewerActorId) ?? rec.actorId;
225127
- const isAgent2 = ev.reviewerIsAgent === true;
225128
- const name = nodeName(nodeId);
225129
- reviewCards.set(reviewId, {
225130
- id: `review:${reviewId}`,
225131
- seq: rec.seq,
225132
- at: rec.createdAt,
225133
- updatedAt: rec.createdAt,
225134
- nodeId,
225135
- executorId: reviewerId,
225136
- ...rec.handActorId ? { handActorId: rec.handActorId } : {},
225137
- // agent 评审是「机器在跑」= 进行中;人审是待办 = 待开始(人没有「开始看了」这个事件)。
225138
- phase: isAgent2 ? "running" : "not_started",
225139
- status: isAgent2 ? ACTIVITY_COPY.reviewing(name) : ACTIVITY_COPY.reviewPending(name),
225140
- /* 挂上被审那一次交付的**全部产物**(发起人 2026-09-08 定):要人点「通过/不通过」,
225141
- 就得让他在同一张卡上看到到底在审什么。此前这里恒空,产物只挂在验收卡上——
225142
- 而验收卡与审核卡是同一件事的两张(见下方 `acceptCards` 那段的跳过判据)。 */
225143
- artifacts: artifactsByWork.get(targetWorkId) ?? [],
225144
- // 人审才出底栏按钮;agent 审无按钮(原型同)。
225145
- actions: isAgent2 ? [] : [
225146
- { kind: "review", label: BUTTON.approve, target: nodeId, verdict: "approve" },
225147
- { kind: "review", label: BUTTON.reject, target: nodeId, verdict: "request_changes" }
225148
- ]
225149
- });
225150
- break;
225151
- }
225152
- case "review.started": {
225153
- const d = reviewCards.get(String(ev.reviewId ?? ""));
225154
- if (d) touch(d, rec);
225155
- break;
225156
- }
225157
- case "review.response": {
225158
- const reviewId = str4(ev.reviewId);
225159
- const targetWorkId = String(ev.targetWorkId ?? "");
225160
- const d = reviewId ? reviewCards.get(reviewId) : [...reviewCards.values()].find((c) => reviewById.get(c.id.slice("review:".length))?.targetWorkId === targetWorkId);
225161
- if (!d) break;
225162
- touch(d, rec);
225163
- d.phase = "done";
225164
- d.executorId = str4(ev.reviewerActorId) ?? d.executorId;
225165
- d.actions = [];
225166
- const nodeId = d.nodeId;
225167
- const name = nodeName(nodeId);
225168
- if (ev.verdict === "approve") {
225169
- d.status = ACTIVITY_COPY.reviewApproved(name);
225170
- } else {
225171
- d.status = ACTIVITY_COPY.reviewRejected;
225172
- const note = str4(ev.note);
225173
- if (note) d.detail = ACTIVITY_COPY.reviewRejectedDetail(note);
225174
- }
225175
- break;
225176
- }
225177
- case "review.timeout":
225178
- case "review.kill": {
225179
- const d = reviewCards.get(String(ev.reviewId ?? ""));
225180
- if (!d) break;
225181
- touch(d, rec);
225182
- d.phase = "stuck";
225183
- d.status = ev.kind === "review.kill" ? ACTIVITY_COPY.reviewKilled(nodeName(d.nodeId)) : ACTIVITY_COPY.reviewTimedOut(nodeName(d.nodeId));
225184
- d.actions = [];
225185
- break;
225186
- }
225187
226008
  /* ── 沟通与异常(issue)────────────────────────────────────── */
225188
226009
  case "issue.create": {
225189
226010
  const issueId = String(ev.issueId ?? "");
@@ -225327,24 +226148,14 @@ function buildWorkorderActivity(input) {
225327
226148
  if (issue2?.kind === "escalation") {
225328
226149
  const linkedGap = str4(issue2.gapId);
225329
226150
  const stuckActor = (linkedGap ? issueById.get(linkedGap)?.authorActorId : void 0) ?? issue2.authorActorId;
226151
+ touch(d, rec);
226152
+ d.phase = "done";
226153
+ d.executorId = resolverId;
226154
+ d.handActorId = rec.handActorId ?? void 0;
226155
+ d.status = ACTIVITY_COPY.handledEscalation(nameOf(stuckActor));
225330
226156
  d.actions = [];
225331
226157
  const raisedCard = issueCards.get(`${issueId}:raised`);
225332
226158
  if (raisedCard) raisedCard.actions = [];
225333
- standalone.push({
225334
- id: `issue:${issueId}:resolved`,
225335
- seq: rec.seq,
225336
- at: rec.createdAt,
225337
- updatedAt: rec.createdAt,
225338
- ...d.nodeId ? { nodeId: d.nodeId } : {},
225339
- executorId: resolverId,
225340
- ...rec.handActorId ? { handActorId: rec.handActorId } : {},
225341
- phase: "done",
225342
- /* 收口正文不上卡(与开口那一支同一条口径):`ws:wo-635d7add` 的收口 note 是一整段
225343
- 处置说明,铺回展开区又是一屏字。 */
225344
- status: ACTIVITY_COPY.handledEscalation(nameOf(stuckActor)),
225345
- artifacts: [],
225346
- actions: []
225347
- });
225348
226159
  break;
225349
226160
  }
225350
226161
  touch(d, rec);
@@ -225371,9 +226182,7 @@ function buildWorkorderActivity(input) {
225371
226182
  break;
225372
226183
  }
225373
226184
  }
225374
- const reviewedWorkIds = new Set(
225375
- [...reviewCards.keys()].map((rid) => reviewById.get(rid)?.targetWorkId).filter(Boolean)
225376
- );
226185
+ const reviewedWorkIds = new Set([...snap.reviews, ...activityReviews(snap, events)].map((r) => r.targetWorkId));
225377
226186
  for (const w2 of snap.works) {
225378
226187
  if (acceptCards.has(w2.id)) continue;
225379
226188
  if (reviewedWorkIds.has(w2.id)) continue;
@@ -225397,15 +226206,57 @@ function buildWorkorderActivity(input) {
225397
226206
  status: ACTIVITY_COPY.acceptPending,
225398
226207
  artifacts: artifactsByWork.get(w2.id) ?? [],
225399
226208
  actions: [
225400
- { kind: "accept", label: BUTTON.acceptPass, target: node2.id, verdict: "approve" },
225401
- { kind: "accept", label: BUTTON.acceptFail, target: node2.id, verdict: "request_changes" }
226209
+ { kind: "accept", label: BUTTON.acceptPass, target: node2.id, revisionId: w2.id, verdict: "approve" },
226210
+ { kind: "accept", label: BUTTON.acceptFail, target: node2.id, revisionId: w2.id, verdict: "request_changes" }
225402
226211
  ]
225403
226212
  });
225404
226213
  }
226214
+ const visibleReviews = /* @__PURE__ */ new Map();
226215
+ const allReviews = activityReviews(snap, events);
226216
+ const reviewWorks = /* @__PURE__ */ new Map();
226217
+ const reviewCards = /* @__PURE__ */ new Map();
226218
+ for (const r of allReviews) {
226219
+ const rows = reviewWorks.get(r.targetWorkId) ?? [];
226220
+ rows.push(r);
226221
+ reviewWorks.set(r.targetWorkId, rows);
226222
+ const cardId = reviewCardId(r.targetWorkId, r.reviewerActorId);
226223
+ const group = reviewCards.get(cardId) ?? { workId: r.targetWorkId, reviewerId: r.reviewerActorId, reviews: [] };
226224
+ group.reviews.push(r);
226225
+ reviewCards.set(cardId, group);
226226
+ }
226227
+ for (const [cardId, { workId, reviewerId, reviews }] of reviewCards) {
226228
+ const summary = reviewSummary(snap, reviews, events);
226229
+ const first = reviews[0];
226230
+ const ids2 = new Set(reviews.map((r) => r.id));
226231
+ const created = events.filter((r) => r.event.kind === "review.create" && ids2.has(r.event.reviewId));
226232
+ const dates = summary.latest.flatMap((r) => [r.createdAt, r.startedAt, r.endedAt, r.decidedAt, r.cancelledAt, r.retryAt].filter((v2) => !!v2));
226233
+ const pending = summary.pending.filter((r) => r.reviewerActorId.startsWith("actor:human:") && (!input.viewerActorId || r.reviewerActorId === input.viewerActorId));
226234
+ visibleReviews.set(cardId, {
226235
+ id: cardId,
226236
+ reviewWorkId: workId,
226237
+ reviewerId,
226238
+ reviewerIds: [reviewerId],
226239
+ traceAvailable: true,
226240
+ seq: created.length ? Math.min(...created.map((r) => r.seq)) : 0,
226241
+ at: first.createdAt,
226242
+ updatedAt: dates.sort().at(-1) ?? first.createdAt,
226243
+ nodeId: first.nodeId,
226244
+ executorId: reviewerId,
226245
+ phase: summary.phase,
226246
+ status: summary.status,
226247
+ detail: summary.latest.length === 1 ? summary.latest[0].note ? `${summary.latest[0].verdict === "request_changes" ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1"}\uFF1A${summary.latest[0].note}` : void 0 : summary.latest.map((r) => `${nameOf(r.reviewerActorId)}\uFF08\u8BC4\u5BA1\u7EC4 ${[...new Set(summary.latest.map((r2) => r2.reviewGroup))].indexOf(r.reviewGroup) + 1}\uFF09\uFF1A${reviewLabel(r)}${r.note ? `
226248
+ ${r.verdict === "request_changes" ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1"}\uFF1A${r.note}` : ""}`).join("\n\n"),
226249
+ artifacts: artifactsByWork.get(workId) ?? [],
226250
+ actions: pending.length ? [
226251
+ { kind: "review", label: BUTTON.approve, target: first.nodeId, revisionId: workId, verdict: "approve" },
226252
+ { kind: "review", label: BUTTON.reject, target: first.nodeId, revisionId: workId, verdict: "request_changes" }
226253
+ ] : []
226254
+ });
226255
+ }
225405
226256
  const drafts = [
225406
226257
  ...standalone,
225407
226258
  ...groupCards.values(),
225408
- ...reviewCards.values(),
226259
+ ...visibleReviews.values(),
225409
226260
  ...issueCards.values(),
225410
226261
  ...acceptCards.values()
225411
226262
  ].sort((a, b2) => a.updatedAt.localeCompare(b2.updatedAt) || a.seq - b2.seq || a.id.localeCompare(b2.id));
@@ -225429,7 +226280,14 @@ function buildWorkorderActivity(input) {
225429
226280
  }
225430
226281
  }
225431
226282
  for (const d of drafts) {
225432
- if (d.id.startsWith("issue:")) d.traceWorkIds = issueWorks.get(d.id.slice("issue:".length));
226283
+ if (!d.id.startsWith("issue:")) continue;
226284
+ const issueId = d.id.slice("issue:".length);
226285
+ if (issueById.get(issueId)?.kind === "gap") {
226286
+ d.traceAvailable = true;
226287
+ d.traceWorkIds = gapOrigins.get(issueId);
226288
+ } else {
226289
+ d.traceWorkIds = issueWorks.get(issueId);
226290
+ }
225433
226291
  }
225434
226292
  const runIdOfCard = (d) => {
225435
226293
  const map = input.runIdByTarget;
@@ -225439,7 +226297,14 @@ function buildWorkorderActivity(input) {
225439
226297
  if (run) return run;
225440
226298
  }
225441
226299
  }
225442
- return d.id.startsWith("review:") ? map?.get(d.id) : void 0;
226300
+ if (d.reviewWorkId) {
226301
+ for (const r of [...reviewWorks.get(d.reviewWorkId) ?? []].reverse()) {
226302
+ if (d.reviewerId && r.reviewerActorId !== d.reviewerId) continue;
226303
+ const run = map?.get(`review:${r.id}`);
226304
+ if (run) return run;
226305
+ }
226306
+ }
226307
+ return void 0;
225443
226308
  };
225444
226309
  const cards = drafts.map((d) => ({
225445
226310
  id: d.id,
@@ -225448,12 +226313,14 @@ function buildWorkorderActivity(input) {
225448
226313
  updatedAt: d.updatedAt,
225449
226314
  ...d.nodeId ? { nodeId: d.nodeId, nodeTitle: nodeName(d.nodeId) } : {},
225450
226315
  executor: ref2(d.executorId),
226316
+ ...d.reviewWorkId ? { reviewWorkId: d.reviewWorkId, reviewers: d.reviewerIds?.map(ref2) } : {},
225451
226317
  ...d.handActorId ? { handActor: ref2(d.handActorId) } : {},
225452
226318
  phase: d.phase,
225453
226319
  status: d.status,
225454
226320
  ...d.detail ? { detail: d.detail } : {},
225455
226321
  artifacts: d.artifacts,
225456
226322
  actions: d.actions,
226323
+ ...d.traceAvailable ? { traceAvailable: true } : {},
225457
226324
  ...d.traceWorkIds?.length ? { traceWorkIds: d.traceWorkIds, traceAvailable: true } : {},
225458
226325
  .../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(runIdOfCard(d))
225459
226326
  }));
@@ -225465,6 +226332,7 @@ var init_activity2 = __esm({
225465
226332
  "use strict";
225466
226333
  init_src();
225467
226334
  init_src4();
226335
+ init_review_activity();
225468
226336
  init_workorder_manager();
225469
226337
  ACTIVITY_EVENT_KINDS = [
225470
226338
  "plan.changed",
@@ -225629,12 +226497,21 @@ function buildWorkorderActivityTrace(input) {
225629
226497
  const ref2 = input.ref ?? ((id) => ({ id }));
225630
226498
  const empty2 = { workorderId, cardId, attempts: [] };
225631
226499
  if (!snap) return empty2;
226500
+ const reviews = activityReviews(snap, input.events ?? []);
226501
+ const reviewTarget = reviewCardTarget(cardId, reviews);
226502
+ if (reviewTarget) return buildReviewHistory(input, reviewTarget.targetWorkId, reviewTarget.reviewerActorId, reviews);
225632
226503
  const card2 = input.events ? buildWorkorderActivity({
225633
226504
  workorderId,
225634
226505
  snap,
225635
226506
  events: input.events,
225636
226507
  ref: ref2
225637
226508
  }).cards.find((c) => c.id === cardId) : void 0;
226509
+ const gap = cardId.startsWith("issue:") ? snap.issues.find((i) => i.id === cardId.slice(6) && i.kind === "gap") : void 0;
226510
+ if (gap && !card2?.traceWorkIds?.length) return {
226511
+ ...empty2,
226512
+ works: [],
226513
+ notice: "\u7F3A\u53E3\u8BB0\u5F55\u6CA1\u6709\u660E\u786E\u5173\u8054\u5230\u67D0\u6B21\u6267\u884C\uFF0C\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u8F68\u8FF9\u3002"
226514
+ };
225638
226515
  const workIds = card2?.traceWorkIds ?? (cardId.startsWith("work:") ? [cardId.slice(5)] : []);
225639
226516
  if (workIds.length > 0) {
225640
226517
  let firstBriefing;
@@ -225666,6 +226543,7 @@ function buildWorkorderActivityTrace(input) {
225666
226543
  cardId,
225667
226544
  briefing: firstBriefing,
225668
226545
  works,
226546
+ ...gap && !gap.aboutWorkId ? { notice: "\u6839\u636E\u7F3A\u53E3\u63D0\u51FA\u65F6\u7684\u8BB0\u5F55\uFF0C\u5173\u8054\u5230\u8BE5\u8282\u70B9\u3001\u8BE5\u6267\u884C\u4EBA\u5F53\u65F6\u552F\u4E00\u7684\u6267\u884C\u3002" } : {},
225669
226547
  attempts: works.flatMap((w2) => w2.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId))
225670
226548
  };
225671
226549
  }
@@ -225781,10 +226659,98 @@ function buildSingleTrace(input) {
225781
226659
  });
225782
226660
  return { workorderId, cardId, briefing, attempts };
225783
226661
  }
226662
+ function buildReviewHistory(input, targetWorkId, reviewerActorId, allReviews) {
226663
+ const { workorderId, cardId } = input;
226664
+ const snap = input.snap;
226665
+ const ref2 = input.ref ?? ((id) => ({ id }));
226666
+ const rows = allReviews.filter((r) => r.targetWorkId === targetWorkId && r.reviewerActorId === reviewerActorId);
226667
+ if (!rows.length) return { workorderId, cardId, attempts: [] };
226668
+ const byId = new Map(rows.map((r) => [r.id, r]));
226669
+ const current = new Set(latestActivityReviews(rows, input.events ?? []).map((r) => r.id));
226670
+ const work = snap.works.find((w2) => w2.id === targetWorkId);
226671
+ const nodeId = rows[0].nodeId;
226672
+ const events = [];
226673
+ for (const rec of [...input.events ?? []].sort((a, b2) => a.seq - b2.seq)) {
226674
+ const e = rec.event;
226675
+ const r = "reviewId" in e && e.reviewId ? byId.get(e.reviewId) : void 0;
226676
+ const direct = e.kind === "review.response" && e.targetWorkId === targetWorkId && e.reviewerActorId === reviewerActorId;
226677
+ let retry = false;
226678
+ if (e.kind === "plan.node_retry" && e.nodeId === nodeId) {
226679
+ const preceding = snap.works.filter((w2) => w2.nodeId === nodeId && w2.createdAt <= rec.createdAt).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
226680
+ retry = preceding[0]?.id === targetWorkId && rows.some((r2) => r2.createdAt <= rec.createdAt);
226681
+ }
226682
+ if (!r && !direct && !retry) continue;
226683
+ let label;
226684
+ let detail;
226685
+ switch (e.kind) {
226686
+ case "review.create":
226687
+ label = "\u521B\u5EFA\u5BA1\u6838";
226688
+ break;
226689
+ case "review.started":
226690
+ label = "\u5F00\u59CB\u5BA1\u6838";
226691
+ break;
226692
+ case "review.timeout":
226693
+ label = "\u5BA1\u6838\u6267\u884C\u8D85\u65F6 / \u5931\u8D25";
226694
+ break;
226695
+ case "review.kill":
226696
+ label = "\u5BA1\u6838\u5DF2\u505C\u6B62";
226697
+ detail = e.reason;
226698
+ break;
226699
+ case "review.response":
226700
+ label = e.verdict === "approve" ? "\u5BA1\u6838\u901A\u8FC7" : "\u5BA1\u6838\u4E0D\u901A\u8FC7";
226701
+ detail = e.note ?? void 0;
226702
+ break;
226703
+ case "plan.node_retry":
226704
+ label = "\u4ECB\u5165\u91CD\u8BD5";
226705
+ break;
226706
+ default:
226707
+ continue;
226708
+ }
226709
+ const actorId = e.kind === "plan.node_retry" ? e.by : "reviewerActorId" in e ? e.reviewerActorId : rec.actorId;
226710
+ events.push({
226711
+ seq: rec.seq,
226712
+ at: rec.createdAt,
226713
+ kind: e.kind,
226714
+ label,
226715
+ actor: ref2(actorId),
226716
+ ...r ? { reviewId: r.id, reviewGroup: r.reviewGroup } : {},
226717
+ ...detail ? { detail } : {}
226718
+ });
226719
+ }
226720
+ const reviews = rows.map((r) => {
226721
+ const single = buildSingleTrace({ ...input, snap: { ...snap, reviews: allReviews }, cardId: `review:${r.id}` });
226722
+ return {
226723
+ reviewId: r.id,
226724
+ reviewGroup: r.reviewGroup,
226725
+ actor: ref2(r.reviewerActorId),
226726
+ at: r.createdAt,
226727
+ ...r.endedAt ?? r.decidedAt ?? r.cancelledAt ? { endedAt: r.endedAt ?? r.decidedAt ?? r.cancelledAt } : {},
226728
+ status: reviewLabel(r),
226729
+ current: current.has(r.id),
226730
+ ...r.note ? { note: r.note } : {},
226731
+ attempts: single.attempts
226732
+ };
226733
+ });
226734
+ return {
226735
+ workorderId,
226736
+ cardId,
226737
+ attempts: reviews.flatMap((r) => r.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId)),
226738
+ reviewHistory: {
226739
+ targetWorkId,
226740
+ nodeTitle: snap.nodes.find((n) => n.id === nodeId)?.title ?? nodeId,
226741
+ ...work?.outputVersionNo != null ? { outputVersionNo: work.outputVersionNo } : {},
226742
+ artifacts: work ? artifactsOfWork(work, input) : [],
226743
+ reviews,
226744
+ events,
226745
+ ...input.dispatches === void 0 ? { recordsUnavailable: true } : {}
226746
+ }
226747
+ };
226748
+ }
225784
226749
  var init_activity_trace = __esm({
225785
226750
  "../server/src/domains/collab/activity-trace.ts"() {
225786
226751
  "use strict";
225787
226752
  init_src4();
226753
+ init_review_activity();
225788
226754
  init_activity2();
225789
226755
  init_workorder_manager();
225790
226756
  }
@@ -227870,6 +228836,7 @@ function collabDomain(opts) {
227870
228836
  return {
227871
228837
  status: 200,
227872
228838
  body: buildWorkorderActivity({
228839
+ viewerActorId: req.auth.actor,
227873
228840
  workorderId,
227874
228841
  snap,
227875
228842
  events,
@@ -227905,7 +228872,12 @@ function collabDomain(opts) {
227905
228872
  };
227906
228873
  const [snap, events] = engineStore ? await engineStore.transaction(async (tx) => [
227907
228874
  await tx.loadWorkorder(workorderId),
227908
- await tx.listEvents(workorderId, 0, ACTIVITY_EVENT_LIMIT, ACTIVITY_EVENT_KINDS)
228875
+ await tx.listEvents(
228876
+ workorderId,
228877
+ 0,
228878
+ cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? void 0 : ACTIVITY_EVENT_LIMIT,
228879
+ cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? REVIEW_TRACE_EVENT_KINDS : ACTIVITY_EVENT_KINDS
228880
+ )
227909
228881
  ]) : [null, []];
227910
228882
  const manifestFiles = snap ? await resolveActivityManifestFiles(snap.artifacts, blobs) : void 0;
227911
228883
  const dispatches = await listDispatchRows(workorderId, opts.dispatchesOfWorkorder);
@@ -228026,6 +228998,9 @@ function collabDomain(opts) {
228026
228998
  return { status: 201, body: { item } };
228027
228999
  });
228028
229000
  router.post("/api/workorders/:id/hold", async (req) => {
229001
+ if (!isHumanActor(req.auth.actor)) {
229002
+ return { status: 403, body: { error: { code: "forbidden", message: "\u6682\u505C\u6574\u4E2A\u5DE5\u5355\u662F\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
229003
+ }
228029
229004
  const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
228030
229005
  const ws = req.params.id;
228031
229006
  const exists = [...kernel.model.artifacts.values()].some((a) => a.workspace === ws);
@@ -228065,6 +229040,9 @@ function collabDomain(opts) {
228065
229040
  return { status: 200, body: { cancelled } };
228066
229041
  });
228067
229042
  router.post("/api/workorders/:id/dispatch", async (req) => {
229043
+ if (!isHumanActor(req.auth.actor)) {
229044
+ return { status: 403, body: { error: { code: "forbidden", message: "\u6062\u590D\u6574\u4E2A\u5DE5\u5355\u662F\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
229045
+ }
228068
229046
  const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
228069
229047
  const ws = req.params.id;
228070
229048
  const already = !kernel.model.pausedWorkorders.has(ws);
@@ -228390,6 +229368,7 @@ var init_collab = __esm({
228390
229368
  init_workorder_detail();
228391
229369
  init_workorders();
228392
229370
  init_activity2();
229371
+ init_review_activity();
228393
229372
  init_activity_trace();
228394
229373
  init_inbox();
228395
229374
  init_escalation_attention();
@@ -231745,7 +232724,8 @@ function createChatSessionsDomain(opts) {
231745
232724
  ...Object.prototype.hasOwnProperty.call(body2, "projectId") ? { projectId: body2.projectId } : {},
231746
232725
  // 会话级模型覆盖:显式传 null / 空串 = 清除覆盖(回到旧解析);不传这一项 = 不动它。
231747
232726
  ...Object.prototype.hasOwnProperty.call(body2, "model") ? { model: normalizeModel(body2.model) } : {},
231748
- touchedAt: body2.touchedAt ?? (/* @__PURE__ */ new Date()).toISOString()
232727
+ // 只有显式传了才写——改标题 / 换项目 / 换模型都不是「有新动静」,不该顶掉列表顺序与已读水位。
232728
+ ...body2.touchedAt !== void 0 ? { touchedAt: body2.touchedAt } : {}
231749
232729
  });
231750
232730
  return { status: 200, body: await store2.getSession(req.params.id) };
231751
232731
  });
@@ -231964,7 +232944,11 @@ function createChatSessionsDomain(opts) {
231964
232944
  ...record8.label ? { label: record8.label } : {},
231965
232945
  ...record8.taskDigest ? { taskDigest: record8.taskDigest } : {},
231966
232946
  parentMessageId: record8.parentMessageId,
231967
- touches: touches.map((t) => ({ messageId: t.messageId, kind: t.kind, at: t.at })),
232947
+ /* `messageId` 必须过 `touchAnchorMessageId`:流水行的主键是 `(delegation_id, message_id)`,
232948
+ 服务端为此在锚点后缀了一段随机 token(`DelegationService.touchAnchor`)。那段后缀是存储
232949
+ 的去重装置,直接发给前端等于给了一条 `messages[]` 里不存在的 id ——追加转达卡全部掉进
232950
+ floating 堆在流末尾,同一条消息上的两条委派还会因后缀不同被拆成两张单专家卡。 */
232951
+ touches: touches.map((t) => ({ messageId: touchAnchorMessageId(t.messageId), kind: t.kind, at: t.at })),
231968
232952
  artifacts: collectDelegationArtifacts(record8.roundSummaries),
231969
232953
  createdAt: record8.createdAt,
231970
232954
  ...record8.settledAt ? { settledAt: record8.settledAt } : {}
@@ -244470,6 +245454,64 @@ var init_postgres_chat_sessions = __esm({
244470
245454
  async appendMessage(m2, opts) {
244471
245455
  return (await this.appendMessageOnce(m2, opts)).message;
244472
245456
  }
245457
+ /**
245458
+ * 系统状态条的 `chat_items` 补行(ADR-0510 D3 的收口)。
245459
+ *
245460
+ * ## 为什么需要
245461
+ *
245462
+ * `role='system'` 的通告(委派回报「已完成 / 无法完成」、工单终态播报、授权卡状态条)走的是
245463
+ * `appendChatMessageOnce` → 本 store 直写 `chat_messages` 这条路,**不经 `ChatItemLedger`**
245464
+ * (`chat-system-message.ts` 文件头原话:「非 live 直写……不产生 chat_items」)。
245465
+ *
245466
+ * 在 D3 之前这没关系:`/items` 会给没有 item 行的消息合成一条快照条。D3 把历史收成
245467
+ * **只从 `chat_items` 出**、删掉了那条合成路之后,这些通告就从对话页上**凭空消失**了。
245468
+ *
245469
+ * 后果不只是「少看见一条通告」——委派产物的附件卡是**锚在那条回报通告之后的第一条 assistant**
245470
+ * 上的(ws:wo-4a2cd648 §①)。通告不在流里 ⇒ 找不到锚点 ⇒ 卡掉进 floating、渲染到末尾。
245471
+ * 2026-09-09 用户现场:`--continue` 续跑两个子会话,回报通告写进了 `chat_messages`
245472
+ * (07:29:56 / 07:30:12)但 `chat_items` 一行都没有,产物卡因此挂错位置。
245473
+ *
245474
+ * ## 为什么补在这一层
245475
+ *
245476
+ * `appendChatMessageOnce` 是所有系统消息的唯一入口,但那三个调用点(return-flow /
245477
+ * chat-broadcast / server.ts 授权卡)**都拿不到 `ChatItemStore`**,从上面递下来要动三份 deps 契约。
245478
+ * 而这里本来就握着同一个 schema 的连接池——把「用户能看见的消息必有 item 行」这条不变量
245479
+ * 落在存储边界上,比在三个业务处各接一次线更难漏。
245480
+ *
245481
+ * ## 失败了怎么办:吞掉
245482
+ *
245483
+ * item 写失败**不许**把消息本身带走——消息已经 INSERT 成功、幂等键已经占住,抛出去会让调用方
245484
+ * 当成「没写成」而重试,而重试会撞主键。所以这里只记日志。代价是那条通告在页面上仍然看不见,
245485
+ * 与修复前一致,不会更坏。
245486
+ */
245487
+ async writeSystemMessageItem(m2) {
245488
+ try {
245489
+ await this.pool.query(
245490
+ `INSERT INTO ${this.s}.chat_items
245491
+ (id, session_id, message_id, turn_id, run_id, seq, kind, role, status,
245492
+ version, turn_version, provider_item_key, payload, metadata,
245493
+ created_at, updated_at, completed_at)
245494
+ VALUES ($1, $2, $3, $4, NULL, 1, 'text', $5, 'completed',
245495
+ COALESCE((SELECT MAX(version) FROM ${this.s}.chat_items WHERE session_id = $2), 0) + 1,
245496
+ 1, NULL, $6::jsonb, '{"legacyContent": false}'::jsonb,
245497
+ $7::timestamptz, $7::timestamptz, $7::timestamptz)
245498
+ ON CONFLICT (id) DO NOTHING`,
245499
+ [
245500
+ // 与 D3 之前 `/items` 合成条、以及 backfill 补的行**同一套身份**:同一条消息永远只有
245501
+ // 这一个 itemId,重复调用(幂等重写、backfill 再跑)不会造出第二条。
245502
+ `chat-message:${m2.id}`,
245503
+ m2.sessionId,
245504
+ m2.id,
245505
+ `chat-message-turn:${m2.id}`,
245506
+ m2.role,
245507
+ JSON.stringify({ role: m2.role, text: m2.content ?? "" }),
245508
+ m2.createdAt
245509
+ ]
245510
+ );
245511
+ } catch (err) {
245512
+ console.warn(`[chat-items] \u7CFB\u7EDF\u72B6\u6001\u6761\u8865\u884C\u5931\u8D25\uFF08message=${m2.id} session=${m2.sessionId}\uFF09: ${String(err)}`);
245513
+ }
245514
+ }
244473
245515
  async appendMessageOnce(m2, opts) {
244474
245516
  await this.assertSessionInScope(m2.sessionId);
244475
245517
  const insert = async () => {
@@ -244486,7 +245528,9 @@ var init_postgres_chat_sessions = __esm({
244486
245528
  };
244487
245529
  for (let attempt = 0; ; attempt++) {
244488
245530
  try {
244489
- return { message: { ...m2, seq: await insert() }, inserted: true };
245531
+ const written = { message: { ...m2, seq: await insert() }, inserted: true };
245532
+ if ((opts?.source ?? "live") === "system") await this.writeSystemMessageItem(written.message);
245533
+ return written;
244490
245534
  } catch (err) {
244491
245535
  const isUniqueViolation5 = err && typeof err === "object" && err.code === "23505";
244492
245536
  const constraint = String(err.constraint ?? "");
@@ -245138,11 +246182,17 @@ var init_postgres_chat_sessions = __esm({
245138
246182
  model: row.model ?? null
245139
246183
  });
245140
246184
  rowToSessionWithQuality = (row) => {
246185
+ const status = row.last_assistant_status ?? void 0;
245141
246186
  const quality = deriveLastTurnQuality({
245142
- status: row.last_assistant_status ?? void 0,
246187
+ status,
245143
246188
  content: row.last_assistant_content ?? ""
245144
246189
  });
245145
- return { ...rowToSession(row), ...quality ? { lastTurnQuality: quality } : {} };
246190
+ const running = deriveSessionRunning({ status });
246191
+ return {
246192
+ ...rowToSession(row),
246193
+ ...quality ? { lastTurnQuality: quality } : {},
246194
+ ...running ? { running: true } : {}
246195
+ };
245146
246196
  };
245147
246197
  rowToMessage = (row) => ({
245148
246198
  id: row.id,
@@ -263050,7 +264100,7 @@ var COMMAND_DECLS = {
263050
264100
  { name: "as", desc: "\u7ED9\u8FD9\u6B21\u59D4\u6D3E\u8D77\u4E2A\u77ED\u540D\uFF0C\u4E4B\u540E\u7528\u5B83 --continue\uFF1B\u649E\u540D\u670D\u52A1\u7AEF\u81EA\u52A8\u52A0\u540E\u7F00\uFF0C**\u4EE5\u8F93\u51FA\u91CC\u90A3\u4E2A\u4E3A\u51C6**" },
263051
264101
  { name: "file", desc: "\u4E00\u8D77\u5E26\u8FC7\u53BB\u7684\u6587\u4EF6\uFF08\u4F60\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u7684\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u53EF\u7ED9\u591A\u6B21\uFF1B\u5355\u4E2A \u22642MB\u3001\u4E00\u6B21 \u226420 \u4E2A / 20MB\uFF09" },
263052
264102
  { name: "continue", desc: "\u5F80\u5DF2\u6709\u59D4\u6D3E\u91CC\u518D\u8F6C\u8FBE\u4E00\u53E5\uFF1A\u7ED9 label / \u4E13\u5BB6\u540D / delegationId \u524D\u7F00" },
263053
- { name: "text", desc: "\u8F6C\u8FBE\u5185\u5BB9\uFF08\u914D --continue\uFF09\u3002\u6539\u65B9\u5411\u5C31\u660E\u8BF4\u300C\u6539\u65B9\u5411\u300D\uFF0C\u4E13\u5BB6\u4F1A\u81EA\u5DF1\u5224\u65AD\u8981\u4E0D\u8981\u653E\u5F03\u624B\u4E0A\u90A3\u6BB5" },
264103
+ { name: "text", desc: "\u8F6C\u8FBE\u5185\u5BB9\uFF08\u914D --continue\uFF09\u3002\u6539\u65B9\u5411\u5C31\u660E\u8BF4\u300C\u6539\u65B9\u5411\u300D\uFF0C\u4E13\u5BB6\u4F1A\u81EA\u5DF1\u5224\u65AD\u8981\u4E0D\u8981\u653E\u5F03\u624B\u4E0A\u90A3\u6BB5\u3002**\u770B\u56DE\u6267**\uFF1A\u63D2\u8FDB\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E / \u8D77\u4E86\u65B0\u7684\u4E00\u8F6E / \u8FD8\u6392\u5728\u961F\u5217\u91CC\u2014\u2014\u6392\u961F\u65F6\u4E13\u5BB6\u8981\u7B49\u672C\u8F6E\u8DD1\u5B8C\u624D\u770B\u89C1\uFF0C\u8BE5\u8DDF\u7528\u6237\u8BF4\u4E00\u58F0" },
263054
264104
  { name: "status", desc: "\u770B\u4E00\u4E2A\u59D4\u6D3E\u7684\u8BE6\u60C5\uFF1A\u5168\u91CF\u5404\u8F6E\u5C0F\u7ED3 + \u4EA7\u7269\u6E05\u5355" },
263055
264105
  { name: "list", desc: "\u5217\u51FA\u8FD9\u6761\u5BF9\u8BDD\u6D3E\u51FA\u53BB\u7684\u6D3B\uFF1B\u9ED8\u8BA4\u53EA\u7ED9\u8FDB\u884C\u4E2D + \u6700\u8FD1 10 \u6761", boolean: true },
263056
264106
  { name: "all", desc: "\u914D --list\uFF1A\u8FDE\u5386\u53F2\u59D4\u6D3E\u4E00\u8D77\u5217", boolean: true },
@@ -265536,15 +266586,26 @@ ${round.text}`);
265536
266586
  const text5 = flags.get("text");
265537
266587
  if (!text5) throw new Error('--continue \u8981\u914D --text "<\u8981\u8F6C\u8FBE\u7684\u8BDD>"');
265538
266588
  const target = await resolveRef2(continueRef);
265539
- await api.post(
266589
+ const outcome = await api.post(
265540
266590
  `/api/delegations/${encodeURIComponent(target.id)}/messages`,
265541
266591
  { text: text5, ...fileArgs.length ? { files: fileArgs } : {} }
265542
266592
  );
265543
266593
  if (asJson2) {
265544
- println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs }, null, 2));
266594
+ println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs, ...outcome }, null, 2));
265545
266595
  break;
265546
266596
  }
265547
- println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${target.expertName ?? target.expertActorId}\uFF08${target.label ?? target.id.slice(0, 8)}\uFF09\u3002`);
266597
+ const who = `${target.expertName ?? target.expertActorId}\uFF08${target.label ?? target.id.slice(0, 8)}\uFF09`;
266598
+ if (outcome?.outcome === "inserted") {
266599
+ println(`\u5DF2\u63D2\u8FDB ${who} \u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E\u2014\u2014\u5B83\u628A\u5F53\u524D\u8FD9\u4E00\u6B65\u7B54\u5B8C\u5C31\u4F1A\u770B\u5230\u3002`);
266600
+ } else if (outcome?.outcome === "started-round") {
266601
+ println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${who}\uFF1A\u5B83\u4E0A\u4E00\u8F6E\u5DF2\u7ECF\u7ED3\u675F\uFF0C\u8FD9\u53E5\u8BDD\u5F53\u573A\u8D77\u4E86\u65B0\u7684\u4E00\u8F6E\u3002`);
266602
+ } else if (outcome?.outcome === "queued") {
266603
+ println(`\u5DF2\u6536\u4E0B\uFF0C\u4F46${who}**\u73B0\u5728\u8FD8\u770B\u4E0D\u5230**\uFF1A${outcome.reason ?? "\u63D2\u4E0D\u8FDB\u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E"}\u3002`);
266604
+ println(` \u8FD9\u53E5\u8BDD\u6392\u5728\u5F85\u8F6C\u8FBE\u961F\u5217\u91CC\uFF08\u8FD8\u6709 ${outcome.pendingInputs} \u53E5\u6CA1\u9001\u5230\uFF09\uFF0C\u672C\u8F6E\u8DD1\u5B8C\u4F1A\u4F5C\u4E3A\u4E0B\u4E00\u8F6E\u53D1\u51FA\u3002`);
266605
+ println(" \u5B83\u8FD9\u4E00\u8F6E\u53EF\u80FD\u8FD8\u5728\u6309\u65E7\u65B9\u5411\u505A\u2014\u2014\u8981\u4E0D\u8981\u5728\u8FD9\u6761\u5BF9\u8BDD\u91CC\u5148\u8DDF\u7528\u6237\u8BF4\u660E\uFF0C\u4F60\u81EA\u5DF1\u5224\u65AD\u3002");
266606
+ } else {
266607
+ println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${who}\u3002`);
266608
+ }
265548
266609
  if (fileArgs.length) println(` \u4E00\u8D77\u5E26\u8FC7\u53BB\u7684\u6587\u4EF6\uFF1A${fileArgs.join("\u3001")}`);
265549
266610
  println("\u8FD9\u4E00\u8F6E\u5230\u6B64\u4E3A\u6B62\uFF1A\u7ED3\u679C\u4F1A\u81EA\u5DF1\u56DE\u5230\u8FD9\u6761\u5BF9\u8BDD\uFF0C\u4E0D\u8981\u5728\u8FD9\u91CC\u7B49\u3002");
265550
266611
  break;
@@ -268340,7 +269401,7 @@ function shimScript() {
268340
269401
  }
268341
269402
 
268342
269403
  // src/index.ts
268343
- var PKG_VERSION = true ? "2.2.2" : "dev";
269404
+ var PKG_VERSION = true ? "2.2.3" : "dev";
268344
269405
  var LOCAL_BIN = localBin();
268345
269406
  var NPM_PREFIX = npmPrefix();
268346
269407
  var INSTANCE = DEFAULT_INSTANCE;