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.
- package/dist/index.js +535 -142
- 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
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
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
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
/**
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
-- \
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
14291
|
-
if (
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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" &&
|
|
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
|
|
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
|
-
|
|
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
|
|
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://
|
|
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
|
|
172036
|
+
durableRefs,
|
|
171898
172037
|
completedStepIds: [...st.checkpointState.completedStepIds ?? previous?.completedStepIds ?? []],
|
|
171899
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
183020
|
-
|
|
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
|
|
183032
|
-
|
|
183033
|
-
|
|
183034
|
-
|
|
183035
|
-
|
|
183036
|
-
|
|
183037
|
-
|
|
183038
|
-
|
|
183039
|
-
|
|
183040
|
-
|
|
183041
|
-
|
|
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\`.
|
|
183048
|
-
|
|
183049
|
-
|
|
183050
|
-
|
|
183051
|
-
|
|
183052
|
-
|
|
183053
|
-
|
|
183054
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
183523
|
-
|
|
183524
|
-
|
|
183525
|
-
|
|
183526
|
-
|
|
183527
|
-
|
|
183528
|
-
|
|
183529
|
-
|
|
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
|
-
|
|
183536
|
-
(
|
|
183537
|
-
|
|
183538
|
-
|
|
183539
|
-
|
|
183540
|
-
|
|
183541
|
-
|
|
183542
|
-
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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() || "
|
|
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(
|
|
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, {
|
|
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.
|
|
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
|
|
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
|
-
|
|
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: [
|
|
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.
|
|
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");
|