oasis_test 0.1.106 → 0.1.107

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 +535 -142
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3429,12 +3429,19 @@ async function assembleContext(args) {
3429
3429
  if (args.resolveUpstreamHandoff && artifact.workspace) {
3430
3430
  for (const edge of artifact.inputs) {
3431
3431
  if (edge.pinned === null) continue;
3432
- const content = await args.resolveUpstreamHandoff({
3433
- workOrderId: artifact.workspace,
3434
- downstreamNodeId: artifact.id,
3435
- upstreamNodeId: edge.to,
3436
- pinnedWorkId: edge.pinned
3437
- });
3432
+ let content = null;
3433
+ try {
3434
+ content = await args.resolveUpstreamHandoff({
3435
+ workOrderId: artifact.workspace,
3436
+ downstreamNodeId: artifact.id,
3437
+ upstreamNodeId: edge.to,
3438
+ pinnedWorkId: edge.pinned
3439
+ });
3440
+ } catch (err) {
3441
+ console.warn(
3442
+ `[assemble] ${artifact.id} \u4E0A\u6E38 ${edge.to} \u4EA4\u63A5\u89E3\u6790\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u6CE8\u5165 _HANDOFF.md\uFF1A` + (err instanceof Error ? err.message : String(err))
3443
+ );
3444
+ }
3438
3445
  if (content) {
3439
3446
  const dir = `inputs/${slug2(edge.to)}@${edge.pinned.slice(9, 17)}`;
3440
3447
  upstreamHandoffFiles.set(`${dir}/${UPSTREAM_HANDOFF_BASENAME}`, content);
@@ -6024,11 +6031,18 @@ var init_dispatcher = __esm({
6024
6031
  bundle.files["execution/CONTINUITY.md"] = this.opts.executionContinuityInstructions;
6025
6032
  }
6026
6033
  if (this.opts.resolveExecutionResumePack && (spec.action === "produce" || spec.action === "integrate") && continuityArtifact) {
6027
- const resumePack = await this.opts.resolveExecutionResumePack(
6028
- spec.artifactId,
6029
- continuityArtifact.workspace,
6030
- { jobKey, ...spec.part !== void 0 ? { part: spec.part } : {} }
6031
- );
6034
+ let resumePack = null;
6035
+ try {
6036
+ resumePack = await this.opts.resolveExecutionResumePack(
6037
+ spec.artifactId,
6038
+ continuityArtifact.workspace,
6039
+ { jobKey, ...spec.part !== void 0 ? { part: spec.part } : {} }
6040
+ );
6041
+ } catch (err) {
6042
+ console.warn(
6043
+ `[dispatch] ${spec.artifactId} \u6062\u590D\u5305\u89E3\u6790\u5931\u8D25\uFF0C\u672C\u8F6E\u6309\u9996\u6B21\u6267\u884C\u6D3E\u53D1\uFF08\u4E0D\u6CE8\u5165 execution/RESUME.md\uFF09\uFF1A` + (err instanceof Error ? err.message : String(err))
6044
+ );
6045
+ }
6032
6046
  if (resumePack) {
6033
6047
  bundle.files["execution/RESUME.md"] = resumePack;
6034
6048
  bundle.files["TASK.md"] += [
@@ -6700,6 +6714,15 @@ var init_session_limits = __esm({
6700
6714
  });
6701
6715
 
6702
6716
  // ../engine/src/model.ts
6717
+ function isMainWork(w2) {
6718
+ return w2.lane === "main";
6719
+ }
6720
+ function isReplyWork(w2) {
6721
+ return w2.lane === "reply";
6722
+ }
6723
+ function replyIssueIdOf(w2) {
6724
+ return w2.lane === "reply" ? w2.replyToIssueId ?? null : null;
6725
+ }
6703
6726
  var BUSINESS_HANDOFF_POLICY_FIELD, BUSINESS_HANDOFF_POLICY_EXEMPT, DEFAULT_CONFIG, deriveWorkId, deriveReplyWorkId, deriveReviewId, deriveReplyId;
6704
6727
  var init_model = __esm({
6705
6728
  "../engine/src/model.ts"() {
@@ -6798,7 +6821,7 @@ function isReviewOpen(r) {
6798
6821
  return !r.cancelledAt && !r.endedAt && !r.verdict;
6799
6822
  }
6800
6823
  function countFailedForNode(works) {
6801
- const sorted = [...works].filter((w2) => !w2.replyToIssueId).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
6824
+ const sorted = works.filter(isMainWork).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
6802
6825
  let count2 = 0;
6803
6826
  for (const w2 of sorted) {
6804
6827
  if (w2.retryAt) break;
@@ -6814,7 +6837,7 @@ function countFailedForNode(works) {
6814
6837
  return count2;
6815
6838
  }
6816
6839
  function countFailedForReplyRound(works) {
6817
- const sorted = works.filter((w2) => w2.replyToIssueId).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
6840
+ const sorted = works.filter(isReplyWork).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
6818
6841
  let count2 = 0;
6819
6842
  for (const w2 of sorted) {
6820
6843
  if (w2.retryAt) break;
@@ -6858,6 +6881,7 @@ function findUnresolvedCRFromOthers(nodeId, issues) {
6858
6881
  var init_views2 = __esm({
6859
6882
  "../engine/src/views.ts"() {
6860
6883
  "use strict";
6884
+ init_model();
6861
6885
  }
6862
6886
  });
6863
6887
 
@@ -6866,6 +6890,7 @@ var WorkorderState;
6866
6890
  var init_state = __esm({
6867
6891
  "../engine/src/state.ts"() {
6868
6892
  "use strict";
6893
+ init_model();
6869
6894
  init_views2();
6870
6895
  WorkorderState = class {
6871
6896
  workorder;
@@ -6925,12 +6950,25 @@ var init_state = __esm({
6925
6950
  allWorks() {
6926
6951
  return [...this.workMap.values()];
6927
6952
  }
6928
- worksOf(nodeId) {
6953
+ /**
6954
+ * ★ 该节点的**全部** work——**主链与回信轮都在里面**(刺眼命名:调它 = 显式声明「我不按 lane 过滤」)。
6955
+ * 只有确实需要「所有运行」的入口才用它(work 编号 = 现有数 +1、I1 占位判定、连败链计数——那些函数自己再按
6956
+ * lane 分)。要主链就 {@link mainWorksOf}、要回信就 {@link replyWorksOf},别在调用点手写 `!replyToIssueId`。
6957
+ */
6958
+ allWorksIncludingReplies(nodeId) {
6929
6959
  return this.allWorks().filter((w2) => w2.nodeId === nodeId);
6930
6960
  }
6931
- /** 该节点有没有还占着名额的运行(I1 判据的一半,另一半见 hasQueuedWorkFor)。 */
6961
+ /** 该节点的**主链** work(派发/验收/发版本号那条道)。判据 = lane({@link isMainWork})。 */
6962
+ mainWorksOf(nodeId) {
6963
+ return this.allWorksIncludingReplies(nodeId).filter(isMainWork);
6964
+ }
6965
+ /** 该节点的**回信轮** work(对 comment 的答话那条道)。判据 = lane({@link isReplyWork})。 */
6966
+ replyWorksOf(nodeId) {
6967
+ return this.allWorksIncludingReplies(nodeId).filter(isReplyWork);
6968
+ }
6969
+ /** 该节点有没有还占着名额的运行(I1 判据的一半,另一半见 hasQueuedWorkFor)。主链或回信都占名额。 */
6932
6970
  openWorkOf(nodeId) {
6933
- return this.worksOf(nodeId).find(isWorkOpen);
6971
+ return this.allWorksIncludingReplies(nodeId).find(isWorkOpen);
6934
6972
  }
6935
6973
  artifactsOf(workId) {
6936
6974
  return this.artifactList.filter((a) => a.workId === workId);
@@ -6972,7 +7010,7 @@ var init_state = __esm({
6972
7010
  }
6973
7011
  /** 本事务是否已对该节点的某个 work 排了 work.accept——activate 据此避让。 */
6974
7012
  hasAcceptedWorkFor(nodeId) {
6975
- const myWorks = new Set(this.worksOf(nodeId).map((w2) => w2.id));
7013
+ const myWorks = new Set(this.allWorksIncludingReplies(nodeId).map((w2) => w2.id));
6976
7014
  return this.emittedList.some(
6977
7015
  (e) => e.kind === "work.accept" && myWorks.has(e.workId)
6978
7016
  );
@@ -7135,7 +7173,7 @@ function scan(state, ctx) {
7135
7173
  continue;
7136
7174
  }
7137
7175
  if (wstate === "failed") {
7138
- const cf = countFailedForNode(state.worksOf(node.id));
7176
+ const cf = countFailedForNode(state.allWorksIncludingReplies(node.id));
7139
7177
  const overLimit = cf > (ctx.config?.maxRetries ?? DEFAULT_CONFIG.maxRetries);
7140
7178
  if (overLimit && classify(node.assigneeActorId) === "agent") {
7141
7179
  events.push({
@@ -7194,7 +7232,7 @@ function tryToRun(state, nodeId, ctx, events) {
7194
7232
  if (isBusinessDependencyEdge(state, e) && !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return;
7195
7233
  }
7196
7234
  if (state.hasQueuedWorkFor(nodeId)) return;
7197
- const workId = deriveWorkId(nodeId, state.worksOf(nodeId).length + 1);
7235
+ const workId = deriveWorkId(nodeId, state.allWorksIncludingReplies(nodeId).length + 1);
7198
7236
  events.push({
7199
7237
  kind: "work.create",
7200
7238
  workorderId: state.workorder.id,
@@ -7318,7 +7356,7 @@ function processCommentReplies(state, nodeId, ctx, events) {
7318
7356
  const comments = state.allIssues().filter((i) => i.kind === "comment" && i.resolvedAt === null && i.aboutNodeId === nodeId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
7319
7357
  if (comments.length === 0) return;
7320
7358
  if (state.hasQueuedWorkFor(nodeId)) return;
7321
- const replyWorks = state.worksOf(nodeId).filter((w2) => w2.replyToIssueId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
7359
+ const replyWorks = state.replyWorksOf(nodeId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
7322
7360
  const inflight = replyWorks.find((w2) => !w2.endedAt && !w2.deadAt);
7323
7361
  if (inflight) {
7324
7362
  const start = inflight.startedAt ?? inflight.createdAt;
@@ -7483,16 +7521,24 @@ function handoffBlock(state, nodeId, workId) {
7483
7521
  overridable: false
7484
7522
  };
7485
7523
  }
7486
- const delivered = work?.businessHandoffStatus === "delivered" || work?.status === "success" && work.agentHandoffAttemptId === null && work.businessHandoffStatus === null;
7487
- if (delivered) return null;
7488
- return {
7489
- code: "handoff-undeclared",
7490
- detail: `\u4E1A\u52A1\u4EA4\u63A5\u672A\u5B8C\u6210\uFF1Awork.status=${work?.status ?? "?"}\u3001handoff=${work?.businessHandoffStatus ?? "\u672A\u58F0\u660E"}${work?.agentHandoffAttemptId ? `\uFF08attempt ${work.agentHandoffAttemptId}\uFF09` : ""}\u2014\u2014\u7B49\u6267\u884C\u4EBA\u8865\u4EA4\u63A5\uFF0C\u6216\u7531\u4EBA force-conclude \u7B7E\u5B57\u7A7F\u900F`,
7491
- overridable: true
7492
- };
7524
+ if (work?.businessHandoffStatus === "not_delivered") {
7525
+ return {
7526
+ code: "handoff-undeclared",
7527
+ detail: `\u6267\u884C\u4EBA\u660E\u786E\u58F0\u660E\u672C\u8F6E\u672A\u4EA4\u4ED8\uFF08not_delivered${work.agentHandoffAttemptId ? `\uFF0Cattempt ${work.agentHandoffAttemptId}` : ""}\uFF09\u2014\u2014\u7B49\u8FD9\u4E00\u7248\u91CD\u8DD1\u4EA4\u4ED8\uFF0C\u6216\u7531\u4EBA force-conclude \u7B7E\u5B57\u7A7F\u900F`,
7528
+ overridable: true
7529
+ };
7530
+ }
7531
+ if (work?.status !== "success" && work?.businessHandoffStatus !== "delivered") {
7532
+ return {
7533
+ code: "work-not-success",
7534
+ detail: `\u8FD9\u4E00\u7248\u4E0D\u662F\u6709\u6548\u4EA7\u51FA\uFF08work.status=${work?.status ?? "?"}\uFF09\u2014\u2014\u6539\u5BF9\u6700\u8FD1\u4E00\u7248\u6210\u529F\u7684\u4EA7\u51FA\u6536\u53E3\uFF0C\u6216\u7531\u4EBA force-conclude \u7B7E\u5B57\u7A7F\u900F`,
7535
+ overridable: true
7536
+ };
7537
+ }
7538
+ return null;
7493
7539
  }
7494
7540
  function requiresBusinessHandoff(state, nodeId, work) {
7495
- if (work?.replyToIssueId) return false;
7541
+ if (work && isReplyWork(work)) return false;
7496
7542
  if (!state.edgesFrom(nodeId).some((edge) => isBusinessDependencyEdge(state, edge))) return false;
7497
7543
  if (typeof work?.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) return false;
7498
7544
  return true;
@@ -7506,12 +7552,12 @@ function activate(state, nodeId, _ctx) {
7506
7552
  if (!node.assigneeActorId) return "no_assignee";
7507
7553
  if (state.openWorkOf(nodeId)) return "already_running";
7508
7554
  if (state.hasQueuedWorkFor(nodeId)) return "already_running";
7509
- if (state.worksOf(nodeId).some((w2) => w2.acceptanceState === "draft")) return "in_review";
7555
+ if (state.allWorksIncludingReplies(nodeId).some((w2) => w2.acceptanceState === "draft")) return "in_review";
7510
7556
  const unresolvedSelf = findUnresolvedSelfIssue(nodeId, state.allIssues());
7511
7557
  if (unresolvedSelf) return unresolvedSelf.kind === "escalation" ? "failed" : "waiting_answer";
7512
7558
  const inbound = state.edgesInto(nodeId);
7513
7559
  if (inbound.some((e) => e.required && e.kind === "data" && !e.pinnedWorkId && !isEdgeInputUnobtainable(state, e))) return "upstream_not_ready";
7514
- if (inbound.some((e) => state.worksOf(e.fromNodeId).some(isWorkOpen))) return "upstream_unstable";
7560
+ if (inbound.some((e) => state.allWorksIncludingReplies(e.fromNodeId).some(isWorkOpen))) return "upstream_unstable";
7515
7561
  return "dispatched";
7516
7562
  }
7517
7563
  function assessConvergence(state, ctx) {
@@ -7528,6 +7574,7 @@ var init_activate = __esm({
7528
7574
  "use strict";
7529
7575
  init_src2();
7530
7576
  init_model();
7577
+ init_model();
7531
7578
  init_views2();
7532
7579
  }
7533
7580
  });
@@ -7626,7 +7673,10 @@ CREATE TABLE IF NOT EXISTS ${schema}.works (
7626
7673
  outcome_detail jsonb,
7627
7674
  session_ref text,
7628
7675
  continues_work_id text,
7629
- -- \u56DE\u4FE1 work\uFF08\u8BBE\u8BA1-\u6700\u7EC8 \xA7\u56DE\u4FE1\u8F6E\uFF09\uFF1A\u975E\u7A7A = \u5BF9\u8BE5 comment issue \u7684\u56DE\u4FE1\u8F6E\uFF0C\u4E0D\u8FDB\u8282\u70B9\u4E3B\u94FE
7676
+ -- \u2605 work \u5C5E\u4E8E\u54EA\u6761\u9053\uFF08\u6B63\u5411\u3001\u663E\u5F0F\u4E8B\u5B9E\uFF09\uFF1A'main'=\u8282\u70B9\u4E3B\u94FE\u4EA7\u51FA / 'reply'=\u56DE\u4FE1\u8F6E\u3002\u53D6\u4EE3\u65E7\u7684\u5426\u5B9A\u5F0F\u5224\u636E
7677
+ -- "reply_to_issue_id IS NULL"\u3002\u5B58\u91CF\u884C\u6309 reply_to_issue_id \u56DE\u586B\uFF08\u89C1\u4E0B\u65B9\u8FC1\u79FB\u6BB5\uFF09\u3002
7678
+ lane text CHECK (lane IN ('main','reply')),
7679
+ -- \u56DE\u4FE1\u8F6E\u6307\u5411\u54EA\u6761 comment issue\uFF08**\u7EAF\u6570\u636E**\uFF1A\u975E\u7A7A\u4EC5\u8868\u793A\u300C\u7B54\u54EA\u6761\u4FE1\u300D\uFF0C\u662F\u4E0D\u662F\u56DE\u4FE1\u8F6E\u7531 lane \u5224\uFF09
7630
7680
  reply_to_issue_id text,
7631
7681
  output_version_no integer,
7632
7682
  conclusion text,
@@ -7807,8 +7857,15 @@ ALTER TABLE ${schema}.works DROP CONSTRAINT IF EXISTS works_business_handoff_sta
7807
7857
  ALTER TABLE ${schema}.works ADD CONSTRAINT works_business_handoff_status_check
7808
7858
  CHECK (business_handoff_status IS NULL OR business_handoff_status IN ('delivered','not_delivered'));
7809
7859
  ALTER TABLE ${schema}.reviews ADD COLUMN IF NOT EXISTS status text;
7810
- -- \u56DE\u4FE1 work\uFF08\u8BBE\u8BA1-\u6700\u7EC8 \xA7\u56DE\u4FE1\u8F6E\uFF09\uFF1A\u975E\u7A7A = \u56DE\u4FE1\u8F6E\uFF08\u951A\u6700\u65E9\u672A\u89E3 comment\uFF09\uFF0C\u4E0D\u8FDB\u8282\u70B9\u4E3B\u94FE
7860
+ -- \u56DE\u4FE1\u8F6E\u6307\u5411\u54EA\u6761 comment issue\uFF08\u7EAF\u6570\u636E\uFF09
7811
7861
  ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS reply_to_issue_id text;
7862
+ -- \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
7863
+ -- \u53EA\u586B\u5F53\u524D\u4E3A NULL \u7684\u884C \u2192 \u5E42\u7B49\uFF08\u91CD\u542F\u4E0D\u8986\u76D6 handler \u5DF2\u5199\u7684\u503C\uFF09\u3002\u8BFB\u9762\u53E6\u6709\u515C\u5E95\uFF08store-postgres \u8BFB\u6620\u5C04\uFF09\u3002
7864
+ ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS lane text;
7865
+ UPDATE ${schema}.works SET lane = CASE WHEN reply_to_issue_id IS NULL THEN 'main' ELSE 'reply' END
7866
+ WHERE lane IS NULL;
7867
+ ALTER TABLE ${schema}.works DROP CONSTRAINT IF EXISTS works_lane_check;
7868
+ ALTER TABLE ${schema}.works ADD CONSTRAINT works_lane_check CHECK (lane IS NULL OR lane IN ('main','reply'));
7812
7869
  -- \u2605 issue \u5B58\u6D3B\u5224\u636E\u552F\u4E00\u5316\uFF08ADR 0144\uFF09\uFF1Aresolution \u627F\u63A5\u65E7 state \u7684 closed\uFF08=wontfix\uFF09\uFF1B
7813
7870
  -- \u968F\u540E state / blocking \u5217\u5220\u9664\uFF08\u4F9D\u8D56 state \u7684 CHECK \u4E0E\u4E09\u4E2A\u65E7\u90E8\u5206\u7D22\u5F15\u968F\u5217\u81EA\u52A8\u5220\u9664\uFF09\u3002
7814
7871
  -- \u56DE\u586B\u5FC5\u987B\u5728\u5220\u5217\u524D\u3001\u4E14\u53EA\u5728 state \u5217\u8FD8\u5B58\u5728\u65F6\u6267\u884C\uFF08DO \u5757\u5224 information_schema\uFF0C\u91CD\u590D\u542F\u52A8\u5E42\u7B49\uFF09\u3002
@@ -13259,6 +13316,9 @@ var init_store_postgres = __esm({
13259
13316
  outcomeDetail: w2.outcome_detail,
13260
13317
  sessionRef: w2.session_ref,
13261
13318
  continuesWorkId: w2.continues_work_id,
13319
+ // ★ lane 兜底(存量行迁移前 / 迁移与回填之间的竞态窗):无值时按 reply_to_issue_id 派生,
13320
+ // 保证 TS 侧 `Work.lane` 恒非空。reply_to_issue_id 在读写映射层可直接读(护栏白名单)。
13321
+ lane: w2.lane ?? (w2.reply_to_issue_id == null ? "main" : "reply"),
13262
13322
  replyToIssueId: w2.reply_to_issue_id ?? null,
13263
13323
  outputVersionNo: w2.output_version_no,
13264
13324
  conclusion: w2.conclusion,
@@ -13470,8 +13530,8 @@ var init_store_postgres = __esm({
13470
13530
  (id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
13471
13531
  outcome_detail,session_ref,continues_work_id,reply_to_issue_id,output_version_no,
13472
13532
  node_version,conclusion,agent_handoff_attempt_id,business_handoff_status,business_handoff_recorded_at,
13473
- acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason)
13474
- 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)
13533
+ acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane)
13534
+ 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)
13475
13535
  ON CONFLICT (id) DO NOTHING`,
13476
13536
  [
13477
13537
  w2.id,
@@ -13501,7 +13561,8 @@ var init_store_postgres = __esm({
13501
13561
  w2.acceptedBy,
13502
13562
  w2.rejectedReason,
13503
13563
  w2.overrideBy,
13504
- w2.overrideReason
13564
+ w2.overrideReason,
13565
+ w2.lane
13505
13566
  ]
13506
13567
  );
13507
13568
  break;
@@ -13935,7 +13996,7 @@ function markNodeForRetry(state, nodeId, at) {
13935
13996
  (i) => i.kind === "comment" && i.resolvedAt === null && i.aboutNodeId === nodeId
13936
13997
  );
13937
13998
  if (hasUnresolvedComment) {
13938
- const replyWorks = state.worksOf(nodeId).filter((w2) => w2.replyToIssueId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
13999
+ const replyWorks = state.replyWorksOf(nodeId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
13939
14000
  const latest = replyWorks[replyWorks.length - 1];
13940
14001
  if (latest && latest.deadAt && workState(latest, hasOutput(latest, 0)) === "dead") {
13941
14002
  state.updateWork(latest.id, { retryAt: at, status: "retry" });
@@ -14236,7 +14297,7 @@ var init_workorder = __esm({
14236
14297
 
14237
14298
  // ../engine/src/handlers/work.ts
14238
14299
  function latestSuccessOf(state, nodeId) {
14239
- const successes = state.worksOf(nodeId).filter((w2) => !w2.replyToIssueId && w2.status === "success").sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
14300
+ const successes = state.mainWorksOf(nodeId).filter((w2) => w2.status === "success").sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
14240
14301
  return successes.length > 0 ? successes[successes.length - 1].id : null;
14241
14302
  }
14242
14303
  var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workTimeout;
@@ -14245,6 +14306,7 @@ var init_work = __esm({
14245
14306
  "use strict";
14246
14307
  init_views2();
14247
14308
  init_kill();
14309
+ init_model();
14248
14310
  workCreate = {
14249
14311
  name: "work/create",
14250
14312
  kind: "work.create",
@@ -14253,7 +14315,7 @@ var init_work = __esm({
14253
14315
  const node = state.node(e.nodeId);
14254
14316
  if (!node) return;
14255
14317
  if (e.replyToIssueId) {
14256
- if (state.worksOf(e.nodeId).some((w2) => !w2.endedAt && !w2.deadAt)) return;
14318
+ if (state.allWorksIncludingReplies(e.nodeId).some((w2) => !w2.endedAt && !w2.deadAt)) return;
14257
14319
  state.insertWork({
14258
14320
  id: e.workId,
14259
14321
  workorderId: e.workorderId,
@@ -14265,6 +14327,7 @@ var init_work = __esm({
14265
14327
  deadAt: null,
14266
14328
  cancelledAt: null,
14267
14329
  status: "running",
14330
+ lane: "reply",
14268
14331
  outcome: null,
14269
14332
  outcomeDetail: null,
14270
14333
  sessionRef: null,
@@ -14287,8 +14350,8 @@ var init_work = __esm({
14287
14350
  }
14288
14351
  supersedeLatestWork(state, e.nodeId, ctx.at);
14289
14352
  killNodeReviews(state, e.nodeId, ctx.at);
14290
- for (const w2 of state.worksOf(e.nodeId)) {
14291
- if (w2.replyToIssueId && !w2.endedAt && !w2.deadAt) killWorkRow(state, w2, ctx.at);
14353
+ for (const w2 of state.replyWorksOf(e.nodeId)) {
14354
+ if (!w2.endedAt && !w2.deadAt) killWorkRow(state, w2, ctx.at);
14292
14355
  }
14293
14356
  state.insertWork({
14294
14357
  id: e.workId,
@@ -14301,6 +14364,7 @@ var init_work = __esm({
14301
14364
  deadAt: null,
14302
14365
  cancelledAt: null,
14303
14366
  status: "running",
14367
+ lane: "main",
14304
14368
  outcome: null,
14305
14369
  outcomeDetail: null,
14306
14370
  sessionRef: null,
@@ -14386,7 +14450,7 @@ var init_work = __esm({
14386
14450
  apply(e, state, ctx) {
14387
14451
  const w2 = state.work(e.workId);
14388
14452
  if (!w2 || w2.endedAt || w2.deadAt) return;
14389
- if (w2.replyToIssueId) {
14453
+ if (isReplyWork(w2)) {
14390
14454
  const myReplies = state.allIssues().filter((i) => i.kind === "comment" && i.aboutNodeId === w2.nodeId).flatMap((i) => state.repliesOf(i.id)).filter((r) => r.authorActorId === w2.assigneeActorId && r.createdAt >= w2.createdAt).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
14391
14455
  const replied = myReplies.length > 0;
14392
14456
  const ok = e.outcome === "completed" && (replied || hasOutput(w2, 0));
@@ -14771,7 +14835,7 @@ var init_review = __esm({
14771
14835
  const block = acceptBlock(state, e.workId);
14772
14836
  if (block && !(block.overridable && e.override)) return;
14773
14837
  const w2 = state.work(e.workId);
14774
- if (w2.replyToIssueId) {
14838
+ if (isReplyWork(w2)) {
14775
14839
  state.updateWork(e.workId, {
14776
14840
  acceptanceState: "accepted",
14777
14841
  acceptedAt: ctx.at,
@@ -14827,7 +14891,7 @@ var init_review = __esm({
14827
14891
  ...w2.endedAt ? {} : { endedAt: ctx.at, outcome: "completed" }
14828
14892
  });
14829
14893
  if (w2.outputVersionNo === null) {
14830
- const versions = state.worksOf(w2.nodeId).map((x2) => x2.outputVersionNo ?? 0);
14894
+ const versions = state.allWorksIncludingReplies(w2.nodeId).map((x2) => x2.outputVersionNo ?? 0);
14831
14895
  state.updateWork(e.workId, { outputVersionNo: Math.max(0, ...versions) + 1 });
14832
14896
  }
14833
14897
  for (const r of state.openReviewsOf(e.workId)) {
@@ -15665,7 +15729,8 @@ function workToRevision(w2, workInputs, artifactFiles) {
15665
15729
  resolves: void 0
15666
15730
  };
15667
15731
  revision.rejectedReason = w2.rejectedReason;
15668
- revision.replyToIssueId = w2.replyToIssueId ?? null;
15732
+ revision.lane = w2.lane;
15733
+ revision.replyIssueId = replyIssueIdOf(w2);
15669
15734
  return revision;
15670
15735
  }
15671
15736
  function issueToAnnotation(issue2, replies, recipients) {
@@ -15780,7 +15845,7 @@ function workToConcludeAttempt(w2, requirements, reviews) {
15780
15845
  gate: buildGateFromRequirements(reqs)
15781
15846
  };
15782
15847
  }
15783
- if (w2.acceptanceState === "accepted") {
15848
+ if (w2.acceptanceState === "accepted" && w2.outputVersionNo !== null) {
15784
15849
  const reqs = requirements.filter((r) => r.nodeId === w2.nodeId);
15785
15850
  return {
15786
15851
  head: w2.id,
@@ -16861,7 +16926,7 @@ var init_kernel_bridge = __esm({
16861
16926
  await this.refreshTracked();
16862
16927
  const wid = this.wo(args.artifactId);
16863
16928
  let workId;
16864
- const openWorks = [...this.model.revisions.values()].filter((r) => r.artifactId === args.artifactId && r.state === "working" && !r.replyToIssueId).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
16929
+ const openWorks = [...this.model.revisions.values()].filter((r) => r.artifactId === args.artifactId && r.state === "working" && r.lane !== "reply").sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
16865
16930
  workId = openWorks[0]?.id;
16866
16931
  const artifact = this.model.artifacts.get(args.artifactId);
16867
16932
  if (!workId) {
@@ -130836,7 +130901,7 @@ function buildNodeTimeline(artifactId, snap, runIdByTarget) {
130836
130901
  const nodeWorkIds = /* @__PURE__ */ new Set();
130837
130902
  for (const w2 of snap.works) {
130838
130903
  if (w2.nodeId !== artifactId) continue;
130839
- if (w2.replyToIssueId) continue;
130904
+ if (isReplyWork(w2)) continue;
130840
130905
  nodeWorkIds.add(w2.id);
130841
130906
  const status = w2.status ?? (w2.retryAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
130842
130907
  items.push({
@@ -130904,6 +130969,7 @@ var TYPE_ORDER;
130904
130969
  var init_timeline = __esm({
130905
130970
  "../server/src/domains/collab/timeline.ts"() {
130906
130971
  "use strict";
130972
+ init_src3();
130907
130973
  TYPE_ORDER = { work: 0, review: 1, issue: 2 };
130908
130974
  }
130909
130975
  });
@@ -157272,6 +157338,8 @@ function executionEnvHash(p2) {
157272
157338
  workRoot: p2.workRoot,
157273
157339
  home: p2.homeHostDir,
157274
157340
  bin: p2.binHostPath,
157341
+ // 包根目录进哈希:它决定挂载形态(整包 vs 单文件),换了就得重建 persist 容器
157342
+ binPkg: p2.binPackageDir ?? null,
157275
157343
  extraRo: [...p2.extraRoBindDirs ?? []].sort()
157276
157344
  });
157277
157345
  return (0, import_node_crypto13.createHash)("sha256").update(material).digest("hex").slice(0, 16);
@@ -157312,9 +157380,9 @@ function commonArgs(p2, name) {
157312
157380
  // 不变量 1:workdir 根同路径挂入(rw)
157313
157381
  "-v",
157314
157382
  `${p2.workRoot}:${p2.workRoot}`,
157315
- // 不变量 4:agent CLI 只读挂入
157316
- "-v",
157317
- `${p2.binHostPath}:${binMount}:ro`,
157383
+ // 不变量 4:agent CLI 只读挂入。自包含二进制挂单文件到 /usr/local/bin/<name>;
157384
+ // 脚本型 CLI 改挂整包(同路径),执行走 binHostPath 原路径——见 WrapParams.binPackageDir。
157385
+ ...p2.binPackageDir ? ["-v", `${p2.binPackageDir}:${p2.binPackageDir}:ro`] : ["-v", `${p2.binHostPath}:${binMount}:ro`],
157318
157386
  // 不变量 5:HOME 持久
157319
157387
  "-v",
157320
157388
  `${p2.homeHostDir}:${CONTAINER_HOME}`
@@ -157338,7 +157406,7 @@ function envFlags(env) {
157338
157406
  }
157339
157407
  function wrapForContainer(p2) {
157340
157408
  const ee = p2.executionEnv;
157341
- const binInContainer = `/usr/local/bin/${p2.binName ?? path6.basename(p2.binHostPath)}`;
157409
+ const binInContainer = p2.binPackageDir ? p2.binHostPath : `/usr/local/bin/${p2.binName ?? path6.basename(p2.binHostPath)}`;
157342
157410
  const eeHash = executionEnvHash(p2);
157343
157411
  if (ee.persist && p2.job.workdirKey) {
157344
157412
  const name2 = persistContainerName(p2.job.workdirKey);
@@ -157437,6 +157505,20 @@ function resolveBinHostPath(bin) {
157437
157505
  }
157438
157506
  throw new Error(`executionEnv\uFF1A\u5728 PATH \u4E0A\u627E\u4E0D\u5230\u53EF\u6267\u884C\u7684 ${bin}\uFF0C\u65E0\u6CD5\u6302\u5165\u5BB9\u5668`);
157439
157507
  }
157508
+ function resolveBinPackageDir(binHostPath) {
157509
+ if (!/\.(js|mjs|cjs)$/i.test(binHostPath)) return null;
157510
+ let dir = path7.dirname(binHostPath);
157511
+ for (let i = 0; i < 8; i++) {
157512
+ if (fs7.existsSync(path7.join(dir, "package.json"))) {
157513
+ if (dir === "/" || dir === os3.tmpdir() || dir.split(path7.sep).length <= 2) return null;
157514
+ return dir;
157515
+ }
157516
+ const parent = path7.dirname(dir);
157517
+ if (parent === dir) break;
157518
+ dir = parent;
157519
+ }
157520
+ return null;
157521
+ }
157440
157522
  function containerHomeHostDir(workRoot) {
157441
157523
  const dir = path7.join(resolveWorkRoot(workRoot), "container-home");
157442
157524
  fs7.mkdirSync(dir, { recursive: true });
@@ -157525,10 +157607,13 @@ async function maybeContainerize(params) {
157525
157607
  return [];
157526
157608
  }
157527
157609
  }))];
157610
+ const binHostPath = resolveBinHostPath(params.bin);
157611
+ const binPackageDir = resolveBinPackageDir(binHostPath);
157528
157612
  const plan = wrapForContainer({
157529
- binHostPath: resolveBinHostPath(params.bin),
157613
+ binHostPath,
157530
157614
  binName: path7.basename(params.bin),
157531
157615
  // realpath 前的名字("claude")——argv[0] 语义要保住
157616
+ ...binPackageDir ? { binPackageDir } : {},
157532
157617
  args: params.args,
157533
157618
  cwd: params.cwd,
157534
157619
  env,
@@ -163799,14 +163884,36 @@ async function startOasisServer(opts) {
163799
163884
  ...currentCompanyId !== void 0 ? { companyId: currentCompanyId } : {}
163800
163885
  }
163801
163886
  );
163802
- if (tokenClaims?.dispatchId && opts.onDispatchCommandCompleted) {
163887
+ if (tokenClaims?.dispatchId && opts.onDispatchCommandCompleted && !READ_ONLY_COMMANDS.has(body.command)) {
163803
163888
  try {
163889
+ const revision = body.command === "propose" || body.command === "integrate" ? result?.data : void 0;
163890
+ const deliveredArtifactId = typeof revision?.artifactId === "string" ? revision.artifactId : tokenClaims.artifactId;
163891
+ const listArg = (key) => {
163892
+ const raw = body.args?.[key];
163893
+ if (!Array.isArray(raw)) return void 0;
163894
+ const items = raw.filter((x2) => typeof x2 === "string" && x2.trim() !== "");
163895
+ return items.length > 0 ? items : void 0;
163896
+ };
163897
+ const delivered = revision && typeof revision.id === "string" && typeof revision.reason === "string" && revision.reason.trim() && deliveredArtifactId !== void 0 ? {
163898
+ summary: revision.reason,
163899
+ refs: [{
163900
+ kind: "artifact",
163901
+ id: deliveredArtifactId,
163902
+ version: revision.id,
163903
+ ...engine.kernel.model.artifacts.get(deliveredArtifactId)?.title ? { label: engine.kernel.model.artifacts.get(deliveredArtifactId).title } : {}
163904
+ }],
163905
+ ...listArg("unresolvedIssues") ? { unresolvedIssues: listArg("unresolvedIssues") } : {},
163906
+ ...listArg("risks") ? { risks: listArg("risks") } : {},
163907
+ ...listArg("nextActions") ? { nextActions: listArg("nextActions") } : {},
163908
+ ...listArg("verificationGaps") ? { verificationGaps: listArg("verificationGaps") } : {}
163909
+ } : void 0;
163804
163910
  await opts.onDispatchCommandCompleted({
163805
163911
  dispatchId: tokenClaims.dispatchId,
163806
163912
  command: body.command,
163807
163913
  ...tokenClaims.artifactId !== void 0 ? { artifactId: tokenClaims.artifactId } : {},
163808
163914
  durableRefs: tokenClaims.artifactId !== void 0 ? [{ kind: "artifact", id: tokenClaims.artifactId, label: body.command }] : [],
163809
- at: (/* @__PURE__ */ new Date()).toISOString()
163915
+ at: (/* @__PURE__ */ new Date()).toISOString(),
163916
+ ...delivered ? { delivered } : {}
163810
163917
  });
163811
163918
  } catch (error2) {
163812
163919
  console.error(`[execution-continuity] important-output checkpoint failed for ${tokenClaims.dispatchId}:`, error2);
@@ -165650,7 +165757,7 @@ async function startOasisServer(opts) {
165650
165757
  await new Promise((resolve10) => httpServer.listen(opts.port ?? 0, "0.0.0.0", resolve10));
165651
165758
  const address = httpServer.address();
165652
165759
  if (address === null || typeof address === "string") throw new Error("listen failed");
165653
- const baseUrl = `http://0.0.0.0:${address.port}`;
165760
+ const baseUrl = `http://127.0.0.1:${address.port}`;
165654
165761
  if (channelWsSupervisor) void channelWsSupervisor.start();
165655
165762
  return {
165656
165763
  baseUrl,
@@ -165662,7 +165769,7 @@ async function startOasisServer(opts) {
165662
165769
  })
165663
165770
  };
165664
165771
  }
165665
- var http, githubAppPending, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, STREAMING_PATHS, WILL_VERBS, GOVERNANCE_COMMANDS, isAgent, isHuman;
165772
+ var http, githubAppPending, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, STREAMING_PATHS, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
165666
165773
  var init_server3 = __esm({
165667
165774
  "../server/src/server.ts"() {
165668
165775
  "use strict";
@@ -165702,6 +165809,21 @@ var init_server3 = __esm({
165702
165809
  STREAMING_PATHS = ["/api/events", "/api/chat", "/api/trajectory/events", "/api/intervention/wait"];
165703
165810
  WILL_VERBS = /* @__PURE__ */ new Set(["annotate", "reply", "resolve", "review", "merge", "escalate", "hold", "release"]);
165704
165811
  GOVERNANCE_COMMANDS = /* @__PURE__ */ new Set(["seal", "assign", "promote", "forceConclude", "unlink", "gc", "requestChange", "resolveEscalation"]);
165812
+ READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
165813
+ "ls",
165814
+ "show",
165815
+ "timeline",
165816
+ "content",
165817
+ "queue",
165818
+ "stale",
165819
+ "blocked",
165820
+ "graph",
165821
+ "log",
165822
+ "working",
165823
+ "proposed",
165824
+ "rejected",
165825
+ "superseded"
165826
+ ]);
165705
165827
  isAgent = (actor) => actor.startsWith("actor:agent:");
165706
165828
  isHuman = (actor) => actor.startsWith("actor:human:");
165707
165829
  }
@@ -166416,9 +166538,25 @@ function systemHandoffFor(run, checkpoint = null, effects = []) {
166416
166538
  createdAt: run.endedAt ?? run.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
166417
166539
  };
166418
166540
  }
166541
+ function mergeSystemHandoff(existing, reconstructed) {
166542
+ if (!existing) return reconstructed;
166543
+ const authored = (text) => text.trim() !== "" && !text.startsWith("[system_reconstructed]");
166544
+ const keepList = (current, next) => current.length > 0 ? structuredClone(current) : structuredClone(next);
166545
+ return {
166546
+ ...reconstructed,
166547
+ // outcome 描述这趟 Attempt 的命运(交稿后仍可能失败/超时)→ 以终态为准。
166548
+ summary: authored(existing.summary) ? existing.summary : reconstructed.summary,
166549
+ changedOutputs: keepList(existing.changedOutputs, reconstructed.changedOutputs),
166550
+ unresolvedIssues: keepList(existing.unresolvedIssues, reconstructed.unresolvedIssues),
166551
+ risks: keepList(existing.risks, reconstructed.risks),
166552
+ nextActions: keepList(existing.nextActions, reconstructed.nextActions),
166553
+ verificationGaps: keepList(existing.verificationGaps, reconstructed.verificationGaps),
166554
+ userHandoff: existing.userHandoff ?? reconstructed.userHandoff,
166555
+ createdAt: existing.createdAt
166556
+ };
166557
+ }
166419
166558
  function validateHandoffInput(input, status) {
166420
166559
  if (!input.summary.trim()) throw new Error("handoff summary is required");
166421
- if ([...input.summary].length > 400) throw new Error("handoff summary exceeds 400 characters");
166422
166560
  if (isTerminalStatus(status) && input.outcome !== handoffOutcomeOf(status)) {
166423
166561
  throw new Error(`handoff outcome ${input.outcome} does not match attempt status ${status}`);
166424
166562
  }
@@ -166516,7 +166654,7 @@ var init_memory_trace_store = __esm({
166516
166654
  const existingHandoff = this.handoffs.get(id);
166517
166655
  const checkpoint = (this.checkpoints.get(id) ?? []).filter((item) => item.trigger !== "attempt_terminal").at(-1) ?? null;
166518
166656
  const effects = [...this.externalEffects.values()].filter((effect) => effect.attemptId === id);
166519
- const handoff = existingHandoff?.producedBy === "agent" ? existingHandoff : systemHandoffFor(merged, checkpoint, effects);
166657
+ const handoff = existingHandoff?.producedBy === "agent" ? existingHandoff : mergeSystemHandoff(existingHandoff ?? null, systemHandoffFor(merged, checkpoint, effects));
166520
166658
  this.handoffs.set(id, handoff);
166521
166659
  merged.handoffId = handoff.handoffId;
166522
166660
  }
@@ -171891,21 +172029,38 @@ var init_sink = __esm({
171891
172029
  };
171892
172030
  }
171893
172031
  checkpointInput(st, previous, trigger, createdAt) {
172032
+ const durableRefs = structuredClone(st.checkpointState.durableRefs ?? previous?.durableRefs ?? []);
171894
172033
  return {
171895
172034
  attemptId: st.runId,
171896
172035
  pendingSteps: [...st.checkpointState.pendingSteps ?? previous?.pendingSteps ?? []],
171897
- durableRefs: structuredClone(st.checkpointState.durableRefs ?? previous?.durableRefs ?? []),
172036
+ durableRefs,
171898
172037
  completedStepIds: [...st.checkpointState.completedStepIds ?? previous?.completedStepIds ?? []],
171899
- recoverability: st.checkpointState.recoverability ?? previous?.recoverability ?? "none",
172038
+ // recoverability 只有 Agent 自己说得准,但「一条持久化产出都没有」与「已经落了东西」这两种局面
172039
+ // 系统分得清:有 durableRefs 就至少是 partial(下一趟能接着用这些产出),别一律记成 none——
172040
+ // 那会让恢复包看起来「什么都没留下」,与账本里的 refs 自相矛盾。
172041
+ recoverability: st.checkpointState.recoverability ?? previous?.recoverability ?? (durableRefs.length > 0 ? "partial" : "none"),
171900
172042
  trigger,
171901
172043
  createdAt
171902
172044
  };
171903
172045
  }
172046
+ /**
172047
+ * 两条 checkpoint 的内容是否一字不差(trigger/时间不算内容)——用于跳过无意义的重复写。
172048
+ *
172049
+ * refs 必须逐字段比,**不能 JSON.stringify 整个对象**:写进 PG 的 jsonb 读回来键序是重排过的
172050
+ * (`{kind,id,version,label}` 回来是 `{id,kind,label,version}`),按字符串比会把「完全一样」判成
172051
+ * 「变了」,去重直接失效——线上的表现就是每个工具结果照样灌一行。
172052
+ */
172053
+ sameCheckpointContent(input, previous) {
172054
+ if (!previous) return false;
172055
+ const refKeys = (refs) => refs.map((ref2) => `${ref2.kind}\0${ref2.id}\0${ref2.version ?? ""}\0${ref2.label ?? ""}`).join("");
172056
+ return JSON.stringify(input.pendingSteps) === JSON.stringify(previous.pendingSteps) && refKeys(input.durableRefs) === refKeys(previous.durableRefs) && JSON.stringify(input.completedStepIds) === JSON.stringify(previous.completedStepIds) && input.recoverability === previous.recoverability;
172057
+ }
171904
172058
  clearObservedCheckpointState(st) {
171905
172059
  st.checkpointState = {};
171906
172060
  }
171907
172061
  async appendSystemCheckpoint(st, trigger, createdAt, appendDurableRefs = []) {
171908
172062
  if (!this.canCheckpoint()) return;
172063
+ if (trigger === "tool_completed" && Object.keys(st.checkpointState).length === 0) return;
171909
172064
  const previous = await this.latestCheckpoint(st);
171910
172065
  const input = this.checkpointInput(st, previous, trigger, createdAt);
171911
172066
  if (appendDurableRefs.length > 0) {
@@ -171915,6 +172070,14 @@ var init_sink = __esm({
171915
172070
  }
171916
172071
  input.durableRefs = [...refs.values()];
171917
172072
  }
172073
+ if (input.durableRefs.length > 0 && st.checkpointState.recoverability === void 0 && previous?.recoverability === void 0) {
172074
+ input.recoverability = "partial";
172075
+ }
172076
+ const observedTrigger = trigger === "tool_completed" || trigger === "important_output";
172077
+ if (observedTrigger && this.sameCheckpointContent(input, previous)) {
172078
+ this.clearObservedCheckpointState(st);
172079
+ return;
172080
+ }
171918
172081
  await this.store.appendCheckpoint(input);
171919
172082
  this.clearObservedCheckpointState(st);
171920
172083
  this.scheduleCheckpointTimer(st);
@@ -171951,8 +172114,16 @@ var init_sink = __esm({
171951
172114
  if (!st || st.ending || !this.canCheckpoint()) return;
171952
172115
  this.enqueue(sessionId, async (current) => {
171953
172116
  if (signal.trigger === "important_output") {
171954
- const { durableRefs = [], ...state } = signal;
172117
+ const { durableRefs = [], completedStepIds = [], ...state } = signal;
171955
172118
  this.mergeCheckpointState(current, state);
172119
+ if (completedStepIds.length > 0) {
172120
+ const previous = await this.latestCheckpoint(current);
172121
+ const merged = /* @__PURE__ */ new Set([
172122
+ ...current.checkpointState.completedStepIds ?? previous?.completedStepIds ?? [],
172123
+ ...completedStepIds
172124
+ ]);
172125
+ current.checkpointState = { ...current.checkpointState, completedStepIds: [...merged] };
172126
+ }
171956
172127
  await this.appendSystemCheckpoint(
171957
172128
  current,
171958
172129
  signal.trigger,
@@ -180335,7 +180506,7 @@ function collectWaitingRows(snapshots2, me) {
180335
180506
  const latestReply = /* @__PURE__ */ new Map();
180336
180507
  for (const w2 of snap.works) {
180337
180508
  if (w2.assigneeActorId !== me || cancelled.has(w2.nodeId)) continue;
180338
- latestBy(w2.replyToIssueId ? latestReply : latestMain, w2);
180509
+ latestBy(isReplyWork(w2) ? latestReply : latestMain, w2);
180339
180510
  }
180340
180511
  for (const w2 of [...latestMain.values(), ...latestReply.values()]) {
180341
180512
  const st = workState(w2, hasOutput(w2, outputCountOf(w2.id)));
@@ -182982,7 +183153,9 @@ function renderCoordinatorView(view) {
182982
183153
  if (node.handoffProducer === "agent") {
182983
183154
  lines.push(`- Agent handoff: ${node.summary || "No summary"}`);
182984
183155
  } else if (node.handoffProducer === "system_reconstructed") {
182985
- lines.push(`- System reconstruction (not an Agent conclusion): ${node.summary || "No summary"}`);
183156
+ lines.push(
183157
+ node.summary.startsWith("[system_reconstructed]") ? `- System reconstruction (not an Agent conclusion): ${node.summary || "No summary"}` : `- Delivery note recorded at submit time (the executor's own wording, captured by the system): ${node.summary}`
183158
+ );
182986
183159
  }
182987
183160
  for (const output of node.changedOutputs) {
182988
183161
  lines.push(` - Durable output: ${output.kind}:${output.id}${output.version ? `@${output.version}` : ""}`);
@@ -183005,6 +183178,11 @@ function renderCoordinatorView(view) {
183005
183178
  return `${lines.join("\n")}
183006
183179
  `;
183007
183180
  }
183181
+ function shouldInjectResumePack(pack) {
183182
+ const status = pack?.previousAttempt?.status;
183183
+ if (!status) return false;
183184
+ return status !== "succeeded" && status !== "no-output";
183185
+ }
183008
183186
  function renderContinuityProtocol() {
183009
183187
  return `# Execution continuity protocol
183010
183188
 
@@ -183016,8 +183194,13 @@ authoritative systems. Record only durable facts and stable references here.
183016
183194
  Run \`oasis continuity-checkpoint --trigger plan_completed\` after planning and
183017
183195
  \`oasis continuity-checkpoint --trigger stage_completed\` after a key stage so the system knows
183018
183196
  what remains, stable completed-step ids, recoverability, and only references that another agent can open.
183019
- The system silently adds checkpoints after successful tool results, durable Oasis command writes,
183020
- changed long-task state, and immediately before terminal status; do not emit timer-only checkpoints.
183197
+ Your explicit checkpoints are the only source of pendingSteps, completedStepIds and recoverability.
183198
+ The system appends checkpoints on its own after each successful tool result, after a durable Oasis
183199
+ command succeeds (carrying a coarse reference to the artifact you wrote), after a captured connector
183200
+ external effect settles, and once immediately before terminal status \u2014 but every one of those merely
183201
+ re-persists what you already declared plus references it can see; none of them can infer what remains
183202
+ to be done. If you never checkpoint, a later attempt on this node has nothing to resume from. Do not
183203
+ emit timer-only checkpoints.
183021
183204
 
183022
183205
  ## External effects
183023
183206
 
@@ -183028,30 +183211,35 @@ succeeds, use \`oasis continuity-effect\` with a stable key that the same node w
183028
183211
 
183029
183212
  ## Handoff
183030
183213
 
183031
- Before terminal exit, run \`oasis continuity-handoff\` with a concise technical summary (maximum 400
183032
- characters), changed durable outputs, unresolved issues, risks, next actions, and verification gaps.
183033
- You must also provide \`--user-summary\` as short business-facing language for the people receiving
183034
- the work. Say what you completed, what result you formed, and what downstream work can use; if not
183035
- delivered, say only why and what must happen next. Use \`--user-next-step\` when downstream needs an
183036
- instruction. List only openable business results in \`--user-deliverables\`; keep technical refs in
183037
- \`--outputs\`, and never list conclude, gap, checkpoint, or op as a user deliverable. Do not use a
183038
- generic success/output-count template or mention attempts, checkpoints, runtimes, recovery,
183039
- infrastructure paths, or internal diagnostics there.
183040
- The system reconstructs an internal handoff if the process dies first, but reconstructed content is
183041
- never shown as a business handoff.
183214
+ Before terminal exit, run \`oasis continuity-handoff\` with a technical summary, changed durable
183215
+ outputs, unresolved issues, risks, next actions, and verification gaps. Submitting work already
183216
+ records the delivered revision and your \`--reason\` as this node's handoff summary, so the value you
183217
+ add here is what the system cannot observe: what is still unresolved, what is risky, what the next
183218
+ agent should do, and what you could not verify. You must also provide \`--user-summary\` as short
183219
+ business-facing language for the people receiving the work. Say what you completed, what result you
183220
+ formed, and what downstream work can use; if not delivered, say only why and what must happen next.
183221
+ Use \`--user-next-step\` when downstream needs an instruction. List only openable business results in
183222
+ \`--user-deliverables\`; keep technical refs in \`--outputs\`, and never list conclude, gap,
183223
+ checkpoint, or op as a user deliverable. Do not use a generic success/output-count template or
183224
+ mention attempts, checkpoints, runtimes, recovery, infrastructure paths, or internal diagnostics
183225
+ there. Only an explicit non-success outcome holds downstream work back; a missing handoff does not,
183226
+ so never claim non-delivery merely because you ran out of time to write one.
183042
183227
  `;
183043
183228
  }
183044
183229
  function renderBusinessHandoffProtocol() {
183045
183230
  return `# Business handoff protocol
183046
183231
 
183047
- Before terminal exit, run \`oasis continuity-handoff\`. The NodeHandoff and its userHandoff are a
183048
- required completion barrier for business downstream work. A successful handoff must use outcome
183049
- \`succeeded\`, include a non-empty \`--user-summary\` saying what was done, what result was formed,
183050
- and what downstream can use, and may have zero files. If the work cannot be delivered, submit a
183051
- non-success outcome with a user summary explaining why delivery failed and \`--user-next-step\`;
183052
- that records the handoff but does not release downstream work. List only openable business results
183053
- in \`--user-deliverables\`. Never use a generic success/output-count template or expose attempts,
183054
- checkpoints, runtimes, recovery, infrastructure paths, or internal diagnostics in userHandoff.
183232
+ Before terminal exit, run \`oasis continuity-handoff\`. Submitting work already records the delivered
183233
+ revision and your \`--reason\` as this node's handoff summary; this command adds what the system
183234
+ cannot observe \u2014 unresolved issues, risks, next actions, verification gaps \u2014 plus a business-facing
183235
+ \`--user-summary\`. A successful handoff must use outcome \`succeeded\`, include a non-empty
183236
+ \`--user-summary\` saying what was done, what result was formed, and what downstream can use, and may
183237
+ have zero files. If the work genuinely cannot be delivered, submit a non-success outcome with a user
183238
+ summary explaining why and \`--user-next-step\`: that is the one signal which holds downstream work
183239
+ back, so never use it just because you ran out of time to write a handoff. Omitting this command does
183240
+ not block downstream. List only openable business results in \`--user-deliverables\`. Never use a
183241
+ generic success/output-count template or expose attempts, checkpoints, runtimes, recovery,
183242
+ infrastructure paths, or internal diagnostics in userHandoff.
183055
183243
  `;
183056
183244
  }
183057
183245
  function emptyExternalEffectProjection() {
@@ -183225,7 +183413,6 @@ var init_service7 = __esm({
183225
183413
  async handoff(input) {
183226
183414
  if (!HANDOFF_OUTCOMES2.has(input.outcome)) throw new Error(`invalid handoff outcome: ${input.outcome}`);
183227
183415
  if (!input.summary.trim()) throw new Error("handoff summary is required");
183228
- if ([...input.summary].length > 400) throw new Error("handoff summary exceeds 400 characters");
183229
183416
  if (input.producedBy === "agent" && !input.userHandoff) {
183230
183417
  throw new Error("agent-authored handoff requires userHandoff");
183231
183418
  }
@@ -183288,7 +183475,7 @@ var init_service7 = __esm({
183288
183475
  }
183289
183476
  async recoveryView(attemptId, companyId) {
183290
183477
  const attempt = await this.store.getAttempt(attemptId);
183291
- if (!attempt?.workOrderId || !attempt.nodeId || attempt.continuityMode !== "resume") return null;
183478
+ if (!attempt?.workOrderId || !attempt.nodeId || !attempt.continuityMode || attempt.continuityMode === "off") return null;
183292
183479
  const recoverySnapshot = this.readRecoverySnapshot(attempt);
183293
183480
  if (!recoverySnapshot) return null;
183294
183481
  const graph = await this.resolveGraph(attempt.workOrderId, companyId);
@@ -183322,7 +183509,7 @@ var init_service7 = __esm({
183322
183509
  return candidate ? { workOrderId: candidate.workOrderId, nodeId: candidate.nodeId, attemptId: candidate.attemptId } : null;
183323
183510
  }
183324
183511
  readRecoverySnapshot(attempt) {
183325
- if (!attempt.nodeId || attempt.continuityMode !== "resume") return null;
183512
+ if (!attempt.nodeId || attempt.continuityMode === "off" || attempt.continuityMode === null) return null;
183326
183513
  const metadata = attempt.metadata;
183327
183514
  const manifest = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata["bundleManifest"] : null;
183328
183515
  const expectedHash = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest["execution/RESUME.md"] : null;
@@ -183479,6 +183666,10 @@ function renderRefs(refs) {
183479
183666
  (ref2) => `- ${ref2.kind}:${ref2.id}${ref2.version ? `@${ref2.version}` : ""}${ref2.label ? ` \u2014 ${ref2.label}` : ""}`
183480
183667
  );
183481
183668
  }
183669
+ function bounded(text) {
183670
+ const t = text.trim();
183671
+ return [...t].length <= INJECTED_TEXT_LIMIT ? t : `${[...t].slice(0, INJECTED_TEXT_LIMIT).join("")}\u2026\uFF08\u539F\u6587\u8FC7\u957F\uFF0C\u6B64\u5904\u622A\u65AD\uFF1B\u5B8C\u6574\u5185\u5BB9\u89C1\u8BE5\u4E0A\u6E38\u8282\u70B9\u7684\u4EA4\u4ED8\u8BF4\u660E\uFF09`;
183672
+ }
183482
183673
  function renderList(title, values) {
183483
183674
  return [
183484
183675
  `## ${title}`,
@@ -183504,8 +183695,9 @@ function renderUpstreamNodeHandoff(handoff, source) {
183504
183695
  `Created at: ${handoff.createdAt}`,
183505
183696
  "",
183506
183697
  "## Summary",
183507
- handoff.summary,
183698
+ bounded(handoff.summary),
183508
183699
  "",
183700
+ ...source.conclusion ? ["## Delivery note (upstream conclusion)", bounded(source.conclusion), ""] : [],
183509
183701
  "## Changed durable outputs",
183510
183702
  ...handoff.changedOutputs.length > 0 ? renderRefs(handoff.changedOutputs) : ["- None"],
183511
183703
  "",
@@ -183517,49 +183709,116 @@ function renderUpstreamNodeHandoff(handoff, source) {
183517
183709
  ""
183518
183710
  ].join("\n");
183519
183711
  }
183712
+ function renderDeliveredWorkHandoff(source) {
183713
+ const authored = [
183714
+ ...source.unresolvedIssues ?? [],
183715
+ ...source.risks ?? [],
183716
+ ...source.nextActions ?? [],
183717
+ ...source.verificationGaps ?? []
183718
+ ];
183719
+ return [
183720
+ "# Upstream NodeHandoff",
183721
+ "",
183722
+ `Upstream node: ${source.upstreamNodeId}${source.upstreamTitle ? `\uFF08${source.upstreamTitle}\uFF09` : ""}`,
183723
+ `Pinned work: ${source.pinnedWorkId}`,
183724
+ "Produced by: system\uFF08\u636E\u4E0A\u6E38\u4EA4\u4F5C\u4E1A\u4E8B\u5B9E\u751F\u6210\uFF0C\u975E Agent \u624B\u5199\u7684\u6280\u672F\u4EA4\u63A5\uFF09",
183725
+ "",
183726
+ "## Summary",
183727
+ source.conclusion && source.conclusion.trim() ? bounded(source.conclusion) : "\u4E0A\u6E38\u672A\u7559\u6536\u5C3E\u7ED3\u8BBA\uFF1B\u4EE5\u672C\u76EE\u5F55\u4E2D\u9489\u4F4F\u7684\u4EA4\u4ED8\u7269\u5185\u5BB9\u4E3A\u51C6\u3002",
183728
+ "",
183729
+ "## Changed durable outputs",
183730
+ ...source.changedOutputs.length > 0 ? renderRefs(source.changedOutputs) : ["- None"],
183731
+ "",
183732
+ ...renderList("Unresolved issues", [...source.unresolvedIssues ?? []]),
183733
+ ...renderList("Risks", [...source.risks ?? []]),
183734
+ ...renderList("Next actions", [...source.nextActions ?? []]),
183735
+ ...renderList("Verification gaps", [...source.verificationGaps ?? []]),
183736
+ authored.length > 0 ? "\u4EE5\u4E0A\u56DB\u8282\u662F\u4E0A\u6E38\u6267\u884C\u4EBA\u4EA4\u4F5C\u4E1A\u65F6\u81EA\u5DF1\u5199\u7684\u3002" : "\u4E0A\u6E38\u8FD9\u4E00\u8F6E\u6CA1\u5199\u672A\u51B3 / \u98CE\u9669 / \u4E0B\u4E00\u6B65 / \u6838\u9A8C\u7F3A\u53E3\u2014\u2014**\u7A7A\u4E0D\u7B49\u4E8E\u300C\u4E0A\u6E38\u786E\u8BA4\u65E0\u98CE\u9669\u300D**\uFF0C\u53EA\u7B49\u4E8E\u6CA1\u4EBA\u5199\u3002",
183737
+ "\u4EE5\u672C\u76EE\u5F55\u4E2D\u9489\u4F4F\u7684\u90A3\u4E00\u7248\u4EA4\u4ED8\u7269\u4E3A\u51C6\uFF1B\u6709\u7591\u95EE\u56DE\u4E0A\u6E38\u8282\u70B9\u63D0\u56DE\u538B\uFF0C\u4E0D\u8981\u51ED\u672C\u6587\u4EF6\u63A8\u65AD\u672A\u5199\u660E\u7684\u4E8B\u3002",
183738
+ ""
183739
+ ].join("\n");
183740
+ }
183520
183741
  function createUpstreamHandoffInputResolver(opts) {
183742
+ const warn = opts.warn ?? ((message) => console.warn(`[upstream-handoff] ${message}`));
183521
183743
  return async (input) => {
183522
- const snapshot = await opts.loadWorkorder(input.workOrderId);
183523
- if (!snapshot) throw new Error(`workorder not found while assembling upstream handoff: ${input.workOrderId}`);
183524
- const edge = snapshot.edges.find(
183525
- (candidate) => candidate.fromNodeId === input.upstreamNodeId && candidate.toNodeId === input.downstreamNodeId
183526
- );
183527
- const downstream = snapshot.nodes.find((node) => node.id === input.downstreamNodeId);
183528
- const businessEdge = edge?.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
183529
- if (!businessEdge) return null;
183530
- if (edge.pinnedWorkId !== input.pinnedWorkId) {
183531
- throw new Error(
183532
- `upstream handoff pin drift for ${input.upstreamNodeId}\u2192${input.downstreamNodeId}: bundle=${input.pinnedWorkId}, edge=${edge.pinnedWorkId ?? "none"}`
183744
+ try {
183745
+ const snapshot = await opts.loadWorkorder(input.workOrderId);
183746
+ if (!snapshot) {
183747
+ warn(`\u5DE5\u5355\u5FEB\u7167\u8BFB\u4E0D\u5230\uFF0C\u8DF3\u8FC7\u4E0A\u6E38\u4EA4\u63A5\u6CE8\u5165\uFF1A${input.workOrderId}`);
183748
+ return null;
183749
+ }
183750
+ const edge = snapshot.edges.find(
183751
+ (candidate) => candidate.fromNodeId === input.upstreamNodeId && candidate.toNodeId === input.downstreamNodeId
183533
183752
  );
183534
- }
183535
- const work = snapshot.works.find(
183536
- (candidate) => candidate.id === input.pinnedWorkId && candidate.nodeId === input.upstreamNodeId
183537
- );
183538
- if (!work) throw new Error(`pinned upstream Work not found: ${input.pinnedWorkId}`);
183539
- if (typeof work.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) {
183540
- return null;
183541
- }
183542
- if (work.status === "success" && work.agentHandoffAttemptId === null && work.businessHandoffStatus === null) {
183753
+ const downstream = snapshot.nodes.find((node) => node.id === input.downstreamNodeId);
183754
+ const businessEdge = edge?.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
183755
+ if (!businessEdge) return null;
183756
+ if (edge.pinnedWorkId !== input.pinnedWorkId) {
183757
+ warn(
183758
+ `pin \u6F02\u79FB\uFF0C\u8DF3\u8FC7\u6CE8\u5165 ${input.upstreamNodeId}\u2192${input.downstreamNodeId}\uFF1Abundle=${input.pinnedWorkId}\u3001edge=${edge.pinnedWorkId ?? "none"}`
183759
+ );
183760
+ return null;
183761
+ }
183762
+ const work = snapshot.works.find(
183763
+ (candidate) => candidate.id === input.pinnedWorkId && candidate.nodeId === input.upstreamNodeId
183764
+ );
183765
+ if (!work) {
183766
+ warn(`\u9489\u4F4F\u7684\u4E0A\u6E38 Work \u4E0D\u5728\u5FEB\u7167\u91CC\uFF0C\u8DF3\u8FC7\u6CE8\u5165\uFF1A${input.pinnedWorkId}`);
183767
+ return null;
183768
+ }
183769
+ if (typeof work.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) {
183770
+ return null;
183771
+ }
183772
+ const upstreamNode = snapshot.nodes.find((node) => node.id === input.upstreamNodeId);
183773
+ const conclusion = typeof work.conclusion === "string" ? work.conclusion : null;
183774
+ const changedOutputs = [{
183775
+ kind: "artifact",
183776
+ id: input.upstreamNodeId,
183777
+ version: input.pinnedWorkId,
183778
+ ...upstreamNode?.title ? { label: upstreamNode.title } : {}
183779
+ }];
183780
+ const deliveredRow = typeof work.sessionRef === "string" && work.sessionRef ? await opts.getHandoff(work.sessionRef).catch(() => null) : null;
183781
+ const systemHandoff = () => renderDeliveredWorkHandoff({
183782
+ upstreamNodeId: input.upstreamNodeId,
183783
+ upstreamTitle: upstreamNode?.title,
183784
+ pinnedWorkId: input.pinnedWorkId,
183785
+ conclusion,
183786
+ changedOutputs: (deliveredRow?.changedOutputs.length ?? 0) > 0 ? deliveredRow.changedOutputs : changedOutputs,
183787
+ ...deliveredRow?.unresolvedIssues.length ? { unresolvedIssues: deliveredRow.unresolvedIssues } : {},
183788
+ ...deliveredRow?.risks.length ? { risks: deliveredRow.risks } : {},
183789
+ ...deliveredRow?.nextActions.length ? { nextActions: deliveredRow.nextActions } : {},
183790
+ ...deliveredRow?.verificationGaps.length ? { verificationGaps: deliveredRow.verificationGaps } : {}
183791
+ });
183792
+ if (!work.agentHandoffAttemptId || work.businessHandoffStatus !== "delivered") {
183793
+ return systemHandoff();
183794
+ }
183795
+ const handoff = await opts.getHandoff(work.agentHandoffAttemptId).catch((error2) => {
183796
+ warn(`\u8BFB Agent NodeHandoff \u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u7CFB\u7EDF\u751F\u6210\u7248\uFF1A${error2 instanceof Error ? error2.message : String(error2)}`);
183797
+ return null;
183798
+ });
183799
+ if (!handoff || handoff.attemptId !== work.agentHandoffAttemptId || handoff.producedBy !== "agent" || handoff.outcome !== "succeeded" || handoff.userHandoff?.status !== "delivered") {
183800
+ warn(
183801
+ `Agent NodeHandoff \u4E0E\u6295\u5F71\u5BF9\u4E0D\u4E0A\uFF08work=${work.id}\u3001attempt=${work.agentHandoffAttemptId}\uFF09\uFF0C\u964D\u7EA7\u4E3A\u7CFB\u7EDF\u751F\u6210\u7248`
183802
+ );
183803
+ return systemHandoff();
183804
+ }
183805
+ return renderUpstreamNodeHandoff(handoff, {
183806
+ upstreamNodeId: input.upstreamNodeId,
183807
+ pinnedWorkId: input.pinnedWorkId,
183808
+ conclusion
183809
+ });
183810
+ } catch (error2) {
183811
+ warn(`\u4E0A\u6E38\u4EA4\u63A5\u89E3\u6790\u5F02\u5E38\uFF0C\u672C\u8F6E\u4E0D\u6CE8\u5165\uFF1A${error2 instanceof Error ? error2.message : String(error2)}`);
183543
183812
  return null;
183544
183813
  }
183545
- if (!work.agentHandoffAttemptId || work.businessHandoffStatus !== "delivered") {
183546
- throw new Error(`pinned upstream Work ${work.id} has no delivered Agent handoff projection`);
183547
- }
183548
- const handoff = await opts.getHandoff(work.agentHandoffAttemptId);
183549
- if (!handoff) throw new Error(`Agent NodeHandoff not found for Attempt ${work.agentHandoffAttemptId}`);
183550
- if (handoff.attemptId !== work.agentHandoffAttemptId || handoff.producedBy !== "agent" || handoff.outcome !== "succeeded" || handoff.userHandoff?.status !== "delivered") {
183551
- throw new Error(`invalid Agent NodeHandoff for pinned Work ${work.id}`);
183552
- }
183553
- return renderUpstreamNodeHandoff(handoff, {
183554
- upstreamNodeId: input.upstreamNodeId,
183555
- pinnedWorkId: input.pinnedWorkId
183556
- });
183557
183814
  };
183558
183815
  }
183816
+ var INJECTED_TEXT_LIMIT;
183559
183817
  var init_upstream_handoff_input = __esm({
183560
183818
  "../server/src/domains/execution-continuity/upstream-handoff-input.ts"() {
183561
183819
  "use strict";
183562
183820
  init_src3();
183821
+ INJECTED_TEXT_LIMIT = 4e3;
183563
183822
  }
183564
183823
  });
183565
183824
 
@@ -183580,6 +183839,51 @@ var init_node_conclusions = __esm({
183580
183839
  }
183581
183840
  });
183582
183841
 
183842
+ // ../server/src/domains/execution-continuity/delivered-handoff.ts
183843
+ function mergeRefs(primary, extra) {
183844
+ const out = [...primary];
183845
+ for (const ref2 of extra) {
183846
+ if (!out.some((item) => item.kind === ref2.kind && item.id === ref2.id && item.version === ref2.version)) {
183847
+ out.push(ref2);
183848
+ }
183849
+ }
183850
+ return out;
183851
+ }
183852
+ async function recordDeliveredOutputHandoff(deps, facts) {
183853
+ const warn = deps.warn ?? ((message) => console.warn(`[execution-continuity] ${message}`));
183854
+ if (!facts.summary.trim() || facts.refs.length === 0) return null;
183855
+ try {
183856
+ if (deps.getAttempt) {
183857
+ const attempt = await deps.getAttempt(facts.attemptId).catch(() => null);
183858
+ if (!attempt?.nodeId) return null;
183859
+ }
183860
+ const existing = await deps.getHandoff(facts.attemptId).catch(() => null);
183861
+ if (existing?.producedBy === "agent") return existing;
183862
+ return await deps.handoff({
183863
+ attemptId: facts.attemptId,
183864
+ producedBy: "system_reconstructed",
183865
+ outcome: "succeeded",
183866
+ summary: facts.summary,
183867
+ changedOutputs: mergeRefs(facts.refs, existing?.changedOutputs ?? []),
183868
+ // 本次带来的优先;没带就把已有的机器派生内容原样带回——本次只补「交了什么」,
183869
+ // 不该顺手抹掉终态触发器算出来的风险项。
183870
+ unresolvedIssues: [...facts.unresolvedIssues ?? existing?.unresolvedIssues ?? []],
183871
+ risks: [...facts.risks ?? existing?.risks ?? []],
183872
+ nextActions: [...facts.nextActions ?? existing?.nextActions ?? []],
183873
+ verificationGaps: [...facts.verificationGaps ?? existing?.verificationGaps ?? []],
183874
+ ...facts.at ? { createdAt: facts.at } : {}
183875
+ });
183876
+ } catch (error2) {
183877
+ warn(`\u4EA4\u4F5C\u4E1A\u4EA4\u63A5\u843D\u5E93\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u6B21\u4EA4\u7A3F\uFF09${facts.attemptId}: ${error2 instanceof Error ? error2.message : String(error2)}`);
183878
+ return null;
183879
+ }
183880
+ }
183881
+ var init_delivered_handoff = __esm({
183882
+ "../server/src/domains/execution-continuity/delivered-handoff.ts"() {
183883
+ "use strict";
183884
+ }
183885
+ });
183886
+
183583
183887
  // ../server/src/domains/execution-continuity/index.ts
183584
183888
  function createExecutionContinuityDomain(opts) {
183585
183889
  const service = new ExecutionContinuityService(opts);
@@ -183595,6 +183899,7 @@ var init_execution_continuity2 = __esm({
183595
183899
  init_handoff_work_bridge();
183596
183900
  init_upstream_handoff_input();
183597
183901
  init_node_conclusions();
183902
+ init_delivered_handoff();
183598
183903
  }
183599
183904
  });
183600
183905
 
@@ -187121,13 +187426,24 @@ ${ctx.nodeFault}
187121
187426
  }
187122
187427
  }
187123
187428
  const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto38.randomUUID)();
187429
+ const taskWithContext = coordinatorContext ? [
187430
+ task,
187431
+ ``,
187432
+ `## \u5168\u5C40\u6267\u884C\u72B6\u6001\uFF08\u5148\u8BFB \`execution/COORDINATOR_VIEW.md\`\uFF09`,
187433
+ `\u90A3\u4EFD\u6587\u4EF6\u662F\u672C\u5DE5\u5355**\u6240\u6709\u8282\u70B9**\u7684\u4E00\u6B21\u6027\u5FEB\u7167\uFF1A\u5404\u8282\u70B9\u72B6\u6001\u3001\u5361\u5728\u8C01\u8EAB\u4E0A\u3001\u5DF2\u4EA4\u4ED8\u7684\u8282\u70B9\u4EA4\u4E86\u4EC0\u4E48`,
187434
+ `\uFF08Delivery note = \u6267\u884C\u4EBA\u4EA4\u4F5C\u4E1A\u65F6\u5199\u4E0B\u7684\u539F\u8BDD\uFF09\u3001\u52A8\u4E86\u54EA\u4E9B\u4EA7\u7269\u4E0E\u7248\u672C\u3001\u4EE5\u53CA\u672A\u51B3\u9879 / \u98CE\u9669 / \u6838\u9A8C\u7F3A\u53E3\u3002`,
187435
+ `\u5224\u300C\u771F\u6B7B\u9501\u8FD8\u662F\u5065\u5EB7\u7B49\u5F85\u300D\u4E4B\u524D\u5148\u770B\u5B83\u2014\u2014\u53EA\u770B\u672C\u8282\u70B9\u90BB\u57DF\u5BB9\u6613\u628A\u300C\u4E0A\u6E38\u6B63\u5E38\u5728\u4EA7\u300D\u8BEF\u5224\u6210\u5361\u4F4F\u3002`,
187436
+ `\u6CE8\u610F\u533A\u5206\u4E24\u79CD\u6458\u8981\uFF1A**Delivery note** \u662F\u6267\u884C\u4EBA\u81EA\u5DF1\u7684\u8BDD\uFF0C\u53EF\u4FE1\uFF1B**System reconstruction** \u662F\u8FDB\u7A0B`,
187437
+ `\u7EC8\u6001\u62FC\u51FA\u6765\u7684\u673A\u5668\u4E32\uFF0C\u53EA\u8BF4\u660E\u4F1A\u8BDD\u600E\u4E48\u7ED3\u675F\u7684\uFF0C\u4E0D\u4EE3\u8868\u4E1A\u52A1\u7ED3\u8BBA\u3002`,
187438
+ ``
187439
+ ].join("\n") : task;
187124
187440
  const job = {
187125
187441
  actor: actorId,
187126
187442
  actorToken: token,
187127
187443
  artifactId,
187128
187444
  bundle: {
187129
187445
  files: {
187130
- "TASK.md": task,
187446
+ "TASK.md": taskWithContext,
187131
187447
  ...coordinatorContext ? { "execution/COORDINATOR_VIEW.md": coordinatorContext } : {}
187132
187448
  }
187133
187449
  },
@@ -190284,7 +190600,7 @@ var init_postgres_trace = __esm({
190284
190600
  part text,
190285
190601
  produced_by text NOT NULL CHECK (produced_by IN ('agent','system_reconstructed')),
190286
190602
  outcome text NOT NULL CHECK (outcome IN ('succeeded','no-output','failed','cancelled','timeout','orphaned')),
190287
- summary text NOT NULL CHECK (char_length(summary) <= 400),
190603
+ summary text NOT NULL,
190288
190604
  changed_outputs jsonb NOT NULL DEFAULT '[]'::jsonb,
190289
190605
  unresolved_issues jsonb NOT NULL DEFAULT '[]'::jsonb,
190290
190606
  risks jsonb NOT NULL DEFAULT '[]'::jsonb,
@@ -190296,6 +190612,7 @@ var init_postgres_trace = __esm({
190296
190612
  await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS job_key text NOT NULL DEFAULT ''`);
190297
190613
  await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS part text`);
190298
190614
  await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS user_handoff jsonb`);
190615
+ await pool.query(`ALTER TABLE "${s2}".node_handoffs DROP CONSTRAINT IF EXISTS node_handoffs_summary_check`);
190299
190616
  await pool.query(`
190300
190617
  UPDATE "${s2}".node_handoffs h
190301
190618
  SET job_key=COALESCE(NULLIF(h.job_key, ''), r.job_key, 'attempt:' || h.attempt_id), part=r.part
@@ -190423,6 +190740,15 @@ var init_postgres_trace = __esm({
190423
190740
  reconstructed_summary, NEW.output_refs, '[]'::jsonb, reconstructed_risks,
190424
190741
  COALESCE(latest_pending, '[]'::jsonb), reconstructed_gaps, NULL,
190425
190742
  COALESCE(NEW.ended_at, NEW.updated_at, NEW.started_at))
190743
+ -- \u2605 2026-08-17\uFF1A\u7EC8\u6001\u91CD\u5EFA\u4ECE\u300C\u6574\u884C\u8986\u76D6\u300D\u6539\u6210\u300C**\u53EA\u586B\u7A7A**\u300D\u3002
190744
+ --
190745
+ -- \u75C5\u6839\uFF1A\u8FD9\u91CC\u539F\u672C\u5BF9\u4EFB\u4F55 produced_by <> 'agent' \u7684\u884C\u4E00\u5F8B\u7528 EXCLUDED \u8986\u76D6\u3002\u4EA4\u4F5C\u4E1A\u90A3\u4E00\u523B\u7531\u670D\u52A1\u7AEF
190746
+ -- \u843D\u7684\u771F\u5B9E\u6458\u8981\uFF08work.conclusion\uFF09\u4E0E\u4EA4\u4ED8\u7248\u672C\uFF0C\u4F1A\u5728\u4F1A\u8BDD\u7EC8\u6001\u88AB\u6362\u6210
190747
+ -- "[system_reconstructed] attempt=\u2026 reason=\u2026" \u8FD9\u4E32\u673A\u5668\u8BDD\u2014\u2014\u5199\u4E86\u7B49\u4E8E\u6CA1\u5199\u3002
190748
+ --
190749
+ -- \u65B0\u89C4\u5219\uFF1AAgent \u4EB2\u624B\u5199\u7684\u6700\u9AD8\u4F18\u5148\uFF08\u539F\u6837\u4FDD\u7559\uFF09\uFF1B\u7CFB\u7EDF\u884C\u53EA\u5728**\u8BE5\u5B57\u6BB5\u8FD8\u662F\u7A7A\u7684**\u65F6\u5019\u624D\u586B\uFF1B\u673A\u5668\u4E32
190750
+ -- \uFF08\u4EE5 [system_reconstructed] \u5F00\u5934\uFF09\u89C6\u540C\u7A7A\uFF0C\u53EF\u88AB\u771F\u5B9E\u6458\u8981\u66FF\u6362\u3001\u4E5F\u53EF\u88AB\u65B0\u7684\u673A\u5668\u4E32\u5237\u65B0\u3002
190751
+ -- outcome \u4F8B\u5916\uFF1A\u5B83\u63CF\u8FF0\u7684\u662F\u8FD9\u8D9F Attempt \u7684\u547D\u8FD0\uFF08\u53EF\u80FD\u5728\u4EA4\u7A3F\u540E\u624D\u5931\u8D25/\u8D85\u65F6\uFF09\uFF0C\u4EE5\u7EC8\u6001\u4E3A\u51C6\u3002
190426
190752
  ON CONFLICT (attempt_id) DO UPDATE SET
190427
190753
  produced_by=CASE
190428
190754
  WHEN current_handoff.produced_by = 'agent'
@@ -190434,29 +190760,46 @@ var init_postgres_trace = __esm({
190434
190760
  summary=CASE
190435
190761
  WHEN current_handoff.produced_by = 'agent'
190436
190762
  THEN current_handoff.summary
190763
+ WHEN COALESCE(current_handoff.summary, '') <> ''
190764
+ AND current_handoff.summary NOT LIKE '[system_reconstructed]%'
190765
+ THEN current_handoff.summary
190437
190766
  ELSE EXCLUDED.summary
190438
190767
  END,
190439
190768
  changed_outputs=CASE
190440
190769
  WHEN current_handoff.produced_by = 'agent'
190441
190770
  THEN current_handoff.changed_outputs
190771
+ WHEN jsonb_array_length(COALESCE(current_handoff.changed_outputs, '[]'::jsonb)) > 0
190772
+ THEN current_handoff.changed_outputs
190442
190773
  ELSE EXCLUDED.changed_outputs
190443
190774
  END,
190444
190775
  unresolved_issues=CASE
190445
190776
  WHEN current_handoff.produced_by = 'agent'
190446
- THEN current_handoff.unresolved_issues ELSE EXCLUDED.unresolved_issues END,
190777
+ THEN current_handoff.unresolved_issues
190778
+ WHEN jsonb_array_length(COALESCE(current_handoff.unresolved_issues, '[]'::jsonb)) > 0
190779
+ THEN current_handoff.unresolved_issues
190780
+ ELSE EXCLUDED.unresolved_issues END,
190447
190781
  risks=CASE
190448
190782
  WHEN current_handoff.produced_by = 'agent'
190449
- THEN current_handoff.risks ELSE EXCLUDED.risks END,
190783
+ THEN current_handoff.risks
190784
+ WHEN jsonb_array_length(COALESCE(current_handoff.risks, '[]'::jsonb)) > 0
190785
+ THEN current_handoff.risks
190786
+ ELSE EXCLUDED.risks END,
190450
190787
  next_actions=CASE
190451
190788
  WHEN current_handoff.produced_by = 'agent'
190452
- THEN current_handoff.next_actions ELSE EXCLUDED.next_actions END,
190789
+ THEN current_handoff.next_actions
190790
+ WHEN jsonb_array_length(COALESCE(current_handoff.next_actions, '[]'::jsonb)) > 0
190791
+ THEN current_handoff.next_actions
190792
+ ELSE EXCLUDED.next_actions END,
190453
190793
  verification_gaps=CASE
190454
190794
  WHEN current_handoff.produced_by = 'agent'
190455
- THEN current_handoff.verification_gaps ELSE EXCLUDED.verification_gaps END,
190795
+ THEN current_handoff.verification_gaps
190796
+ WHEN jsonb_array_length(COALESCE(current_handoff.verification_gaps, '[]'::jsonb)) > 0
190797
+ THEN current_handoff.verification_gaps
190798
+ ELSE EXCLUDED.verification_gaps END,
190456
190799
  user_handoff=CASE
190457
190800
  WHEN current_handoff.produced_by = 'agent'
190458
190801
  THEN current_handoff.user_handoff
190459
- ELSE EXCLUDED.user_handoff
190802
+ ELSE COALESCE(current_handoff.user_handoff, EXCLUDED.user_handoff)
190460
190803
  END;
190461
190804
  NEW.handoff_id := COALESCE(NEW.handoff_id, generated_handoff_id);
190462
190805
  RETURN NEW;
@@ -191259,7 +191602,6 @@ var init_postgres_trace = __esm({
191259
191602
  }
191260
191603
  async putHandoff(input) {
191261
191604
  if (!input.summary.trim()) throw new Error("handoff summary is required");
191262
- if ([...input.summary].length > 400) throw new Error("handoff summary exceeds 400 characters");
191263
191605
  const client = await this.pool.connect();
191264
191606
  try {
191265
191607
  await client.query("BEGIN");
@@ -194218,11 +194560,11 @@ async function startServe(opts) {
194218
194560
  fs28.writeFile(serverHeartbeatFile, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), pid: process.pid }) + "\n", () => {
194219
194561
  });
194220
194562
  };
194563
+ const lock = path22.join(opts.dir, "serve.lock");
194564
+ acquireServeLock(lock);
194221
194565
  writeServerHeartbeat();
194222
194566
  const serverHeartbeatTimer = setInterval(writeServerHeartbeat, 5e3);
194223
194567
  serverHeartbeatTimer.unref?.();
194224
- const lock = path22.join(opts.dir, "serve.lock");
194225
- acquireServeLock(lock);
194226
194568
  let activeTimeoutPolicy;
194227
194569
  try {
194228
194570
  activeTimeoutPolicy = loadPolicyFromFile(resolvePolicyFilePath());
@@ -194583,7 +194925,7 @@ async function startServe(opts) {
194583
194925
  const trajReader = new FsTrajectorySink(opts.dir);
194584
194926
  const trace = createTraceDomain({ store: traceStore, readBundleFile: (id, rel) => trajReader.readBundleFile(id, rel) });
194585
194927
  const traceHealth = createTraceHealth();
194586
- const continuityModeRaw = process.env["OASIS_EXECUTION_CONTINUITY_MODE"]?.trim() || "off";
194928
+ const continuityModeRaw = process.env["OASIS_EXECUTION_CONTINUITY_MODE"]?.trim() || "record";
194587
194929
  if (!(continuityModeRaw === "off" || continuityModeRaw === "record" || continuityModeRaw === "resume")) {
194588
194930
  throw new Error(`OASIS_EXECUTION_CONTINUITY_MODE must be off|record|resume, received ${continuityModeRaw}`);
194589
194931
  }
@@ -194632,7 +194974,9 @@ async function startServe(opts) {
194632
194974
  return snap ? nodeConclusionsFromSnapshot(snap) : {};
194633
194975
  }
194634
194976
  });
194635
- console.log(`[serve] execution-continuity mode=${continuityMode}\uFF08record=\u5BF9\u7167\u7EC4\uFF0Cresume=\u6062\u590D\u5305\u5B9E\u9A8C\u7EC4\uFF09`);
194977
+ console.log(
194978
+ `[serve] execution-continuity mode=${continuityMode}\uFF08off=\u5168\u5173\uFF1Brecord/resume \u884C\u4E3A\u4E00\u81F4\uFF1A\u8BB0 checkpoint + \u4E2D\u65AD\u65F6\u6CE8\u6062\u590D\u5305\u3002A/B \u5B9E\u9A8C\u5DF2\u7ED3\u675F\uFF0Cresume \u4EC5\u4E3A\u517C\u5BB9\u4FDD\u7559\uFF09${process.env["OASIS_EXTERNAL_EFFECT_CAPTURE"] === "1" ? "\uFF1B\u5916\u90E8\u526F\u4F5C\u7528\u62E6\u622A\uFF1A\u5F00" : ""}`
194979
+ );
194636
194980
  const automationStore = pgPool ? await PostgresAutomationStore.open(pgPool, pgSchema) : new MemoryAutomationStore();
194637
194981
  console.log(`[serve] \u81EA\u52A8\u5316\u4F53\u7CFB\uFF1A${pgPool ? `Postgres schema=${pgSchema}` : "\u5185\u5B58 dev store"}`);
194638
194982
  const automations = createAutomationsDomain({ store: automationStore, kernel });
@@ -195382,9 +195726,36 @@ async function startServe(opts) {
195382
195726
  ...await materializeSkillFiles({ service: actors.service, actorId, runtimeKind })
195383
195727
  }),
195384
195728
  uploadChatAttachment: async ({ contentBase64 }) => ({ blobRef: await assets.put(new Uint8Array(Buffer.from(contentBase64, "base64"))) }),
195385
- onDispatchCommandCompleted: async ({ dispatchId, durableRefs, at }) => {
195386
- traceStoreSink.checkpoint(dispatchId, { trigger: "important_output", durableRefs, at });
195729
+ onDispatchCommandCompleted: async ({ dispatchId, command, durableRefs, at, delivered }) => {
195730
+ traceStoreSink.checkpoint(dispatchId, {
195731
+ trigger: "important_output",
195732
+ // 交作业的那条命令带着版本号(`wk:<节点>:<n>`),比笼统的「动过这个产物」有用得多——
195733
+ // 下一趟接手时据它知道上一趟已经交到哪一版。其余持久化命令仍记粗粒度引用。
195734
+ durableRefs: delivered?.refs ?? durableRefs,
195735
+ // 稳定步骤 id = 命令名:同一步骤重跑时 id 一致(checkpoint 契约对 stable id 的要求),
195736
+ // 恢复时下一趟能看出「上一趟已经 propose 过 / 已经 gap 过」。
195737
+ completedStepIds: [command],
195738
+ at
195739
+ });
195387
195740
  await traceStoreSink.flush(dispatchId);
195741
+ if (!delivered) return;
195742
+ await recordDeliveredOutputHandoff(
195743
+ {
195744
+ getAttempt: (attemptId) => traceStore.getAttempt(attemptId),
195745
+ getHandoff: (attemptId) => traceStore.getHandoff(attemptId),
195746
+ handoff: (input) => executionContinuity.service.handoff(input)
195747
+ },
195748
+ {
195749
+ attemptId: dispatchId,
195750
+ summary: delivered.summary,
195751
+ refs: delivered.refs,
195752
+ ...delivered.unresolvedIssues ? { unresolvedIssues: delivered.unresolvedIssues } : {},
195753
+ ...delivered.risks ? { risks: delivered.risks } : {},
195754
+ ...delivered.nextActions ? { nextActions: delivered.nextActions } : {},
195755
+ ...delivered.verificationGaps ? { verificationGaps: delivered.verificationGaps } : {},
195756
+ at
195757
+ }
195758
+ );
195388
195759
  },
195389
195760
  dispatchProduce: async ({ artifactId, actorId, part, companyId }) => {
195390
195761
  const d = dispatchers.get(companyId ?? defaultCompanyId);
@@ -196128,7 +196499,7 @@ async function startServe(opts) {
196128
196499
  const rev = kernelModel.revisions.get(workId);
196129
196500
  if (!rev) return;
196130
196501
  if (rev.state === "merged") return;
196131
- const replyIssueId = rev.replyToIssueId ?? null;
196502
+ const replyIssueId = rev.replyIssueId ?? null;
196132
196503
  console.log(`[new-engine] dispatchWork ${workId} \u2192 ${rev.artifactId} / ${rev.author}${replyIssueId ? `\uFF08\u56DE\u4FE1 ${replyIssueId}\uFF09` : ""}`);
196133
196504
  const art = kernelModel.artifacts.get(rev.artifactId);
196134
196505
  const wid = art?.workspace ?? "";
@@ -196432,7 +196803,7 @@ async function startServe(opts) {
196432
196803
  artifactId ? await resolveDispatchScope(artifactId) : void 0,
196433
196804
  (key) => emitBrokerRefused(actorId, artifactId, key)
196434
196805
  );
196435
- return continuityMode === "off" ? provision : { ...provision, env: { ...provision.env, OASIS_EXTERNAL_EFFECT_CAPTURE: "1" } };
196806
+ return process.env["OASIS_EXTERNAL_EFFECT_CAPTURE"] === "1" ? { ...provision, env: { ...provision.env, OASIS_EXTERNAL_EFFECT_CAPTURE: "1" } } : provision;
196436
196807
  },
196437
196808
  resolveActorContext: async (actorId) => buildActorContext((await runActors()).service, actorId),
196438
196809
  // 收尾评审的角色分工段(reviewer 此前彼此不知道对方存在 → 越界 / 重复劳动)。
@@ -196486,13 +196857,15 @@ async function startServe(opts) {
196486
196857
  getHandoff: (attemptId) => traceStore.getHandoff(attemptId)
196487
196858
  }),
196488
196859
  executionContinuityInstructions: continuityMode === "off" ? renderBusinessHandoffProtocol() : renderContinuityProtocol(),
196489
- ...continuityMode === "resume" ? {
196860
+ // 恢复包注入(2026-08-17):从「只有 resume 实验组才给」改成「只要账本开着就给」——
196861
+ // A/B 实验早已结束,record 与 resume 现在行为一致(resume 值保留只为兼容既有部署变量)。
196862
+ ...continuityMode !== "off" ? {
196490
196863
  resolveExecutionResumePack: async (artifactId, workOrderId, scope) => {
196491
196864
  const pack = await executionContinuity.service.resumePack(workOrderId, artifactId, companyId, {
196492
196865
  jobKey: scope.jobKey,
196493
196866
  part: scope.part ?? null
196494
196867
  });
196495
- return pack ? renderResumePack(pack) : null;
196868
+ return shouldInjectResumePack(pack) ? renderResumePack(pack) : null;
196496
196869
  }
196497
196870
  } : {},
196498
196871
  schema: liveSchemaMap,
@@ -198612,9 +198985,16 @@ var COMMAND_DECLS = {
198612
198985
  { name: "rebase-of", desc: "\u628A\u4E00\u6761 queued revision \u6539 base \u540E\u91CD\u6302" },
198613
198986
  { name: "checkpoint", desc: "\u65AD\u70B9\u7EED\u4F20\u6807\u8BB0" },
198614
198987
  { name: "continue", desc: "\u7EE7\u7EED\u4E0A\u6B21\u65AD\u70B9" },
198615
- { name: "resolves", desc: "\u672C\u7248\u663E\u5F0F\u7B54\u590D\u7684\u4FE1\uFF08\u9017\u53F7\u5206\u9694 annotationId\uFF09\u3002\u5E38\u89C4\u4E0D\u7528\u586B\u2014\u2014\u7CFB\u7EDF\u6309\u6D3E\u53D1\u70B9\u540D\u96C6\u81EA\u52A8\u76D6\u7AE0\uFF1B\u53EA\u5728\u4E2D\u9014\u7ECF status \u53D1\u73B0\u7684\u4FE1\u7B49\u7279\u4F8B\u7528" }
198988
+ { name: "resolves", desc: "\u672C\u7248\u663E\u5F0F\u7B54\u590D\u7684\u4FE1\uFF08\u9017\u53F7\u5206\u9694 annotationId\uFF09\u3002\u5E38\u89C4\u4E0D\u7528\u586B\u2014\u2014\u7CFB\u7EDF\u6309\u6D3E\u53D1\u70B9\u540D\u96C6\u81EA\u52A8\u76D6\u7AE0\uFF1B\u53EA\u5728\u4E2D\u9014\u7ECF status \u53D1\u73B0\u7684\u4FE1\u7B49\u7279\u4F8B\u7528" },
198989
+ { name: "unresolved", desc: "\u7559\u7ED9\u4E0B\u6E38\u7684\u672A\u51B3\u4E8B\u9879\uFF08\u5206\u53F7\u5206\u9694\uFF09\u3002\u5199\u8FDB\u672C\u8282\u70B9\u7684\u6280\u672F\u4EA4\u63A5\uFF0C\u4E0B\u6E38\u5F00\u5DE5\u524D\u4F1A\u8BFB\u5230" },
198990
+ { name: "risks", desc: "\u4E0B\u6E38\u8BE5\u77E5\u9053\u7684\u98CE\u9669\uFF08\u5206\u53F7\u5206\u9694\uFF09" },
198991
+ { name: "next", desc: "\u5EFA\u8BAE\u4E0B\u6E38\u63A5\u7740\u505A\u4EC0\u4E48\uFF08\u5206\u53F7\u5206\u9694\uFF09" },
198992
+ { name: "gaps", desc: "\u4F60\u6CA1\u80FD\u6838\u9A8C\u7684\u90E8\u5206\uFF08\u5206\u53F7\u5206\u9694\uFF09\u2014\u2014\u522B\u8BA9\u4E0B\u6E38\u4EE5\u4E3A\u5DF2\u9A8C\u8FC7" }
198616
198993
  ],
198617
- examples: ['oasis propose artifact:dev:abc-123 --from-dir deliverable/ --reason "\u4FEE\u590D\u767B\u5F55\u9875\u767D\u5C4F"']
198994
+ examples: [
198995
+ 'oasis propose artifact:dev:abc-123 --from-dir deliverable/ --reason "\u4FEE\u590D\u767B\u5F55\u9875\u767D\u5C4F"',
198996
+ 'oasis propose artifact:dev:abc-123 --reason "\u63A5\u53E3\u8054\u8C03\u5B8C\u6210" --unresolved "\u7EBF\u4E0A\u914D\u989D\u6CA1\u786E\u8BA4" --gaps "\u6CA1\u8DD1\u538B\u6D4B"'
198997
+ ]
198618
198998
  },
198619
198999
  queue: {
198620
199000
  group: "\u7248\u672C\u673A\uFF08\xA75\uFF09",
@@ -199896,6 +200276,11 @@ function needPos(positional, i, usage) {
199896
200276
  if (v2 === void 0) throw new Error(`\u7528\u6CD5: ${usage}`);
199897
200277
  return v2;
199898
200278
  }
200279
+ function semicolonList(raw, field) {
200280
+ if (raw === void 0) return {};
200281
+ const items = raw.split(";").map((x2) => x2.trim()).filter(Boolean);
200282
+ return items.length > 0 ? { [field]: items } : {};
200283
+ }
199899
200284
  async function readStdin() {
199900
200285
  if (process.stdin.isTTY) return "";
199901
200286
  const chunks = [];
@@ -201282,7 +201667,15 @@ ${res.warning}`);
201282
201667
  ...checkpoint ? { continuation: { checkpoint: true, continue: true } } : {},
201283
201668
  // 申报单(B 部):显式增补"本版答复的信"(逗号分隔 annotationId)。常规不用填——系统按派发点名集
201284
201669
  // 自动盖章;只在中途经 status 发现的信等特例用。
201285
- ...flags.get("resolves") !== void 0 ? { resolves: flags.get("resolves").split(",").map((x2) => x2.trim()).filter(Boolean) } : {}
201670
+ ...flags.get("resolves") !== void 0 ? { resolves: flags.get("resolves").split(",").map((x2) => x2.trim()).filter(Boolean) } : {},
201671
+ // 技术交接的四项「系统看不见的东西」(2026-08-17):交作业时顺手带上,服务端写进本节点的
201672
+ // NodeHandoff。**不必再等会话终态另跑 `oasis continuity-handoff`**——那条命令的说明书在
201673
+ // execution/CONTINUITY.md 里,而 adapter 只把 TASK.md 当提示词,线上因此只有 3.6% 的节点写到了。
201674
+ // 分号分隔;一条都不填也完全正常(摘要与交付版本系统自己会记)。
201675
+ ...semicolonList(flags.get("unresolved"), "unresolvedIssues"),
201676
+ ...semicolonList(flags.get("risks"), "risks"),
201677
+ ...semicolonList(flags.get("next"), "nextActions"),
201678
+ ...semicolonList(flags.get("gaps"), "verificationGaps")
201286
201679
  });
201287
201680
  println(message);
201288
201681
  break;
@@ -202556,7 +202949,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
202556
202949
  }
202557
202950
 
202558
202951
  // src/index.ts
202559
- var PKG_VERSION = true ? "0.1.106" : "dev";
202952
+ var PKG_VERSION = true ? "0.1.107" : "dev";
202560
202953
  var OASIS_DIR = path28.join(os11.homedir(), ".oasis");
202561
202954
  var CONFIG_FILE = path28.join(OASIS_DIR, "node-config.json");
202562
202955
  var PID_FILE = path28.join(OASIS_DIR, "node.pid");