oasis_test 0.1.106 → 0.1.108
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 +623 -175
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -993,20 +993,27 @@ function pinnedReviewerOf(model, artifactId, role) {
|
|
|
993
993
|
}
|
|
994
994
|
return best?.actor ?? null;
|
|
995
995
|
}
|
|
996
|
+
function latestAttempt(attempts) {
|
|
997
|
+
let best = null;
|
|
998
|
+
for (const a of attempts) {
|
|
999
|
+
if (best === null || a.at > best.at || a.at === best.at && a.seq >= best.seq) best = a;
|
|
1000
|
+
}
|
|
1001
|
+
return best;
|
|
1002
|
+
}
|
|
996
1003
|
function concludeMilestones(model, id) {
|
|
997
1004
|
return (model.concludeAttempts.get(id) ?? []).filter(
|
|
998
1005
|
(a) => attemptStatus(model, a) === "satisfied"
|
|
999
1006
|
);
|
|
1000
1007
|
}
|
|
1001
1008
|
function pendingConcludeAttempt(model, id) {
|
|
1002
|
-
const last = model.concludeAttempts.get(id)
|
|
1009
|
+
const last = latestAttempt(model.concludeAttempts.get(id) ?? []);
|
|
1003
1010
|
return last && attemptStatus(model, last) === "pending" ? last : null;
|
|
1004
1011
|
}
|
|
1005
1012
|
function isConcluded(model, id) {
|
|
1006
1013
|
return concludeMilestones(model, id).length > 0;
|
|
1007
1014
|
}
|
|
1008
1015
|
function concludedHead(model, id) {
|
|
1009
|
-
return concludeMilestones(model, id)
|
|
1016
|
+
return latestAttempt(concludeMilestones(model, id))?.head ?? null;
|
|
1010
1017
|
}
|
|
1011
1018
|
function revisionsOf(model, id) {
|
|
1012
1019
|
return [...model.revisions.values()].filter((r) => r.artifactId === id);
|
|
@@ -3429,12 +3436,19 @@ async function assembleContext(args) {
|
|
|
3429
3436
|
if (args.resolveUpstreamHandoff && artifact.workspace) {
|
|
3430
3437
|
for (const edge of artifact.inputs) {
|
|
3431
3438
|
if (edge.pinned === null) continue;
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3439
|
+
let content = null;
|
|
3440
|
+
try {
|
|
3441
|
+
content = await args.resolveUpstreamHandoff({
|
|
3442
|
+
workOrderId: artifact.workspace,
|
|
3443
|
+
downstreamNodeId: artifact.id,
|
|
3444
|
+
upstreamNodeId: edge.to,
|
|
3445
|
+
pinnedWorkId: edge.pinned
|
|
3446
|
+
});
|
|
3447
|
+
} catch (err) {
|
|
3448
|
+
console.warn(
|
|
3449
|
+
`[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))
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3438
3452
|
if (content) {
|
|
3439
3453
|
const dir = `inputs/${slug2(edge.to)}@${edge.pinned.slice(9, 17)}`;
|
|
3440
3454
|
upstreamHandoffFiles.set(`${dir}/${UPSTREAM_HANDOFF_BASENAME}`, content);
|
|
@@ -4061,6 +4075,25 @@ ${m2.spec.split("\n").map((l) => ` > ${l}`).join("\n")}`);
|
|
|
4061
4075
|
});
|
|
4062
4076
|
})()
|
|
4063
4077
|
),
|
|
4078
|
+
// ★ 2026-08-18:交稿时把「系统看不出来的四件事」一并带上。
|
|
4079
|
+
//
|
|
4080
|
+
// 为什么必须写在**任务书**里:adapter 只把 TASK.md 当提示词喂给模型,其余 bundle 文件(包括
|
|
4081
|
+
// execution/CONTINUITY.md)只是躺在工作目录里、没人读。`--reason` 之所以交付过的节点 100% 都有,
|
|
4082
|
+
// 正因为它既进了任务书又是必填;这四项此前只活在 `oasis help propose` 里——next 上节点全部更新到
|
|
4083
|
+
// 认得它们之后的 10 小时里,两次真实交付**一次都没带**(实测 0/2)。
|
|
4084
|
+
//
|
|
4085
|
+
// 给 `none` 出口是刻意的:逼着写自由文本只会得到「暂无」这种假保证,而「确认没有」与「没人写」
|
|
4086
|
+
// 对下游是两条完全不同的信息(渲染侧据此分流,见 renderDeliveredWorkHandoff)。
|
|
4087
|
+
...args.action === "review" ? [] : [
|
|
4088
|
+
``,
|
|
4089
|
+
`### \u4EA4\u7A3F\u65F6\u4E00\u5E76\u5E26\u4E0A\uFF1A\u4E0B\u6E38\u8981\u7528\u7684\u56DB\u4EF6\u4E8B`,
|
|
4090
|
+
`\u8FD9\u56DB\u9879\u4F1A\u539F\u6837\u51FA\u73B0\u5728\u4E0B\u6E38\u8282\u70B9\u7684 \`inputs/<\u672C\u8282\u70B9>@<\u7248\u672C>/${UPSTREAM_HANDOFF_BASENAME}\` \u91CC\u3002\u5206\u53F7\u5206\u9694\uFF1B**\u786E\u5B9E\u6CA1\u6709\u5C31\u5199 \`none\`\uFF0C\u522B\u7F16\u300C\u6682\u65E0\u300D**\uFF1A`,
|
|
4091
|
+
`- \`--gaps\` **\u4F60\u6CA1\u80FD\u6838\u9A8C\u7684\u90E8\u5206** \u2014\u2014 \u6700\u8981\u7D27\u7684\u4E00\u9879\u3002\u4E0B\u6E38\u9ED8\u8BA4\u300C\u4E0A\u6E38\u4EA4\u4E86\u5C31\u662F\u9A8C\u8FC7\u4E86\u300D\uFF0C\u4F60\u4E0D\u5199\uFF0C\u6CA1\u9A8C\u7684\u90A3\u5757\u5C31\u76F4\u63A5\u8FDB\u4E0B\u4E00\u73AF\u3002\u4F8B\uFF1A\`--gaps "\u6CA1\u505A\u538B\u6D4B;\u53EA\u5728 dev \u9A8C\u8FC7"\``,
|
|
4092
|
+
`- \`--unresolved\` **\u4F60\u77E5\u9053\u4F46\u6CA1\u89E3\u51B3\u7684** \u2014\u2014 \u514D\u5F97\u4E0B\u6E38\u91CD\u590D\u8E29\u4F60\u5DF2\u7ECF\u8E29\u8FC7\u7684\u5751\u3002\u4F8B\uFF1A\`--unresolved "\u7EBF\u4E0A\u914D\u989D\u6CA1\u786E\u8BA4"\``,
|
|
4093
|
+
`- \`--risks\` **\u4EA4\u4ED8\u7269\u672C\u8EAB\u6CA1\u9519\u3001\u4F46\u7528\u7684\u65F6\u5019\u8981\u5F53\u5FC3\u7684** \u2014\u2014 \u4F8B\uFF1A\`--risks "\u5E76\u53D1\u8C03\u7528\u53EF\u80FD\u89E6\u53D1\u9650\u6D41"\``,
|
|
4094
|
+
`- \`--next\` **\u5EFA\u8BAE\u4E0B\u6E38\u5148\u505A\u54EA\u4E00\u6B65**\uFF08\u53EF\u9009\uFF09\u2014\u2014 \u4F8B\uFF1A\`--next "\u5148\u8DD1\u4E00\u6B21\u5C0F\u6D41\u91CF\u7070\u5EA6"\``,
|
|
4095
|
+
`\u5199 \`none\` = \u300C\u6211\u786E\u8BA4\u6CA1\u6709\u300D\uFF1B\u4E0D\u5199 = \u300C\u6CA1\u4EBA\u5199\u8FC7\u300D\u3002**\u8FD9\u4E24\u4EF6\u4E8B\u5BF9\u4E0B\u6E38\u5B8C\u5168\u4E0D\u540C\uFF0C\u522B\u6DF7\u3002**`
|
|
4096
|
+
],
|
|
4064
4097
|
// 兜底(§10):上次失败了 → 明确告诉它发生了什么 + 怎么破(首选 add-revision 拆出去)
|
|
4065
4098
|
...args.lastFailure ? args.lastFailure.resumed ? [
|
|
4066
4099
|
// 续跑分支:上文还在它眼前,别再说"没留下任何产出"(自相矛盾),改成"接着干 + 先核现场"。
|
|
@@ -6024,11 +6057,18 @@ var init_dispatcher = __esm({
|
|
|
6024
6057
|
bundle.files["execution/CONTINUITY.md"] = this.opts.executionContinuityInstructions;
|
|
6025
6058
|
}
|
|
6026
6059
|
if (this.opts.resolveExecutionResumePack && (spec.action === "produce" || spec.action === "integrate") && continuityArtifact) {
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6060
|
+
let resumePack = null;
|
|
6061
|
+
try {
|
|
6062
|
+
resumePack = await this.opts.resolveExecutionResumePack(
|
|
6063
|
+
spec.artifactId,
|
|
6064
|
+
continuityArtifact.workspace,
|
|
6065
|
+
{ jobKey, ...spec.part !== void 0 ? { part: spec.part } : {} }
|
|
6066
|
+
);
|
|
6067
|
+
} catch (err) {
|
|
6068
|
+
console.warn(
|
|
6069
|
+
`[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))
|
|
6070
|
+
);
|
|
6071
|
+
}
|
|
6032
6072
|
if (resumePack) {
|
|
6033
6073
|
bundle.files["execution/RESUME.md"] = resumePack;
|
|
6034
6074
|
bundle.files["TASK.md"] += [
|
|
@@ -6700,6 +6740,15 @@ var init_session_limits = __esm({
|
|
|
6700
6740
|
});
|
|
6701
6741
|
|
|
6702
6742
|
// ../engine/src/model.ts
|
|
6743
|
+
function isMainWork(w2) {
|
|
6744
|
+
return w2.lane === "main";
|
|
6745
|
+
}
|
|
6746
|
+
function isReplyWork(w2) {
|
|
6747
|
+
return w2.lane === "reply";
|
|
6748
|
+
}
|
|
6749
|
+
function replyIssueIdOf(w2) {
|
|
6750
|
+
return w2.lane === "reply" ? w2.replyToIssueId ?? null : null;
|
|
6751
|
+
}
|
|
6703
6752
|
var BUSINESS_HANDOFF_POLICY_FIELD, BUSINESS_HANDOFF_POLICY_EXEMPT, DEFAULT_CONFIG, deriveWorkId, deriveReplyWorkId, deriveReviewId, deriveReplyId;
|
|
6704
6753
|
var init_model = __esm({
|
|
6705
6754
|
"../engine/src/model.ts"() {
|
|
@@ -6798,7 +6847,7 @@ function isReviewOpen(r) {
|
|
|
6798
6847
|
return !r.cancelledAt && !r.endedAt && !r.verdict;
|
|
6799
6848
|
}
|
|
6800
6849
|
function countFailedForNode(works) {
|
|
6801
|
-
const sorted =
|
|
6850
|
+
const sorted = works.filter(isMainWork).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
6802
6851
|
let count2 = 0;
|
|
6803
6852
|
for (const w2 of sorted) {
|
|
6804
6853
|
if (w2.retryAt) break;
|
|
@@ -6814,7 +6863,7 @@ function countFailedForNode(works) {
|
|
|
6814
6863
|
return count2;
|
|
6815
6864
|
}
|
|
6816
6865
|
function countFailedForReplyRound(works) {
|
|
6817
|
-
const sorted = works.filter(
|
|
6866
|
+
const sorted = works.filter(isReplyWork).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
6818
6867
|
let count2 = 0;
|
|
6819
6868
|
for (const w2 of sorted) {
|
|
6820
6869
|
if (w2.retryAt) break;
|
|
@@ -6858,6 +6907,7 @@ function findUnresolvedCRFromOthers(nodeId, issues) {
|
|
|
6858
6907
|
var init_views2 = __esm({
|
|
6859
6908
|
"../engine/src/views.ts"() {
|
|
6860
6909
|
"use strict";
|
|
6910
|
+
init_model();
|
|
6861
6911
|
}
|
|
6862
6912
|
});
|
|
6863
6913
|
|
|
@@ -6866,6 +6916,7 @@ var WorkorderState;
|
|
|
6866
6916
|
var init_state = __esm({
|
|
6867
6917
|
"../engine/src/state.ts"() {
|
|
6868
6918
|
"use strict";
|
|
6919
|
+
init_model();
|
|
6869
6920
|
init_views2();
|
|
6870
6921
|
WorkorderState = class {
|
|
6871
6922
|
workorder;
|
|
@@ -6925,12 +6976,25 @@ var init_state = __esm({
|
|
|
6925
6976
|
allWorks() {
|
|
6926
6977
|
return [...this.workMap.values()];
|
|
6927
6978
|
}
|
|
6928
|
-
|
|
6979
|
+
/**
|
|
6980
|
+
* ★ 该节点的**全部** work——**主链与回信轮都在里面**(刺眼命名:调它 = 显式声明「我不按 lane 过滤」)。
|
|
6981
|
+
* 只有确实需要「所有运行」的入口才用它(work 编号 = 现有数 +1、I1 占位判定、连败链计数——那些函数自己再按
|
|
6982
|
+
* lane 分)。要主链就 {@link mainWorksOf}、要回信就 {@link replyWorksOf},别在调用点手写 `!replyToIssueId`。
|
|
6983
|
+
*/
|
|
6984
|
+
allWorksIncludingReplies(nodeId) {
|
|
6929
6985
|
return this.allWorks().filter((w2) => w2.nodeId === nodeId);
|
|
6930
6986
|
}
|
|
6931
|
-
/**
|
|
6987
|
+
/** 该节点的**主链** work(派发/验收/发版本号那条道)。判据 = lane({@link isMainWork})。 */
|
|
6988
|
+
mainWorksOf(nodeId) {
|
|
6989
|
+
return this.allWorksIncludingReplies(nodeId).filter(isMainWork);
|
|
6990
|
+
}
|
|
6991
|
+
/** 该节点的**回信轮** work(对 comment 的答话那条道)。判据 = lane({@link isReplyWork})。 */
|
|
6992
|
+
replyWorksOf(nodeId) {
|
|
6993
|
+
return this.allWorksIncludingReplies(nodeId).filter(isReplyWork);
|
|
6994
|
+
}
|
|
6995
|
+
/** 该节点有没有还占着名额的运行(I1 判据的一半,另一半见 hasQueuedWorkFor)。主链或回信都占名额。 */
|
|
6932
6996
|
openWorkOf(nodeId) {
|
|
6933
|
-
return this.
|
|
6997
|
+
return this.allWorksIncludingReplies(nodeId).find(isWorkOpen);
|
|
6934
6998
|
}
|
|
6935
6999
|
artifactsOf(workId) {
|
|
6936
7000
|
return this.artifactList.filter((a) => a.workId === workId);
|
|
@@ -6972,7 +7036,7 @@ var init_state = __esm({
|
|
|
6972
7036
|
}
|
|
6973
7037
|
/** 本事务是否已对该节点的某个 work 排了 work.accept——activate 据此避让。 */
|
|
6974
7038
|
hasAcceptedWorkFor(nodeId) {
|
|
6975
|
-
const myWorks = new Set(this.
|
|
7039
|
+
const myWorks = new Set(this.allWorksIncludingReplies(nodeId).map((w2) => w2.id));
|
|
6976
7040
|
return this.emittedList.some(
|
|
6977
7041
|
(e) => e.kind === "work.accept" && myWorks.has(e.workId)
|
|
6978
7042
|
);
|
|
@@ -7135,7 +7199,7 @@ function scan(state, ctx) {
|
|
|
7135
7199
|
continue;
|
|
7136
7200
|
}
|
|
7137
7201
|
if (wstate === "failed") {
|
|
7138
|
-
const cf = countFailedForNode(state.
|
|
7202
|
+
const cf = countFailedForNode(state.allWorksIncludingReplies(node.id));
|
|
7139
7203
|
const overLimit = cf > (ctx.config?.maxRetries ?? DEFAULT_CONFIG.maxRetries);
|
|
7140
7204
|
if (overLimit && classify(node.assigneeActorId) === "agent") {
|
|
7141
7205
|
events.push({
|
|
@@ -7194,7 +7258,7 @@ function tryToRun(state, nodeId, ctx, events) {
|
|
|
7194
7258
|
if (isBusinessDependencyEdge(state, e) && !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return;
|
|
7195
7259
|
}
|
|
7196
7260
|
if (state.hasQueuedWorkFor(nodeId)) return;
|
|
7197
|
-
const workId = deriveWorkId(nodeId, state.
|
|
7261
|
+
const workId = deriveWorkId(nodeId, state.allWorksIncludingReplies(nodeId).length + 1);
|
|
7198
7262
|
events.push({
|
|
7199
7263
|
kind: "work.create",
|
|
7200
7264
|
workorderId: state.workorder.id,
|
|
@@ -7318,7 +7382,7 @@ function processCommentReplies(state, nodeId, ctx, events) {
|
|
|
7318
7382
|
const comments = state.allIssues().filter((i) => i.kind === "comment" && i.resolvedAt === null && i.aboutNodeId === nodeId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
7319
7383
|
if (comments.length === 0) return;
|
|
7320
7384
|
if (state.hasQueuedWorkFor(nodeId)) return;
|
|
7321
|
-
const replyWorks = state.
|
|
7385
|
+
const replyWorks = state.replyWorksOf(nodeId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
7322
7386
|
const inflight = replyWorks.find((w2) => !w2.endedAt && !w2.deadAt);
|
|
7323
7387
|
if (inflight) {
|
|
7324
7388
|
const start = inflight.startedAt ?? inflight.createdAt;
|
|
@@ -7483,16 +7547,24 @@ function handoffBlock(state, nodeId, workId) {
|
|
|
7483
7547
|
overridable: false
|
|
7484
7548
|
};
|
|
7485
7549
|
}
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
}
|
|
7550
|
+
if (work?.businessHandoffStatus === "not_delivered") {
|
|
7551
|
+
return {
|
|
7552
|
+
code: "handoff-undeclared",
|
|
7553
|
+
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`,
|
|
7554
|
+
overridable: true
|
|
7555
|
+
};
|
|
7556
|
+
}
|
|
7557
|
+
if (work?.status !== "success" && work?.businessHandoffStatus !== "delivered") {
|
|
7558
|
+
return {
|
|
7559
|
+
code: "work-not-success",
|
|
7560
|
+
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`,
|
|
7561
|
+
overridable: true
|
|
7562
|
+
};
|
|
7563
|
+
}
|
|
7564
|
+
return null;
|
|
7493
7565
|
}
|
|
7494
7566
|
function requiresBusinessHandoff(state, nodeId, work) {
|
|
7495
|
-
if (work
|
|
7567
|
+
if (work && isReplyWork(work)) return false;
|
|
7496
7568
|
if (!state.edgesFrom(nodeId).some((edge) => isBusinessDependencyEdge(state, edge))) return false;
|
|
7497
7569
|
if (typeof work?.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) return false;
|
|
7498
7570
|
return true;
|
|
@@ -7506,12 +7578,12 @@ function activate(state, nodeId, _ctx) {
|
|
|
7506
7578
|
if (!node.assigneeActorId) return "no_assignee";
|
|
7507
7579
|
if (state.openWorkOf(nodeId)) return "already_running";
|
|
7508
7580
|
if (state.hasQueuedWorkFor(nodeId)) return "already_running";
|
|
7509
|
-
if (state.
|
|
7581
|
+
if (state.allWorksIncludingReplies(nodeId).some((w2) => w2.acceptanceState === "draft")) return "in_review";
|
|
7510
7582
|
const unresolvedSelf = findUnresolvedSelfIssue(nodeId, state.allIssues());
|
|
7511
7583
|
if (unresolvedSelf) return unresolvedSelf.kind === "escalation" ? "failed" : "waiting_answer";
|
|
7512
7584
|
const inbound = state.edgesInto(nodeId);
|
|
7513
7585
|
if (inbound.some((e) => e.required && e.kind === "data" && !e.pinnedWorkId && !isEdgeInputUnobtainable(state, e))) return "upstream_not_ready";
|
|
7514
|
-
if (inbound.some((e) => state.
|
|
7586
|
+
if (inbound.some((e) => state.allWorksIncludingReplies(e.fromNodeId).some(isWorkOpen))) return "upstream_unstable";
|
|
7515
7587
|
return "dispatched";
|
|
7516
7588
|
}
|
|
7517
7589
|
function assessConvergence(state, ctx) {
|
|
@@ -7528,6 +7600,7 @@ var init_activate = __esm({
|
|
|
7528
7600
|
"use strict";
|
|
7529
7601
|
init_src2();
|
|
7530
7602
|
init_model();
|
|
7603
|
+
init_model();
|
|
7531
7604
|
init_views2();
|
|
7532
7605
|
}
|
|
7533
7606
|
});
|
|
@@ -7626,7 +7699,10 @@ CREATE TABLE IF NOT EXISTS ${schema}.works (
|
|
|
7626
7699
|
outcome_detail jsonb,
|
|
7627
7700
|
session_ref text,
|
|
7628
7701
|
continues_work_id text,
|
|
7629
|
-
-- \
|
|
7702
|
+
-- \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
|
|
7703
|
+
-- "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
|
|
7704
|
+
lane text CHECK (lane IN ('main','reply')),
|
|
7705
|
+
-- \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
7706
|
reply_to_issue_id text,
|
|
7631
7707
|
output_version_no integer,
|
|
7632
7708
|
conclusion text,
|
|
@@ -7807,8 +7883,15 @@ ALTER TABLE ${schema}.works DROP CONSTRAINT IF EXISTS works_business_handoff_sta
|
|
|
7807
7883
|
ALTER TABLE ${schema}.works ADD CONSTRAINT works_business_handoff_status_check
|
|
7808
7884
|
CHECK (business_handoff_status IS NULL OR business_handoff_status IN ('delivered','not_delivered'));
|
|
7809
7885
|
ALTER TABLE ${schema}.reviews ADD COLUMN IF NOT EXISTS status text;
|
|
7810
|
-
-- \u56DE\u4FE1
|
|
7886
|
+
-- \u56DE\u4FE1\u8F6E\u6307\u5411\u54EA\u6761 comment issue\uFF08\u7EAF\u6570\u636E\uFF09
|
|
7811
7887
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS reply_to_issue_id text;
|
|
7888
|
+
-- \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
|
|
7889
|
+
-- \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
|
|
7890
|
+
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS lane text;
|
|
7891
|
+
UPDATE ${schema}.works SET lane = CASE WHEN reply_to_issue_id IS NULL THEN 'main' ELSE 'reply' END
|
|
7892
|
+
WHERE lane IS NULL;
|
|
7893
|
+
ALTER TABLE ${schema}.works DROP CONSTRAINT IF EXISTS works_lane_check;
|
|
7894
|
+
ALTER TABLE ${schema}.works ADD CONSTRAINT works_lane_check CHECK (lane IS NULL OR lane IN ('main','reply'));
|
|
7812
7895
|
-- \u2605 issue \u5B58\u6D3B\u5224\u636E\u552F\u4E00\u5316\uFF08ADR 0144\uFF09\uFF1Aresolution \u627F\u63A5\u65E7 state \u7684 closed\uFF08=wontfix\uFF09\uFF1B
|
|
7813
7896
|
-- \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
7897
|
-- \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
|
|
@@ -7937,14 +8020,13 @@ FROM ${schema}.workflow_nodes n
|
|
|
7937
8020
|
LEFT JOIN ${schema}.works lw ON lw.id = n.latest_work_id
|
|
7938
8021
|
LEFT JOIN ${schema}.works ls ON ls.id = n.latest_success_work_id;
|
|
7939
8022
|
|
|
8023
|
+
-- workorder_view \u5DF2\u4E0B\u7EBF\uFF08\u5DE5\u5355 1aa1f35b\uFF09\uFF1A\u5B83\u53EA\u989D\u5916\u7ED9\u51FA total_count / done_count \u4E24\u4E2A\u8FDB\u5EA6\u6570\uFF0C\u5168\u4ED3\u96F6\u6D88\u8D39\u65B9\uFF0C
|
|
8024
|
+
-- \u4E14\u53E3\u5F84\u4E0E\u540E\u7AEF\u771F\u6E90\uFF08server summaryOfWorkspace \u7684 progressExcludingBrief\uFF09\u4E0D\u540C\u2014\u2014total \u53EA\u6392 cancelled\u3001done \u7528
|
|
8025
|
+
-- latest_accept_id IS NOT NULL\uFF08\u66FE\u88AB\u9A8C\u6536\u8FC7\u81F3\u5C11\u4E00\u6B21\uFF09\uFF0C\u800C\u771F\u6E90\u6392\u9664 brief + \u5DF2\u4F5C\u5E9F\u3001\u6309\u300C\u9A8C\u6536\u843D\u5728\u5F53\u524D\u7248\u300D\u7B97\u3002
|
|
8026
|
+
-- \u4FDD\u7559\u4E00\u4EFD\u89C6\u56FE\u65E2\u6CA1\u4EBA\u8BFB\u3001\u53E3\u5F84\u53C8\u5BF9\u4E0D\u4E0A\uFF0C\u53EA\u4F1A\u662F\u7B2C\u4E09\u5904\u5BF9\u4E0D\u4E0A\u7684\u8FDB\u5EA6\u6570\u3002\u8FD9\u91CC\u4E0D\u518D CREATE\uFF1B\u4E0B\u9762\u7684 DROP \u8BA9\u5B58\u91CF\u5E93
|
|
8027
|
+
-- \u5728\u4E0B\u6B21 migrate \u65F6\u628A\u65E7\u89C6\u56FE\u5220\u6389\uFF08ENGINE_DDL \u6BB5\u4EA6\u6709\u4E00\u5904 DROP\uFF0C\u4E8C\u8005\u90FD\u7559\u4EE5\u9632\u91CD\u6392\uFF09\u3002\u5DE5\u5355\u8FDB\u5EA6\u7EDF\u4E00\u8D70\u540E\u7AEF\u8BFB\u6A21\u578B\uFF0C
|
|
8028
|
+
-- \u9700\u8981 SQL \u4FA7\u7EDF\u8BA1\u65F6\u53E6\u7ACB\u4E0E\u771F\u6E90\u540C\u53E3\u5F84\u7684\u89C6\u56FE\u5E76\u6CE8\u660E\u670D\u52A1\u5BF9\u8C61\uFF0C\u522B\u590D\u6D3B\u8FD9\u4E24\u4E2A\u6570\u3002
|
|
7940
8029
|
DROP VIEW IF EXISTS ${schema}.workorder_view;
|
|
7941
|
-
CREATE VIEW ${schema}.workorder_view AS
|
|
7942
|
-
SELECT o.*,
|
|
7943
|
-
(SELECT count(*) FROM ${schema}.workflow_nodes n
|
|
7944
|
-
WHERE n.workorder_id = o.id AND n.cancelled_at IS NULL) AS total_count,
|
|
7945
|
-
(SELECT count(*) FROM ${schema}.workflow_nodes n
|
|
7946
|
-
WHERE n.workorder_id = o.id AND n.cancelled_at IS NULL AND n.latest_accept_id IS NOT NULL) AS done_count
|
|
7947
|
-
FROM ${schema}.workorders o;
|
|
7948
8030
|
`;
|
|
7949
8031
|
}
|
|
7950
8032
|
var init_schema2 = __esm({
|
|
@@ -13158,7 +13240,10 @@ var init_store_postgres = __esm({
|
|
|
13158
13240
|
JOIN ${this.s}.workflow_nodes n ON n.id = r.node_id WHERE n.workorder_id = $1`,
|
|
13159
13241
|
[id]
|
|
13160
13242
|
);
|
|
13161
|
-
const works = await this.c.query(
|
|
13243
|
+
const works = await this.c.query(
|
|
13244
|
+
`SELECT * FROM ${this.s}.works WHERE workorder_id = $1 ORDER BY created_at ASC, id ASC`,
|
|
13245
|
+
[id]
|
|
13246
|
+
);
|
|
13162
13247
|
const arts = await this.c.query(
|
|
13163
13248
|
`SELECT a.* FROM ${this.s}.work_artifacts a JOIN ${this.s}.works w ON w.id = a.work_id WHERE w.workorder_id = $1`,
|
|
13164
13249
|
[id]
|
|
@@ -13259,6 +13344,9 @@ var init_store_postgres = __esm({
|
|
|
13259
13344
|
outcomeDetail: w2.outcome_detail,
|
|
13260
13345
|
sessionRef: w2.session_ref,
|
|
13261
13346
|
continuesWorkId: w2.continues_work_id,
|
|
13347
|
+
// ★ lane 兜底(存量行迁移前 / 迁移与回填之间的竞态窗):无值时按 reply_to_issue_id 派生,
|
|
13348
|
+
// 保证 TS 侧 `Work.lane` 恒非空。reply_to_issue_id 在读写映射层可直接读(护栏白名单)。
|
|
13349
|
+
lane: w2.lane ?? (w2.reply_to_issue_id == null ? "main" : "reply"),
|
|
13262
13350
|
replyToIssueId: w2.reply_to_issue_id ?? null,
|
|
13263
13351
|
outputVersionNo: w2.output_version_no,
|
|
13264
13352
|
conclusion: w2.conclusion,
|
|
@@ -13470,8 +13558,8 @@ var init_store_postgres = __esm({
|
|
|
13470
13558
|
(id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
|
|
13471
13559
|
outcome_detail,session_ref,continues_work_id,reply_to_issue_id,output_version_no,
|
|
13472
13560
|
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)
|
|
13561
|
+
acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane)
|
|
13562
|
+
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
13563
|
ON CONFLICT (id) DO NOTHING`,
|
|
13476
13564
|
[
|
|
13477
13565
|
w2.id,
|
|
@@ -13501,7 +13589,8 @@ var init_store_postgres = __esm({
|
|
|
13501
13589
|
w2.acceptedBy,
|
|
13502
13590
|
w2.rejectedReason,
|
|
13503
13591
|
w2.overrideBy,
|
|
13504
|
-
w2.overrideReason
|
|
13592
|
+
w2.overrideReason,
|
|
13593
|
+
w2.lane
|
|
13505
13594
|
]
|
|
13506
13595
|
);
|
|
13507
13596
|
break;
|
|
@@ -13935,7 +14024,7 @@ function markNodeForRetry(state, nodeId, at) {
|
|
|
13935
14024
|
(i) => i.kind === "comment" && i.resolvedAt === null && i.aboutNodeId === nodeId
|
|
13936
14025
|
);
|
|
13937
14026
|
if (hasUnresolvedComment) {
|
|
13938
|
-
const replyWorks = state.
|
|
14027
|
+
const replyWorks = state.replyWorksOf(nodeId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
13939
14028
|
const latest = replyWorks[replyWorks.length - 1];
|
|
13940
14029
|
if (latest && latest.deadAt && workState(latest, hasOutput(latest, 0)) === "dead") {
|
|
13941
14030
|
state.updateWork(latest.id, { retryAt: at, status: "retry" });
|
|
@@ -14002,6 +14091,11 @@ var init_plan = __esm({
|
|
|
14002
14091
|
if (!node || node.cancelledAt) return;
|
|
14003
14092
|
killLatestWork(state, e.nodeId, ctx.at);
|
|
14004
14093
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
14094
|
+
for (const issue2 of state.allIssues()) {
|
|
14095
|
+
if (issue2.resolvedAt !== null) continue;
|
|
14096
|
+
if (issue2.aboutNodeId !== e.nodeId && issue2.raisedByNodeId !== e.nodeId) continue;
|
|
14097
|
+
state.updateIssue(issue2.id, { resolvedAt: ctx.at, resolution: "wontfix" });
|
|
14098
|
+
}
|
|
14005
14099
|
state.updateNode(e.nodeId, { cancelledAt: ctx.at, updatedAt: ctx.at });
|
|
14006
14100
|
for (const edge of state.edgesFrom(e.nodeId)) {
|
|
14007
14101
|
const dn = state.node(edge.toNodeId);
|
|
@@ -14236,7 +14330,7 @@ var init_workorder = __esm({
|
|
|
14236
14330
|
|
|
14237
14331
|
// ../engine/src/handlers/work.ts
|
|
14238
14332
|
function latestSuccessOf(state, nodeId) {
|
|
14239
|
-
const successes = state.
|
|
14333
|
+
const successes = state.mainWorksOf(nodeId).filter((w2) => w2.status === "success").sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
14240
14334
|
return successes.length > 0 ? successes[successes.length - 1].id : null;
|
|
14241
14335
|
}
|
|
14242
14336
|
var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workTimeout;
|
|
@@ -14245,6 +14339,7 @@ var init_work = __esm({
|
|
|
14245
14339
|
"use strict";
|
|
14246
14340
|
init_views2();
|
|
14247
14341
|
init_kill();
|
|
14342
|
+
init_model();
|
|
14248
14343
|
workCreate = {
|
|
14249
14344
|
name: "work/create",
|
|
14250
14345
|
kind: "work.create",
|
|
@@ -14253,7 +14348,7 @@ var init_work = __esm({
|
|
|
14253
14348
|
const node = state.node(e.nodeId);
|
|
14254
14349
|
if (!node) return;
|
|
14255
14350
|
if (e.replyToIssueId) {
|
|
14256
|
-
if (state.
|
|
14351
|
+
if (state.allWorksIncludingReplies(e.nodeId).some((w2) => !w2.endedAt && !w2.deadAt)) return;
|
|
14257
14352
|
state.insertWork({
|
|
14258
14353
|
id: e.workId,
|
|
14259
14354
|
workorderId: e.workorderId,
|
|
@@ -14265,6 +14360,7 @@ var init_work = __esm({
|
|
|
14265
14360
|
deadAt: null,
|
|
14266
14361
|
cancelledAt: null,
|
|
14267
14362
|
status: "running",
|
|
14363
|
+
lane: "reply",
|
|
14268
14364
|
outcome: null,
|
|
14269
14365
|
outcomeDetail: null,
|
|
14270
14366
|
sessionRef: null,
|
|
@@ -14287,8 +14383,8 @@ var init_work = __esm({
|
|
|
14287
14383
|
}
|
|
14288
14384
|
supersedeLatestWork(state, e.nodeId, ctx.at);
|
|
14289
14385
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
14290
|
-
for (const w2 of state.
|
|
14291
|
-
if (
|
|
14386
|
+
for (const w2 of state.replyWorksOf(e.nodeId)) {
|
|
14387
|
+
if (!w2.endedAt && !w2.deadAt) killWorkRow(state, w2, ctx.at);
|
|
14292
14388
|
}
|
|
14293
14389
|
state.insertWork({
|
|
14294
14390
|
id: e.workId,
|
|
@@ -14301,6 +14397,7 @@ var init_work = __esm({
|
|
|
14301
14397
|
deadAt: null,
|
|
14302
14398
|
cancelledAt: null,
|
|
14303
14399
|
status: "running",
|
|
14400
|
+
lane: "main",
|
|
14304
14401
|
outcome: null,
|
|
14305
14402
|
outcomeDetail: null,
|
|
14306
14403
|
sessionRef: null,
|
|
@@ -14386,7 +14483,7 @@ var init_work = __esm({
|
|
|
14386
14483
|
apply(e, state, ctx) {
|
|
14387
14484
|
const w2 = state.work(e.workId);
|
|
14388
14485
|
if (!w2 || w2.endedAt || w2.deadAt) return;
|
|
14389
|
-
if (w2
|
|
14486
|
+
if (isReplyWork(w2)) {
|
|
14390
14487
|
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
14488
|
const replied = myReplies.length > 0;
|
|
14392
14489
|
const ok = e.outcome === "completed" && (replied || hasOutput(w2, 0));
|
|
@@ -14771,7 +14868,7 @@ var init_review = __esm({
|
|
|
14771
14868
|
const block = acceptBlock(state, e.workId);
|
|
14772
14869
|
if (block && !(block.overridable && e.override)) return;
|
|
14773
14870
|
const w2 = state.work(e.workId);
|
|
14774
|
-
if (w2
|
|
14871
|
+
if (isReplyWork(w2)) {
|
|
14775
14872
|
state.updateWork(e.workId, {
|
|
14776
14873
|
acceptanceState: "accepted",
|
|
14777
14874
|
acceptedAt: ctx.at,
|
|
@@ -14827,7 +14924,7 @@ var init_review = __esm({
|
|
|
14827
14924
|
...w2.endedAt ? {} : { endedAt: ctx.at, outcome: "completed" }
|
|
14828
14925
|
});
|
|
14829
14926
|
if (w2.outputVersionNo === null) {
|
|
14830
|
-
const versions = state.
|
|
14927
|
+
const versions = state.allWorksIncludingReplies(w2.nodeId).map((x2) => x2.outputVersionNo ?? 0);
|
|
14831
14928
|
state.updateWork(e.workId, { outputVersionNo: Math.max(0, ...versions) + 1 });
|
|
14832
14929
|
}
|
|
14833
14930
|
for (const r of state.openReviewsOf(e.workId)) {
|
|
@@ -15665,7 +15762,8 @@ function workToRevision(w2, workInputs, artifactFiles) {
|
|
|
15665
15762
|
resolves: void 0
|
|
15666
15763
|
};
|
|
15667
15764
|
revision.rejectedReason = w2.rejectedReason;
|
|
15668
|
-
revision.
|
|
15765
|
+
revision.lane = w2.lane;
|
|
15766
|
+
revision.replyIssueId = replyIssueIdOf(w2);
|
|
15669
15767
|
return revision;
|
|
15670
15768
|
}
|
|
15671
15769
|
function issueToAnnotation(issue2, replies, recipients) {
|
|
@@ -15780,7 +15878,7 @@ function workToConcludeAttempt(w2, requirements, reviews) {
|
|
|
15780
15878
|
gate: buildGateFromRequirements(reqs)
|
|
15781
15879
|
};
|
|
15782
15880
|
}
|
|
15783
|
-
if (w2.acceptanceState === "accepted") {
|
|
15881
|
+
if (w2.acceptanceState === "accepted" && w2.outputVersionNo !== null) {
|
|
15784
15882
|
const reqs = requirements.filter((r) => r.nodeId === w2.nodeId);
|
|
15785
15883
|
return {
|
|
15786
15884
|
head: w2.id,
|
|
@@ -15942,6 +16040,7 @@ function bridgeLoadReadModel(input) {
|
|
|
15942
16040
|
const holds = /* @__PURE__ */ new Map();
|
|
15943
16041
|
for (const node of allNodes) {
|
|
15944
16042
|
if (!node.latestWorkId) continue;
|
|
16043
|
+
if (node.cancelledAt) continue;
|
|
15945
16044
|
const lw = allWorks.find((w2) => w2.id === node.latestWorkId);
|
|
15946
16045
|
if (!lw || !lw.deadAt) continue;
|
|
15947
16046
|
if (node.latestAcceptId === node.latestWorkId) continue;
|
|
@@ -16861,7 +16960,7 @@ var init_kernel_bridge = __esm({
|
|
|
16861
16960
|
await this.refreshTracked();
|
|
16862
16961
|
const wid = this.wo(args.artifactId);
|
|
16863
16962
|
let workId;
|
|
16864
|
-
const openWorks = [...this.model.revisions.values()].filter((r) => r.artifactId === args.artifactId && r.state === "working" &&
|
|
16963
|
+
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
16964
|
workId = openWorks[0]?.id;
|
|
16866
16965
|
const artifact = this.model.artifacts.get(args.artifactId);
|
|
16867
16966
|
if (!workId) {
|
|
@@ -130836,7 +130935,7 @@ function buildNodeTimeline(artifactId, snap, runIdByTarget) {
|
|
|
130836
130935
|
const nodeWorkIds = /* @__PURE__ */ new Set();
|
|
130837
130936
|
for (const w2 of snap.works) {
|
|
130838
130937
|
if (w2.nodeId !== artifactId) continue;
|
|
130839
|
-
if (w2
|
|
130938
|
+
if (isReplyWork(w2)) continue;
|
|
130840
130939
|
nodeWorkIds.add(w2.id);
|
|
130841
130940
|
const status = w2.status ?? (w2.retryAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
130842
130941
|
items.push({
|
|
@@ -130904,6 +131003,7 @@ var TYPE_ORDER;
|
|
|
130904
131003
|
var init_timeline = __esm({
|
|
130905
131004
|
"../server/src/domains/collab/timeline.ts"() {
|
|
130906
131005
|
"use strict";
|
|
131006
|
+
init_src3();
|
|
130907
131007
|
TYPE_ORDER = { work: 0, review: 1, issue: 2 };
|
|
130908
131008
|
}
|
|
130909
131009
|
});
|
|
@@ -149035,6 +149135,7 @@ function stallOf(model, id, runtime, dispatcherSignals) {
|
|
|
149035
149135
|
if (gaps.length > 0) {
|
|
149036
149136
|
return { kind: "waiting_human", action: "resolve_gap", detail: `\u5361\u5728\u7F3A\u53E3\u4E0A\u5F85\u4EBA\u8865\uFF1A${gaps[0].description}` };
|
|
149037
149137
|
}
|
|
149138
|
+
if (isDeliveredSettled(model, id)) return null;
|
|
149038
149139
|
if (isHeld(model, id)) {
|
|
149039
149140
|
const h = holdOf(model, id);
|
|
149040
149141
|
return { kind: "held", action: "resume", detail: `\u5DF2\u6682\u505C\u6D3E\u53D1${h?.reason ? `\uFF1A${h.reason}` : ""}\u2014\u2014\u5F85\u6062\u590D`, ...h?.at ? { since: h.at } : {} };
|
|
@@ -149080,7 +149181,7 @@ function stallOf(model, id, runtime, dispatcherSignals) {
|
|
|
149080
149181
|
if (dep) return { kind: "waiting_system", ref: dep.to, detail: `\u7B49\u4E0A\u6E38\u6536\u53E3\uFF1A${dep.to}` };
|
|
149081
149182
|
return { kind: "waiting_system", detail: `\u963B\u585E\u4E2D\uFF1A${b2.reasons[0] ?? "\u5F85\u524D\u7F6E\u5C31\u7EEA"}` };
|
|
149082
149183
|
}
|
|
149083
|
-
const queueCause =
|
|
149184
|
+
const queueCause = queueBlockCauseOf(model, id);
|
|
149084
149185
|
if (queueCause !== null) {
|
|
149085
149186
|
const pending = queueOf(model, id);
|
|
149086
149187
|
const since = pending[0]?.createdAt;
|
|
@@ -149907,9 +150008,10 @@ function summaryOfWorkspace(model, workspace, arts, ref2, resolveProject, dispat
|
|
|
149907
150008
|
owner: ref2(seed.owner),
|
|
149908
150009
|
participants: participantIds.map(ref2),
|
|
149909
150010
|
artifactCount: arts.length,
|
|
150011
|
+
// 原始计数(含 brief、含已作废)——仅作旧客户端回退;界面真源是下面的 progressExcludingBrief。
|
|
149910
150012
|
progress: { concluded, total: arts.length },
|
|
149911
|
-
// ADR 0088 D1
|
|
149912
|
-
progressExcludingBrief: progressExcludingBrief(arts, members
|
|
150013
|
+
// ADR 0088 D1:派生位内联,前端不再按行拉详情。这是进度的唯一真源(排除 brief + 已作废,见函数注释)。
|
|
150014
|
+
progressExcludingBrief: progressExcludingBrief(arts, members),
|
|
149913
150015
|
...runtimeAlertOf(arts, members, runtimeOf) ?? {},
|
|
149914
150016
|
updatedAt: lastOps[lastOps.length - 1] ?? "",
|
|
149915
150017
|
projectId: resolveProject?.(workspace) ?? null,
|
|
@@ -149924,20 +150026,17 @@ function tagsOf(seed) {
|
|
|
149924
150026
|
if (!Array.isArray(raw)) return [];
|
|
149925
150027
|
return [...new Set(raw.map((v2) => typeof v2 === "string" ? v2.trim() : "").filter(Boolean))];
|
|
149926
150028
|
}
|
|
149927
|
-
function progressExcludingBrief(arts, members
|
|
149928
|
-
let
|
|
149929
|
-
let
|
|
150029
|
+
function progressExcludingBrief(arts, members) {
|
|
150030
|
+
let total = 0;
|
|
150031
|
+
let concluded = 0;
|
|
149930
150032
|
for (let i = 0; i < arts.length; i++) {
|
|
149931
|
-
|
|
149932
|
-
|
|
149933
|
-
|
|
149934
|
-
|
|
150033
|
+
const info = members[i];
|
|
150034
|
+
if (arts[i].type === "brief") continue;
|
|
150035
|
+
if (info.sealed) continue;
|
|
150036
|
+
total++;
|
|
150037
|
+
if (info.concluded) concluded++;
|
|
149935
150038
|
}
|
|
149936
|
-
|
|
149937
|
-
return {
|
|
149938
|
-
concluded: Math.max(0, progress.concluded - briefConcluded),
|
|
149939
|
-
total: Math.max(0, progress.total - briefCount)
|
|
149940
|
-
};
|
|
150039
|
+
return { concluded, total };
|
|
149941
150040
|
}
|
|
149942
150041
|
function runtimeAlertOf(arts, members, runtimeOf) {
|
|
149943
150042
|
if (!runtimeOf) return void 0;
|
|
@@ -157272,6 +157371,8 @@ function executionEnvHash(p2) {
|
|
|
157272
157371
|
workRoot: p2.workRoot,
|
|
157273
157372
|
home: p2.homeHostDir,
|
|
157274
157373
|
bin: p2.binHostPath,
|
|
157374
|
+
// 包根目录进哈希:它决定挂载形态(整包 vs 单文件),换了就得重建 persist 容器
|
|
157375
|
+
binPkg: p2.binPackageDir ?? null,
|
|
157275
157376
|
extraRo: [...p2.extraRoBindDirs ?? []].sort()
|
|
157276
157377
|
});
|
|
157277
157378
|
return (0, import_node_crypto13.createHash)("sha256").update(material).digest("hex").slice(0, 16);
|
|
@@ -157312,9 +157413,9 @@ function commonArgs(p2, name) {
|
|
|
157312
157413
|
// 不变量 1:workdir 根同路径挂入(rw)
|
|
157313
157414
|
"-v",
|
|
157314
157415
|
`${p2.workRoot}:${p2.workRoot}`,
|
|
157315
|
-
// 不变量 4:agent CLI
|
|
157316
|
-
|
|
157317
|
-
`${p2.binHostPath}:${binMount}:ro
|
|
157416
|
+
// 不变量 4:agent CLI 只读挂入。自包含二进制挂单文件到 /usr/local/bin/<name>;
|
|
157417
|
+
// 脚本型 CLI 改挂整包(同路径),执行走 binHostPath 原路径——见 WrapParams.binPackageDir。
|
|
157418
|
+
...p2.binPackageDir ? ["-v", `${p2.binPackageDir}:${p2.binPackageDir}:ro`] : ["-v", `${p2.binHostPath}:${binMount}:ro`],
|
|
157318
157419
|
// 不变量 5:HOME 持久
|
|
157319
157420
|
"-v",
|
|
157320
157421
|
`${p2.homeHostDir}:${CONTAINER_HOME}`
|
|
@@ -157338,7 +157439,7 @@ function envFlags(env) {
|
|
|
157338
157439
|
}
|
|
157339
157440
|
function wrapForContainer(p2) {
|
|
157340
157441
|
const ee = p2.executionEnv;
|
|
157341
|
-
const binInContainer = `/usr/local/bin/${p2.binName ?? path6.basename(p2.binHostPath)}`;
|
|
157442
|
+
const binInContainer = p2.binPackageDir ? p2.binHostPath : `/usr/local/bin/${p2.binName ?? path6.basename(p2.binHostPath)}`;
|
|
157342
157443
|
const eeHash = executionEnvHash(p2);
|
|
157343
157444
|
if (ee.persist && p2.job.workdirKey) {
|
|
157344
157445
|
const name2 = persistContainerName(p2.job.workdirKey);
|
|
@@ -157437,6 +157538,20 @@ function resolveBinHostPath(bin) {
|
|
|
157437
157538
|
}
|
|
157438
157539
|
throw new Error(`executionEnv\uFF1A\u5728 PATH \u4E0A\u627E\u4E0D\u5230\u53EF\u6267\u884C\u7684 ${bin}\uFF0C\u65E0\u6CD5\u6302\u5165\u5BB9\u5668`);
|
|
157439
157540
|
}
|
|
157541
|
+
function resolveBinPackageDir(binHostPath) {
|
|
157542
|
+
if (!/\.(js|mjs|cjs)$/i.test(binHostPath)) return null;
|
|
157543
|
+
let dir = path7.dirname(binHostPath);
|
|
157544
|
+
for (let i = 0; i < 8; i++) {
|
|
157545
|
+
if (fs7.existsSync(path7.join(dir, "package.json"))) {
|
|
157546
|
+
if (dir === "/" || dir === os3.tmpdir() || dir.split(path7.sep).length <= 2) return null;
|
|
157547
|
+
return dir;
|
|
157548
|
+
}
|
|
157549
|
+
const parent = path7.dirname(dir);
|
|
157550
|
+
if (parent === dir) break;
|
|
157551
|
+
dir = parent;
|
|
157552
|
+
}
|
|
157553
|
+
return null;
|
|
157554
|
+
}
|
|
157440
157555
|
function containerHomeHostDir(workRoot) {
|
|
157441
157556
|
const dir = path7.join(resolveWorkRoot(workRoot), "container-home");
|
|
157442
157557
|
fs7.mkdirSync(dir, { recursive: true });
|
|
@@ -157525,10 +157640,13 @@ async function maybeContainerize(params) {
|
|
|
157525
157640
|
return [];
|
|
157526
157641
|
}
|
|
157527
157642
|
}))];
|
|
157643
|
+
const binHostPath = resolveBinHostPath(params.bin);
|
|
157644
|
+
const binPackageDir = resolveBinPackageDir(binHostPath);
|
|
157528
157645
|
const plan = wrapForContainer({
|
|
157529
|
-
binHostPath
|
|
157646
|
+
binHostPath,
|
|
157530
157647
|
binName: path7.basename(params.bin),
|
|
157531
157648
|
// realpath 前的名字("claude")——argv[0] 语义要保住
|
|
157649
|
+
...binPackageDir ? { binPackageDir } : {},
|
|
157532
157650
|
args: params.args,
|
|
157533
157651
|
cwd: params.cwd,
|
|
157534
157652
|
env,
|
|
@@ -159291,6 +159409,7 @@ var init_codex = __esm({
|
|
|
159291
159409
|
"../adapters/src/codex/index.ts"() {
|
|
159292
159410
|
"use strict";
|
|
159293
159411
|
import_node_child_process9 = require("node:child_process");
|
|
159412
|
+
init_container_runtime();
|
|
159294
159413
|
import_node_crypto15 = require("node:crypto");
|
|
159295
159414
|
fs10 = __toESM(require("node:fs"), 1);
|
|
159296
159415
|
os5 = __toESM(require("node:os"), 1);
|
|
@@ -159336,9 +159455,6 @@ var init_codex = __esm({
|
|
|
159336
159455
|
*/
|
|
159337
159456
|
capabilities = { appendInput: false };
|
|
159338
159457
|
async spawn(job) {
|
|
159339
|
-
if (job.executionEnv) {
|
|
159340
|
-
throw new Error("executionEnv\uFF1Acodex runtime \u5C1A\u672A\u652F\u6301\u5BB9\u5668\u5316\u6267\u884C\uFF08ADR-0147\uFF09\uFF0C\u8BF7\u6539\u6D3E claude-code \u5458\u5DE5\u6216\u53BB\u6389\u8BE5\u8282\u70B9\u7684 executionEnv \u58F0\u660E");
|
|
159341
|
-
}
|
|
159342
159458
|
const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
|
|
159343
159459
|
const id = `codex-${slug6}-${(0, import_node_crypto15.randomUUID)().slice(0, 8)}`;
|
|
159344
159460
|
const startedAtMs = Date.now();
|
|
@@ -159398,7 +159514,21 @@ var init_codex = __esm({
|
|
|
159398
159514
|
),
|
|
159399
159515
|
promptViaStdin ? "-" : prompt
|
|
159400
159516
|
];
|
|
159401
|
-
const
|
|
159517
|
+
const containerized = await maybeContainerize({
|
|
159518
|
+
bin: this.opts.codexBin ?? "codex",
|
|
159519
|
+
args,
|
|
159520
|
+
cwd: dir,
|
|
159521
|
+
job,
|
|
159522
|
+
...this.opts.workRoot !== void 0 ? { workRoot: this.opts.workRoot } : {},
|
|
159523
|
+
...this.opts.env !== void 0 ? { extraEnvEarly: this.opts.env } : {}
|
|
159524
|
+
});
|
|
159525
|
+
const child = containerized ? (0, import_node_child_process9.spawn)(containerized.bin, containerized.args, {
|
|
159526
|
+
cwd: dir,
|
|
159527
|
+
env: scrubLeakyEnv(process.env),
|
|
159528
|
+
// stdin 的开合口径与宿主分支一致:codex 只在走 stdin 时才该收到管道(见下方长注释)。
|
|
159529
|
+
// 容器包装本身带 `-i`,podman 会把这条管道透传给容器内进程。
|
|
159530
|
+
stdio: [promptViaStdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
159531
|
+
}) : (0, import_node_child_process9.spawn)(this.opts.codexBin ?? "codex", args, {
|
|
159402
159532
|
cwd: dir,
|
|
159403
159533
|
env: {
|
|
159404
159534
|
...scrubLeakyEnv(process.env),
|
|
@@ -159513,6 +159643,7 @@ var init_codex = __esm({
|
|
|
159513
159643
|
};
|
|
159514
159644
|
const timer = setTimeout(() => {
|
|
159515
159645
|
killReason ??= "timeout";
|
|
159646
|
+
if (containerized) killContainerBestEffort(containerized.containerName, containerized.mode);
|
|
159516
159647
|
child.kill("SIGKILL");
|
|
159517
159648
|
}, job.limits.wallClockMs);
|
|
159518
159649
|
child.on("error", () => {
|
|
@@ -159540,13 +159671,16 @@ var init_codex = __esm({
|
|
|
159540
159671
|
...telemetry.usage ? { usage: telemetry.usage } : {},
|
|
159541
159672
|
...model ? { model } : {},
|
|
159542
159673
|
...runtimeSessionId ? { runtimeSessionId } : {},
|
|
159543
|
-
...errorMessage ? { errorMessage } : {}
|
|
159674
|
+
...errorMessage ? { errorMessage } : {},
|
|
159675
|
+
// 跑在哪个镜像上要落账:同一题换了镜像,分数就不可比(ADR-0147 M5)
|
|
159676
|
+
...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
|
|
159544
159677
|
});
|
|
159545
159678
|
});
|
|
159546
159679
|
return {
|
|
159547
159680
|
id,
|
|
159548
159681
|
async kill() {
|
|
159549
159682
|
killReason ??= "cancelled";
|
|
159683
|
+
if (containerized) killContainerBestEffort(containerized.containerName, containerized.mode);
|
|
159550
159684
|
child.kill("SIGKILL");
|
|
159551
159685
|
},
|
|
159552
159686
|
onExit(cb) {
|
|
@@ -163799,14 +163933,36 @@ async function startOasisServer(opts) {
|
|
|
163799
163933
|
...currentCompanyId !== void 0 ? { companyId: currentCompanyId } : {}
|
|
163800
163934
|
}
|
|
163801
163935
|
);
|
|
163802
|
-
if (tokenClaims?.dispatchId && opts.onDispatchCommandCompleted) {
|
|
163936
|
+
if (tokenClaims?.dispatchId && opts.onDispatchCommandCompleted && !READ_ONLY_COMMANDS.has(body.command)) {
|
|
163803
163937
|
try {
|
|
163938
|
+
const revision = body.command === "propose" || body.command === "integrate" ? result?.data : void 0;
|
|
163939
|
+
const deliveredArtifactId = typeof revision?.artifactId === "string" ? revision.artifactId : tokenClaims.artifactId;
|
|
163940
|
+
const listArg = (key) => {
|
|
163941
|
+
const raw = body.args?.[key];
|
|
163942
|
+
if (!Array.isArray(raw)) return void 0;
|
|
163943
|
+
const items = raw.filter((x2) => typeof x2 === "string" && x2.trim() !== "");
|
|
163944
|
+
return items.length > 0 ? items : void 0;
|
|
163945
|
+
};
|
|
163946
|
+
const delivered = revision && typeof revision.id === "string" && typeof revision.reason === "string" && revision.reason.trim() && deliveredArtifactId !== void 0 ? {
|
|
163947
|
+
summary: revision.reason,
|
|
163948
|
+
refs: [{
|
|
163949
|
+
kind: "artifact",
|
|
163950
|
+
id: deliveredArtifactId,
|
|
163951
|
+
version: revision.id,
|
|
163952
|
+
...engine.kernel.model.artifacts.get(deliveredArtifactId)?.title ? { label: engine.kernel.model.artifacts.get(deliveredArtifactId).title } : {}
|
|
163953
|
+
}],
|
|
163954
|
+
...listArg("unresolvedIssues") ? { unresolvedIssues: listArg("unresolvedIssues") } : {},
|
|
163955
|
+
...listArg("risks") ? { risks: listArg("risks") } : {},
|
|
163956
|
+
...listArg("nextActions") ? { nextActions: listArg("nextActions") } : {},
|
|
163957
|
+
...listArg("verificationGaps") ? { verificationGaps: listArg("verificationGaps") } : {}
|
|
163958
|
+
} : void 0;
|
|
163804
163959
|
await opts.onDispatchCommandCompleted({
|
|
163805
163960
|
dispatchId: tokenClaims.dispatchId,
|
|
163806
163961
|
command: body.command,
|
|
163807
163962
|
...tokenClaims.artifactId !== void 0 ? { artifactId: tokenClaims.artifactId } : {},
|
|
163808
163963
|
durableRefs: tokenClaims.artifactId !== void 0 ? [{ kind: "artifact", id: tokenClaims.artifactId, label: body.command }] : [],
|
|
163809
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
163964
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
163965
|
+
...delivered ? { delivered } : {}
|
|
163810
163966
|
});
|
|
163811
163967
|
} catch (error2) {
|
|
163812
163968
|
console.error(`[execution-continuity] important-output checkpoint failed for ${tokenClaims.dispatchId}:`, error2);
|
|
@@ -165650,7 +165806,7 @@ async function startOasisServer(opts) {
|
|
|
165650
165806
|
await new Promise((resolve10) => httpServer.listen(opts.port ?? 0, "0.0.0.0", resolve10));
|
|
165651
165807
|
const address = httpServer.address();
|
|
165652
165808
|
if (address === null || typeof address === "string") throw new Error("listen failed");
|
|
165653
|
-
const baseUrl = `http://
|
|
165809
|
+
const baseUrl = `http://127.0.0.1:${address.port}`;
|
|
165654
165810
|
if (channelWsSupervisor) void channelWsSupervisor.start();
|
|
165655
165811
|
return {
|
|
165656
165812
|
baseUrl,
|
|
@@ -165662,7 +165818,7 @@ async function startOasisServer(opts) {
|
|
|
165662
165818
|
})
|
|
165663
165819
|
};
|
|
165664
165820
|
}
|
|
165665
|
-
var http, githubAppPending, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, STREAMING_PATHS, WILL_VERBS, GOVERNANCE_COMMANDS, isAgent, isHuman;
|
|
165821
|
+
var http, githubAppPending, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, STREAMING_PATHS, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
|
|
165666
165822
|
var init_server3 = __esm({
|
|
165667
165823
|
"../server/src/server.ts"() {
|
|
165668
165824
|
"use strict";
|
|
@@ -165702,6 +165858,21 @@ var init_server3 = __esm({
|
|
|
165702
165858
|
STREAMING_PATHS = ["/api/events", "/api/chat", "/api/trajectory/events", "/api/intervention/wait"];
|
|
165703
165859
|
WILL_VERBS = /* @__PURE__ */ new Set(["annotate", "reply", "resolve", "review", "merge", "escalate", "hold", "release"]);
|
|
165704
165860
|
GOVERNANCE_COMMANDS = /* @__PURE__ */ new Set(["seal", "assign", "promote", "forceConclude", "unlink", "gc", "requestChange", "resolveEscalation"]);
|
|
165861
|
+
READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
|
|
165862
|
+
"ls",
|
|
165863
|
+
"show",
|
|
165864
|
+
"timeline",
|
|
165865
|
+
"content",
|
|
165866
|
+
"queue",
|
|
165867
|
+
"stale",
|
|
165868
|
+
"blocked",
|
|
165869
|
+
"graph",
|
|
165870
|
+
"log",
|
|
165871
|
+
"working",
|
|
165872
|
+
"proposed",
|
|
165873
|
+
"rejected",
|
|
165874
|
+
"superseded"
|
|
165875
|
+
]);
|
|
165705
165876
|
isAgent = (actor) => actor.startsWith("actor:agent:");
|
|
165706
165877
|
isHuman = (actor) => actor.startsWith("actor:human:");
|
|
165707
165878
|
}
|
|
@@ -166416,9 +166587,25 @@ function systemHandoffFor(run, checkpoint = null, effects = []) {
|
|
|
166416
166587
|
createdAt: run.endedAt ?? run.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
166417
166588
|
};
|
|
166418
166589
|
}
|
|
166590
|
+
function mergeSystemHandoff(existing, reconstructed) {
|
|
166591
|
+
if (!existing) return reconstructed;
|
|
166592
|
+
const authored = (text) => text.trim() !== "" && !text.startsWith("[system_reconstructed]");
|
|
166593
|
+
const keepList = (current, next) => current.length > 0 ? structuredClone(current) : structuredClone(next);
|
|
166594
|
+
return {
|
|
166595
|
+
...reconstructed,
|
|
166596
|
+
// outcome 描述这趟 Attempt 的命运(交稿后仍可能失败/超时)→ 以终态为准。
|
|
166597
|
+
summary: authored(existing.summary) ? existing.summary : reconstructed.summary,
|
|
166598
|
+
changedOutputs: keepList(existing.changedOutputs, reconstructed.changedOutputs),
|
|
166599
|
+
unresolvedIssues: keepList(existing.unresolvedIssues, reconstructed.unresolvedIssues),
|
|
166600
|
+
risks: keepList(existing.risks, reconstructed.risks),
|
|
166601
|
+
nextActions: keepList(existing.nextActions, reconstructed.nextActions),
|
|
166602
|
+
verificationGaps: keepList(existing.verificationGaps, reconstructed.verificationGaps),
|
|
166603
|
+
userHandoff: existing.userHandoff ?? reconstructed.userHandoff,
|
|
166604
|
+
createdAt: existing.createdAt
|
|
166605
|
+
};
|
|
166606
|
+
}
|
|
166419
166607
|
function validateHandoffInput(input, status) {
|
|
166420
166608
|
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
166609
|
if (isTerminalStatus(status) && input.outcome !== handoffOutcomeOf(status)) {
|
|
166423
166610
|
throw new Error(`handoff outcome ${input.outcome} does not match attempt status ${status}`);
|
|
166424
166611
|
}
|
|
@@ -166516,7 +166703,7 @@ var init_memory_trace_store = __esm({
|
|
|
166516
166703
|
const existingHandoff = this.handoffs.get(id);
|
|
166517
166704
|
const checkpoint = (this.checkpoints.get(id) ?? []).filter((item) => item.trigger !== "attempt_terminal").at(-1) ?? null;
|
|
166518
166705
|
const effects = [...this.externalEffects.values()].filter((effect) => effect.attemptId === id);
|
|
166519
|
-
const handoff = existingHandoff?.producedBy === "agent" ? existingHandoff : systemHandoffFor(merged, checkpoint, effects);
|
|
166706
|
+
const handoff = existingHandoff?.producedBy === "agent" ? existingHandoff : mergeSystemHandoff(existingHandoff ?? null, systemHandoffFor(merged, checkpoint, effects));
|
|
166520
166707
|
this.handoffs.set(id, handoff);
|
|
166521
166708
|
merged.handoffId = handoff.handoffId;
|
|
166522
166709
|
}
|
|
@@ -171891,21 +172078,38 @@ var init_sink = __esm({
|
|
|
171891
172078
|
};
|
|
171892
172079
|
}
|
|
171893
172080
|
checkpointInput(st, previous, trigger, createdAt) {
|
|
172081
|
+
const durableRefs = structuredClone(st.checkpointState.durableRefs ?? previous?.durableRefs ?? []);
|
|
171894
172082
|
return {
|
|
171895
172083
|
attemptId: st.runId,
|
|
171896
172084
|
pendingSteps: [...st.checkpointState.pendingSteps ?? previous?.pendingSteps ?? []],
|
|
171897
|
-
durableRefs
|
|
172085
|
+
durableRefs,
|
|
171898
172086
|
completedStepIds: [...st.checkpointState.completedStepIds ?? previous?.completedStepIds ?? []],
|
|
171899
|
-
|
|
172087
|
+
// recoverability 只有 Agent 自己说得准,但「一条持久化产出都没有」与「已经落了东西」这两种局面
|
|
172088
|
+
// 系统分得清:有 durableRefs 就至少是 partial(下一趟能接着用这些产出),别一律记成 none——
|
|
172089
|
+
// 那会让恢复包看起来「什么都没留下」,与账本里的 refs 自相矛盾。
|
|
172090
|
+
recoverability: st.checkpointState.recoverability ?? previous?.recoverability ?? (durableRefs.length > 0 ? "partial" : "none"),
|
|
171900
172091
|
trigger,
|
|
171901
172092
|
createdAt
|
|
171902
172093
|
};
|
|
171903
172094
|
}
|
|
172095
|
+
/**
|
|
172096
|
+
* 两条 checkpoint 的内容是否一字不差(trigger/时间不算内容)——用于跳过无意义的重复写。
|
|
172097
|
+
*
|
|
172098
|
+
* refs 必须逐字段比,**不能 JSON.stringify 整个对象**:写进 PG 的 jsonb 读回来键序是重排过的
|
|
172099
|
+
* (`{kind,id,version,label}` 回来是 `{id,kind,label,version}`),按字符串比会把「完全一样」判成
|
|
172100
|
+
* 「变了」,去重直接失效——线上的表现就是每个工具结果照样灌一行。
|
|
172101
|
+
*/
|
|
172102
|
+
sameCheckpointContent(input, previous) {
|
|
172103
|
+
if (!previous) return false;
|
|
172104
|
+
const refKeys = (refs) => refs.map((ref2) => `${ref2.kind}\0${ref2.id}\0${ref2.version ?? ""}\0${ref2.label ?? ""}`).join("");
|
|
172105
|
+
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;
|
|
172106
|
+
}
|
|
171904
172107
|
clearObservedCheckpointState(st) {
|
|
171905
172108
|
st.checkpointState = {};
|
|
171906
172109
|
}
|
|
171907
172110
|
async appendSystemCheckpoint(st, trigger, createdAt, appendDurableRefs = []) {
|
|
171908
172111
|
if (!this.canCheckpoint()) return;
|
|
172112
|
+
if (trigger === "tool_completed" && Object.keys(st.checkpointState).length === 0) return;
|
|
171909
172113
|
const previous = await this.latestCheckpoint(st);
|
|
171910
172114
|
const input = this.checkpointInput(st, previous, trigger, createdAt);
|
|
171911
172115
|
if (appendDurableRefs.length > 0) {
|
|
@@ -171915,6 +172119,14 @@ var init_sink = __esm({
|
|
|
171915
172119
|
}
|
|
171916
172120
|
input.durableRefs = [...refs.values()];
|
|
171917
172121
|
}
|
|
172122
|
+
if (input.durableRefs.length > 0 && st.checkpointState.recoverability === void 0 && previous?.recoverability === void 0) {
|
|
172123
|
+
input.recoverability = "partial";
|
|
172124
|
+
}
|
|
172125
|
+
const observedTrigger = trigger === "tool_completed" || trigger === "important_output";
|
|
172126
|
+
if (observedTrigger && this.sameCheckpointContent(input, previous)) {
|
|
172127
|
+
this.clearObservedCheckpointState(st);
|
|
172128
|
+
return;
|
|
172129
|
+
}
|
|
171918
172130
|
await this.store.appendCheckpoint(input);
|
|
171919
172131
|
this.clearObservedCheckpointState(st);
|
|
171920
172132
|
this.scheduleCheckpointTimer(st);
|
|
@@ -171951,8 +172163,16 @@ var init_sink = __esm({
|
|
|
171951
172163
|
if (!st || st.ending || !this.canCheckpoint()) return;
|
|
171952
172164
|
this.enqueue(sessionId, async (current) => {
|
|
171953
172165
|
if (signal.trigger === "important_output") {
|
|
171954
|
-
const { durableRefs = [], ...state } = signal;
|
|
172166
|
+
const { durableRefs = [], completedStepIds = [], ...state } = signal;
|
|
171955
172167
|
this.mergeCheckpointState(current, state);
|
|
172168
|
+
if (completedStepIds.length > 0) {
|
|
172169
|
+
const previous = await this.latestCheckpoint(current);
|
|
172170
|
+
const merged = /* @__PURE__ */ new Set([
|
|
172171
|
+
...current.checkpointState.completedStepIds ?? previous?.completedStepIds ?? [],
|
|
172172
|
+
...completedStepIds
|
|
172173
|
+
]);
|
|
172174
|
+
current.checkpointState = { ...current.checkpointState, completedStepIds: [...merged] };
|
|
172175
|
+
}
|
|
171956
172176
|
await this.appendSystemCheckpoint(
|
|
171957
172177
|
current,
|
|
171958
172178
|
signal.trigger,
|
|
@@ -180335,7 +180555,7 @@ function collectWaitingRows(snapshots2, me) {
|
|
|
180335
180555
|
const latestReply = /* @__PURE__ */ new Map();
|
|
180336
180556
|
for (const w2 of snap.works) {
|
|
180337
180557
|
if (w2.assigneeActorId !== me || cancelled.has(w2.nodeId)) continue;
|
|
180338
|
-
latestBy(w2
|
|
180558
|
+
latestBy(isReplyWork(w2) ? latestReply : latestMain, w2);
|
|
180339
180559
|
}
|
|
180340
180560
|
for (const w2 of [...latestMain.values(), ...latestReply.values()]) {
|
|
180341
180561
|
const st = workState(w2, hasOutput(w2, outputCountOf(w2.id)));
|
|
@@ -182982,7 +183202,9 @@ function renderCoordinatorView(view) {
|
|
|
182982
183202
|
if (node.handoffProducer === "agent") {
|
|
182983
183203
|
lines.push(`- Agent handoff: ${node.summary || "No summary"}`);
|
|
182984
183204
|
} else if (node.handoffProducer === "system_reconstructed") {
|
|
182985
|
-
lines.push(
|
|
183205
|
+
lines.push(
|
|
183206
|
+
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}`
|
|
183207
|
+
);
|
|
182986
183208
|
}
|
|
182987
183209
|
for (const output of node.changedOutputs) {
|
|
182988
183210
|
lines.push(` - Durable output: ${output.kind}:${output.id}${output.version ? `@${output.version}` : ""}`);
|
|
@@ -183005,6 +183227,11 @@ function renderCoordinatorView(view) {
|
|
|
183005
183227
|
return `${lines.join("\n")}
|
|
183006
183228
|
`;
|
|
183007
183229
|
}
|
|
183230
|
+
function shouldInjectResumePack(pack) {
|
|
183231
|
+
const status = pack?.previousAttempt?.status;
|
|
183232
|
+
if (!status) return false;
|
|
183233
|
+
return status !== "succeeded" && status !== "no-output";
|
|
183234
|
+
}
|
|
183008
183235
|
function renderContinuityProtocol() {
|
|
183009
183236
|
return `# Execution continuity protocol
|
|
183010
183237
|
|
|
@@ -183016,8 +183243,13 @@ authoritative systems. Record only durable facts and stable references here.
|
|
|
183016
183243
|
Run \`oasis continuity-checkpoint --trigger plan_completed\` after planning and
|
|
183017
183244
|
\`oasis continuity-checkpoint --trigger stage_completed\` after a key stage so the system knows
|
|
183018
183245
|
what remains, stable completed-step ids, recoverability, and only references that another agent can open.
|
|
183019
|
-
|
|
183020
|
-
|
|
183246
|
+
Your explicit checkpoints are the only source of pendingSteps, completedStepIds and recoverability.
|
|
183247
|
+
The system appends checkpoints on its own after each successful tool result, after a durable Oasis
|
|
183248
|
+
command succeeds (carrying a coarse reference to the artifact you wrote), after a captured connector
|
|
183249
|
+
external effect settles, and once immediately before terminal status \u2014 but every one of those merely
|
|
183250
|
+
re-persists what you already declared plus references it can see; none of them can infer what remains
|
|
183251
|
+
to be done. If you never checkpoint, a later attempt on this node has nothing to resume from. Do not
|
|
183252
|
+
emit timer-only checkpoints.
|
|
183021
183253
|
|
|
183022
183254
|
## External effects
|
|
183023
183255
|
|
|
@@ -183028,30 +183260,35 @@ succeeds, use \`oasis continuity-effect\` with a stable key that the same node w
|
|
|
183028
183260
|
|
|
183029
183261
|
## Handoff
|
|
183030
183262
|
|
|
183031
|
-
Before terminal exit, run \`oasis continuity-handoff\` with a
|
|
183032
|
-
|
|
183033
|
-
|
|
183034
|
-
|
|
183035
|
-
|
|
183036
|
-
|
|
183037
|
-
|
|
183038
|
-
|
|
183039
|
-
|
|
183040
|
-
|
|
183041
|
-
|
|
183263
|
+
Before terminal exit, run \`oasis continuity-handoff\` with a technical summary, changed durable
|
|
183264
|
+
outputs, unresolved issues, risks, next actions, and verification gaps. Submitting work already
|
|
183265
|
+
records the delivered revision and your \`--reason\` as this node's handoff summary, so the value you
|
|
183266
|
+
add here is what the system cannot observe: what is still unresolved, what is risky, what the next
|
|
183267
|
+
agent should do, and what you could not verify. You must also provide \`--user-summary\` as short
|
|
183268
|
+
business-facing language for the people receiving the work. Say what you completed, what result you
|
|
183269
|
+
formed, and what downstream work can use; if not delivered, say only why and what must happen next.
|
|
183270
|
+
Use \`--user-next-step\` when downstream needs an instruction. List only openable business results in
|
|
183271
|
+
\`--user-deliverables\`; keep technical refs in \`--outputs\`, and never list conclude, gap,
|
|
183272
|
+
checkpoint, or op as a user deliverable. Do not use a generic success/output-count template or
|
|
183273
|
+
mention attempts, checkpoints, runtimes, recovery, infrastructure paths, or internal diagnostics
|
|
183274
|
+
there. Only an explicit non-success outcome holds downstream work back; a missing handoff does not,
|
|
183275
|
+
so never claim non-delivery merely because you ran out of time to write one.
|
|
183042
183276
|
`;
|
|
183043
183277
|
}
|
|
183044
183278
|
function renderBusinessHandoffProtocol() {
|
|
183045
183279
|
return `# Business handoff protocol
|
|
183046
183280
|
|
|
183047
|
-
Before terminal exit, run \`oasis continuity-handoff\`.
|
|
183048
|
-
|
|
183049
|
-
|
|
183050
|
-
|
|
183051
|
-
|
|
183052
|
-
|
|
183053
|
-
|
|
183054
|
-
|
|
183281
|
+
Before terminal exit, run \`oasis continuity-handoff\`. Submitting work already records the delivered
|
|
183282
|
+
revision and your \`--reason\` as this node's handoff summary; this command adds what the system
|
|
183283
|
+
cannot observe \u2014 unresolved issues, risks, next actions, verification gaps \u2014 plus a business-facing
|
|
183284
|
+
\`--user-summary\`. A successful handoff must use outcome \`succeeded\`, include a non-empty
|
|
183285
|
+
\`--user-summary\` saying what was done, what result was formed, and what downstream can use, and may
|
|
183286
|
+
have zero files. If the work genuinely cannot be delivered, submit a non-success outcome with a user
|
|
183287
|
+
summary explaining why and \`--user-next-step\`: that is the one signal which holds downstream work
|
|
183288
|
+
back, so never use it just because you ran out of time to write a handoff. Omitting this command does
|
|
183289
|
+
not block downstream. List only openable business results in \`--user-deliverables\`. Never use a
|
|
183290
|
+
generic success/output-count template or expose attempts, checkpoints, runtimes, recovery,
|
|
183291
|
+
infrastructure paths, or internal diagnostics in userHandoff.
|
|
183055
183292
|
`;
|
|
183056
183293
|
}
|
|
183057
183294
|
function emptyExternalEffectProjection() {
|
|
@@ -183225,7 +183462,6 @@ var init_service7 = __esm({
|
|
|
183225
183462
|
async handoff(input) {
|
|
183226
183463
|
if (!HANDOFF_OUTCOMES2.has(input.outcome)) throw new Error(`invalid handoff outcome: ${input.outcome}`);
|
|
183227
183464
|
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
183465
|
if (input.producedBy === "agent" && !input.userHandoff) {
|
|
183230
183466
|
throw new Error("agent-authored handoff requires userHandoff");
|
|
183231
183467
|
}
|
|
@@ -183288,7 +183524,7 @@ var init_service7 = __esm({
|
|
|
183288
183524
|
}
|
|
183289
183525
|
async recoveryView(attemptId, companyId) {
|
|
183290
183526
|
const attempt = await this.store.getAttempt(attemptId);
|
|
183291
|
-
if (!attempt?.workOrderId || !attempt.nodeId || attempt.continuityMode
|
|
183527
|
+
if (!attempt?.workOrderId || !attempt.nodeId || !attempt.continuityMode || attempt.continuityMode === "off") return null;
|
|
183292
183528
|
const recoverySnapshot = this.readRecoverySnapshot(attempt);
|
|
183293
183529
|
if (!recoverySnapshot) return null;
|
|
183294
183530
|
const graph = await this.resolveGraph(attempt.workOrderId, companyId);
|
|
@@ -183322,7 +183558,7 @@ var init_service7 = __esm({
|
|
|
183322
183558
|
return candidate ? { workOrderId: candidate.workOrderId, nodeId: candidate.nodeId, attemptId: candidate.attemptId } : null;
|
|
183323
183559
|
}
|
|
183324
183560
|
readRecoverySnapshot(attempt) {
|
|
183325
|
-
if (!attempt.nodeId || attempt.continuityMode
|
|
183561
|
+
if (!attempt.nodeId || attempt.continuityMode === "off" || attempt.continuityMode === null) return null;
|
|
183326
183562
|
const metadata = attempt.metadata;
|
|
183327
183563
|
const manifest = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata["bundleManifest"] : null;
|
|
183328
183564
|
const expectedHash = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest["execution/RESUME.md"] : null;
|
|
@@ -183475,11 +183711,20 @@ var init_handoff_work_bridge = __esm({
|
|
|
183475
183711
|
|
|
183476
183712
|
// ../server/src/domains/execution-continuity/upstream-handoff-input.ts
|
|
183477
183713
|
function renderRefs(refs) {
|
|
183478
|
-
return refs.map(
|
|
183479
|
-
|
|
183480
|
-
|
|
183714
|
+
return refs.map((ref2) => {
|
|
183715
|
+
const label = ref2.id.startsWith(`${ref2.kind}:`) ? ref2.id : `${ref2.kind}:${ref2.id}`;
|
|
183716
|
+
return `- ${label}${ref2.version ? `@${ref2.version}` : ""}${ref2.label ? ` \u2014 ${ref2.label}` : ""}`;
|
|
183717
|
+
});
|
|
183718
|
+
}
|
|
183719
|
+
function bounded(text) {
|
|
183720
|
+
const t = text.trim();
|
|
183721
|
+
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`;
|
|
183722
|
+
}
|
|
183723
|
+
function isExplicitNone(values) {
|
|
183724
|
+
return values.length === 1 && NONE_TOKENS.has((values[0] ?? "").trim().toLowerCase());
|
|
183481
183725
|
}
|
|
183482
183726
|
function renderList(title, values) {
|
|
183727
|
+
if (isExplicitNone(values)) return [`## ${title}`, "- \u6267\u884C\u4EBA\u786E\u8BA4\uFF1A\u65E0", ""];
|
|
183483
183728
|
return [
|
|
183484
183729
|
`## ${title}`,
|
|
183485
183730
|
...values.length > 0 ? values.map((value) => `- ${value}`) : ["- None"],
|
|
@@ -183504,8 +183749,9 @@ function renderUpstreamNodeHandoff(handoff, source) {
|
|
|
183504
183749
|
`Created at: ${handoff.createdAt}`,
|
|
183505
183750
|
"",
|
|
183506
183751
|
"## Summary",
|
|
183507
|
-
handoff.summary,
|
|
183752
|
+
bounded(handoff.summary),
|
|
183508
183753
|
"",
|
|
183754
|
+
...source.conclusion ? ["## Delivery note (upstream conclusion)", bounded(source.conclusion), ""] : [],
|
|
183509
183755
|
"## Changed durable outputs",
|
|
183510
183756
|
...handoff.changedOutputs.length > 0 ? renderRefs(handoff.changedOutputs) : ["- None"],
|
|
183511
183757
|
"",
|
|
@@ -183517,49 +183763,117 @@ function renderUpstreamNodeHandoff(handoff, source) {
|
|
|
183517
183763
|
""
|
|
183518
183764
|
].join("\n");
|
|
183519
183765
|
}
|
|
183766
|
+
function renderDeliveredWorkHandoff(source) {
|
|
183767
|
+
const authored = [
|
|
183768
|
+
...source.unresolvedIssues ?? [],
|
|
183769
|
+
...source.risks ?? [],
|
|
183770
|
+
...source.nextActions ?? [],
|
|
183771
|
+
...source.verificationGaps ?? []
|
|
183772
|
+
];
|
|
183773
|
+
return [
|
|
183774
|
+
"# Upstream NodeHandoff",
|
|
183775
|
+
"",
|
|
183776
|
+
`Upstream node: ${source.upstreamNodeId}${source.upstreamTitle ? `\uFF08${source.upstreamTitle}\uFF09` : ""}`,
|
|
183777
|
+
`Pinned work: ${source.pinnedWorkId}`,
|
|
183778
|
+
"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",
|
|
183779
|
+
"",
|
|
183780
|
+
"## Summary",
|
|
183781
|
+
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",
|
|
183782
|
+
"",
|
|
183783
|
+
"## Changed durable outputs",
|
|
183784
|
+
...source.changedOutputs.length > 0 ? renderRefs(source.changedOutputs) : ["- None"],
|
|
183785
|
+
"",
|
|
183786
|
+
...renderList("Unresolved issues", [...source.unresolvedIssues ?? []]),
|
|
183787
|
+
...renderList("Risks", [...source.risks ?? []]),
|
|
183788
|
+
...renderList("Next actions", [...source.nextActions ?? []]),
|
|
183789
|
+
...renderList("Verification gaps", [...source.verificationGaps ?? []]),
|
|
183790
|
+
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",
|
|
183791
|
+
"\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",
|
|
183792
|
+
""
|
|
183793
|
+
].join("\n");
|
|
183794
|
+
}
|
|
183520
183795
|
function createUpstreamHandoffInputResolver(opts) {
|
|
183796
|
+
const warn = opts.warn ?? ((message) => console.warn(`[upstream-handoff] ${message}`));
|
|
183521
183797
|
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"}`
|
|
183798
|
+
try {
|
|
183799
|
+
const snapshot = await opts.loadWorkorder(input.workOrderId);
|
|
183800
|
+
if (!snapshot) {
|
|
183801
|
+
warn(`\u5DE5\u5355\u5FEB\u7167\u8BFB\u4E0D\u5230\uFF0C\u8DF3\u8FC7\u4E0A\u6E38\u4EA4\u63A5\u6CE8\u5165\uFF1A${input.workOrderId}`);
|
|
183802
|
+
return null;
|
|
183803
|
+
}
|
|
183804
|
+
const edge = snapshot.edges.find(
|
|
183805
|
+
(candidate) => candidate.fromNodeId === input.upstreamNodeId && candidate.toNodeId === input.downstreamNodeId
|
|
183533
183806
|
);
|
|
183534
|
-
|
|
183535
|
-
|
|
183536
|
-
(
|
|
183537
|
-
|
|
183538
|
-
|
|
183539
|
-
|
|
183540
|
-
|
|
183541
|
-
|
|
183542
|
-
|
|
183807
|
+
const downstream = snapshot.nodes.find((node) => node.id === input.downstreamNodeId);
|
|
183808
|
+
const businessEdge = edge?.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
|
|
183809
|
+
if (!businessEdge) return null;
|
|
183810
|
+
if (edge.pinnedWorkId !== input.pinnedWorkId) {
|
|
183811
|
+
warn(
|
|
183812
|
+
`pin \u6F02\u79FB\uFF0C\u8DF3\u8FC7\u6CE8\u5165 ${input.upstreamNodeId}\u2192${input.downstreamNodeId}\uFF1Abundle=${input.pinnedWorkId}\u3001edge=${edge.pinnedWorkId ?? "none"}`
|
|
183813
|
+
);
|
|
183814
|
+
return null;
|
|
183815
|
+
}
|
|
183816
|
+
const work = snapshot.works.find(
|
|
183817
|
+
(candidate) => candidate.id === input.pinnedWorkId && candidate.nodeId === input.upstreamNodeId
|
|
183818
|
+
);
|
|
183819
|
+
if (!work) {
|
|
183820
|
+
warn(`\u9489\u4F4F\u7684\u4E0A\u6E38 Work \u4E0D\u5728\u5FEB\u7167\u91CC\uFF0C\u8DF3\u8FC7\u6CE8\u5165\uFF1A${input.pinnedWorkId}`);
|
|
183821
|
+
return null;
|
|
183822
|
+
}
|
|
183823
|
+
if (typeof work.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) {
|
|
183824
|
+
return null;
|
|
183825
|
+
}
|
|
183826
|
+
const upstreamNode = snapshot.nodes.find((node) => node.id === input.upstreamNodeId);
|
|
183827
|
+
const conclusion = typeof work.conclusion === "string" ? work.conclusion : null;
|
|
183828
|
+
const changedOutputs = [{
|
|
183829
|
+
kind: "artifact",
|
|
183830
|
+
id: input.upstreamNodeId,
|
|
183831
|
+
version: input.pinnedWorkId,
|
|
183832
|
+
...upstreamNode?.title ? { label: upstreamNode.title } : {}
|
|
183833
|
+
}];
|
|
183834
|
+
const deliveredRow = typeof work.sessionRef === "string" && work.sessionRef ? await opts.getHandoff(work.sessionRef).catch(() => null) : null;
|
|
183835
|
+
const systemHandoff = () => renderDeliveredWorkHandoff({
|
|
183836
|
+
upstreamNodeId: input.upstreamNodeId,
|
|
183837
|
+
upstreamTitle: upstreamNode?.title,
|
|
183838
|
+
pinnedWorkId: input.pinnedWorkId,
|
|
183839
|
+
conclusion,
|
|
183840
|
+
changedOutputs: (deliveredRow?.changedOutputs.length ?? 0) > 0 ? deliveredRow.changedOutputs : changedOutputs,
|
|
183841
|
+
...deliveredRow?.unresolvedIssues.length ? { unresolvedIssues: deliveredRow.unresolvedIssues } : {},
|
|
183842
|
+
...deliveredRow?.risks.length ? { risks: deliveredRow.risks } : {},
|
|
183843
|
+
...deliveredRow?.nextActions.length ? { nextActions: deliveredRow.nextActions } : {},
|
|
183844
|
+
...deliveredRow?.verificationGaps.length ? { verificationGaps: deliveredRow.verificationGaps } : {}
|
|
183845
|
+
});
|
|
183846
|
+
if (!work.agentHandoffAttemptId || work.businessHandoffStatus !== "delivered") {
|
|
183847
|
+
return systemHandoff();
|
|
183848
|
+
}
|
|
183849
|
+
const handoff = await opts.getHandoff(work.agentHandoffAttemptId).catch((error2) => {
|
|
183850
|
+
warn(`\u8BFB Agent NodeHandoff \u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u7CFB\u7EDF\u751F\u6210\u7248\uFF1A${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
183851
|
+
return null;
|
|
183852
|
+
});
|
|
183853
|
+
if (!handoff || handoff.attemptId !== work.agentHandoffAttemptId || handoff.producedBy !== "agent" || handoff.outcome !== "succeeded" || handoff.userHandoff?.status !== "delivered") {
|
|
183854
|
+
warn(
|
|
183855
|
+
`Agent NodeHandoff \u4E0E\u6295\u5F71\u5BF9\u4E0D\u4E0A\uFF08work=${work.id}\u3001attempt=${work.agentHandoffAttemptId}\uFF09\uFF0C\u964D\u7EA7\u4E3A\u7CFB\u7EDF\u751F\u6210\u7248`
|
|
183856
|
+
);
|
|
183857
|
+
return systemHandoff();
|
|
183858
|
+
}
|
|
183859
|
+
return renderUpstreamNodeHandoff(handoff, {
|
|
183860
|
+
upstreamNodeId: input.upstreamNodeId,
|
|
183861
|
+
pinnedWorkId: input.pinnedWorkId,
|
|
183862
|
+
conclusion
|
|
183863
|
+
});
|
|
183864
|
+
} catch (error2) {
|
|
183865
|
+
warn(`\u4E0A\u6E38\u4EA4\u63A5\u89E3\u6790\u5F02\u5E38\uFF0C\u672C\u8F6E\u4E0D\u6CE8\u5165\uFF1A${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
183543
183866
|
return null;
|
|
183544
183867
|
}
|
|
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
183868
|
};
|
|
183558
183869
|
}
|
|
183870
|
+
var INJECTED_TEXT_LIMIT, NONE_TOKENS;
|
|
183559
183871
|
var init_upstream_handoff_input = __esm({
|
|
183560
183872
|
"../server/src/domains/execution-continuity/upstream-handoff-input.ts"() {
|
|
183561
183873
|
"use strict";
|
|
183562
183874
|
init_src3();
|
|
183875
|
+
INJECTED_TEXT_LIMIT = 4e3;
|
|
183876
|
+
NONE_TOKENS = /* @__PURE__ */ new Set(["none", "n/a", "na", "nil", "\u65E0", "\u6CA1\u6709", "\u6682\u65E0"]);
|
|
183563
183877
|
}
|
|
183564
183878
|
});
|
|
183565
183879
|
|
|
@@ -183580,6 +183894,51 @@ var init_node_conclusions = __esm({
|
|
|
183580
183894
|
}
|
|
183581
183895
|
});
|
|
183582
183896
|
|
|
183897
|
+
// ../server/src/domains/execution-continuity/delivered-handoff.ts
|
|
183898
|
+
function mergeRefs(primary, extra) {
|
|
183899
|
+
const out = [...primary];
|
|
183900
|
+
for (const ref2 of extra) {
|
|
183901
|
+
if (!out.some((item) => item.kind === ref2.kind && item.id === ref2.id && item.version === ref2.version)) {
|
|
183902
|
+
out.push(ref2);
|
|
183903
|
+
}
|
|
183904
|
+
}
|
|
183905
|
+
return out;
|
|
183906
|
+
}
|
|
183907
|
+
async function recordDeliveredOutputHandoff(deps, facts) {
|
|
183908
|
+
const warn = deps.warn ?? ((message) => console.warn(`[execution-continuity] ${message}`));
|
|
183909
|
+
if (!facts.summary.trim() || facts.refs.length === 0) return null;
|
|
183910
|
+
try {
|
|
183911
|
+
if (deps.getAttempt) {
|
|
183912
|
+
const attempt = await deps.getAttempt(facts.attemptId).catch(() => null);
|
|
183913
|
+
if (!attempt?.nodeId) return null;
|
|
183914
|
+
}
|
|
183915
|
+
const existing = await deps.getHandoff(facts.attemptId).catch(() => null);
|
|
183916
|
+
if (existing?.producedBy === "agent") return existing;
|
|
183917
|
+
return await deps.handoff({
|
|
183918
|
+
attemptId: facts.attemptId,
|
|
183919
|
+
producedBy: "system_reconstructed",
|
|
183920
|
+
outcome: "succeeded",
|
|
183921
|
+
summary: facts.summary,
|
|
183922
|
+
changedOutputs: mergeRefs(facts.refs, existing?.changedOutputs ?? []),
|
|
183923
|
+
// 本次带来的优先;没带就把已有的机器派生内容原样带回——本次只补「交了什么」,
|
|
183924
|
+
// 不该顺手抹掉终态触发器算出来的风险项。
|
|
183925
|
+
unresolvedIssues: [...facts.unresolvedIssues ?? existing?.unresolvedIssues ?? []],
|
|
183926
|
+
risks: [...facts.risks ?? existing?.risks ?? []],
|
|
183927
|
+
nextActions: [...facts.nextActions ?? existing?.nextActions ?? []],
|
|
183928
|
+
verificationGaps: [...facts.verificationGaps ?? existing?.verificationGaps ?? []],
|
|
183929
|
+
...facts.at ? { createdAt: facts.at } : {}
|
|
183930
|
+
});
|
|
183931
|
+
} catch (error2) {
|
|
183932
|
+
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)}`);
|
|
183933
|
+
return null;
|
|
183934
|
+
}
|
|
183935
|
+
}
|
|
183936
|
+
var init_delivered_handoff = __esm({
|
|
183937
|
+
"../server/src/domains/execution-continuity/delivered-handoff.ts"() {
|
|
183938
|
+
"use strict";
|
|
183939
|
+
}
|
|
183940
|
+
});
|
|
183941
|
+
|
|
183583
183942
|
// ../server/src/domains/execution-continuity/index.ts
|
|
183584
183943
|
function createExecutionContinuityDomain(opts) {
|
|
183585
183944
|
const service = new ExecutionContinuityService(opts);
|
|
@@ -183595,6 +183954,7 @@ var init_execution_continuity2 = __esm({
|
|
|
183595
183954
|
init_handoff_work_bridge();
|
|
183596
183955
|
init_upstream_handoff_input();
|
|
183597
183956
|
init_node_conclusions();
|
|
183957
|
+
init_delivered_handoff();
|
|
183598
183958
|
}
|
|
183599
183959
|
});
|
|
183600
183960
|
|
|
@@ -187121,13 +187481,24 @@ ${ctx.nodeFault}
|
|
|
187121
187481
|
}
|
|
187122
187482
|
}
|
|
187123
187483
|
const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto38.randomUUID)();
|
|
187484
|
+
const taskWithContext = coordinatorContext ? [
|
|
187485
|
+
task,
|
|
187486
|
+
``,
|
|
187487
|
+
`## \u5168\u5C40\u6267\u884C\u72B6\u6001\uFF08\u5148\u8BFB \`execution/COORDINATOR_VIEW.md\`\uFF09`,
|
|
187488
|
+
`\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`,
|
|
187489
|
+
`\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`,
|
|
187490
|
+
`\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`,
|
|
187491
|
+
`\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`,
|
|
187492
|
+
`\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`,
|
|
187493
|
+
``
|
|
187494
|
+
].join("\n") : task;
|
|
187124
187495
|
const job = {
|
|
187125
187496
|
actor: actorId,
|
|
187126
187497
|
actorToken: token,
|
|
187127
187498
|
artifactId,
|
|
187128
187499
|
bundle: {
|
|
187129
187500
|
files: {
|
|
187130
|
-
"TASK.md":
|
|
187501
|
+
"TASK.md": taskWithContext,
|
|
187131
187502
|
...coordinatorContext ? { "execution/COORDINATOR_VIEW.md": coordinatorContext } : {}
|
|
187132
187503
|
}
|
|
187133
187504
|
},
|
|
@@ -190284,7 +190655,7 @@ var init_postgres_trace = __esm({
|
|
|
190284
190655
|
part text,
|
|
190285
190656
|
produced_by text NOT NULL CHECK (produced_by IN ('agent','system_reconstructed')),
|
|
190286
190657
|
outcome text NOT NULL CHECK (outcome IN ('succeeded','no-output','failed','cancelled','timeout','orphaned')),
|
|
190287
|
-
summary text NOT NULL
|
|
190658
|
+
summary text NOT NULL,
|
|
190288
190659
|
changed_outputs jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
190289
190660
|
unresolved_issues jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
190290
190661
|
risks jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
@@ -190296,6 +190667,7 @@ var init_postgres_trace = __esm({
|
|
|
190296
190667
|
await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS job_key text NOT NULL DEFAULT ''`);
|
|
190297
190668
|
await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS part text`);
|
|
190298
190669
|
await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS user_handoff jsonb`);
|
|
190670
|
+
await pool.query(`ALTER TABLE "${s2}".node_handoffs DROP CONSTRAINT IF EXISTS node_handoffs_summary_check`);
|
|
190299
190671
|
await pool.query(`
|
|
190300
190672
|
UPDATE "${s2}".node_handoffs h
|
|
190301
190673
|
SET job_key=COALESCE(NULLIF(h.job_key, ''), r.job_key, 'attempt:' || h.attempt_id), part=r.part
|
|
@@ -190423,6 +190795,15 @@ var init_postgres_trace = __esm({
|
|
|
190423
190795
|
reconstructed_summary, NEW.output_refs, '[]'::jsonb, reconstructed_risks,
|
|
190424
190796
|
COALESCE(latest_pending, '[]'::jsonb), reconstructed_gaps, NULL,
|
|
190425
190797
|
COALESCE(NEW.ended_at, NEW.updated_at, NEW.started_at))
|
|
190798
|
+
-- \u2605 2026-08-17\uFF1A\u7EC8\u6001\u91CD\u5EFA\u4ECE\u300C\u6574\u884C\u8986\u76D6\u300D\u6539\u6210\u300C**\u53EA\u586B\u7A7A**\u300D\u3002
|
|
190799
|
+
--
|
|
190800
|
+
-- \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
|
|
190801
|
+
-- \u843D\u7684\u771F\u5B9E\u6458\u8981\uFF08work.conclusion\uFF09\u4E0E\u4EA4\u4ED8\u7248\u672C\uFF0C\u4F1A\u5728\u4F1A\u8BDD\u7EC8\u6001\u88AB\u6362\u6210
|
|
190802
|
+
-- "[system_reconstructed] attempt=\u2026 reason=\u2026" \u8FD9\u4E32\u673A\u5668\u8BDD\u2014\u2014\u5199\u4E86\u7B49\u4E8E\u6CA1\u5199\u3002
|
|
190803
|
+
--
|
|
190804
|
+
-- \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
|
|
190805
|
+
-- \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
|
|
190806
|
+
-- 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
190807
|
ON CONFLICT (attempt_id) DO UPDATE SET
|
|
190427
190808
|
produced_by=CASE
|
|
190428
190809
|
WHEN current_handoff.produced_by = 'agent'
|
|
@@ -190434,29 +190815,46 @@ var init_postgres_trace = __esm({
|
|
|
190434
190815
|
summary=CASE
|
|
190435
190816
|
WHEN current_handoff.produced_by = 'agent'
|
|
190436
190817
|
THEN current_handoff.summary
|
|
190818
|
+
WHEN COALESCE(current_handoff.summary, '') <> ''
|
|
190819
|
+
AND current_handoff.summary NOT LIKE '[system_reconstructed]%'
|
|
190820
|
+
THEN current_handoff.summary
|
|
190437
190821
|
ELSE EXCLUDED.summary
|
|
190438
190822
|
END,
|
|
190439
190823
|
changed_outputs=CASE
|
|
190440
190824
|
WHEN current_handoff.produced_by = 'agent'
|
|
190441
190825
|
THEN current_handoff.changed_outputs
|
|
190826
|
+
WHEN jsonb_array_length(COALESCE(current_handoff.changed_outputs, '[]'::jsonb)) > 0
|
|
190827
|
+
THEN current_handoff.changed_outputs
|
|
190442
190828
|
ELSE EXCLUDED.changed_outputs
|
|
190443
190829
|
END,
|
|
190444
190830
|
unresolved_issues=CASE
|
|
190445
190831
|
WHEN current_handoff.produced_by = 'agent'
|
|
190446
|
-
THEN current_handoff.unresolved_issues
|
|
190832
|
+
THEN current_handoff.unresolved_issues
|
|
190833
|
+
WHEN jsonb_array_length(COALESCE(current_handoff.unresolved_issues, '[]'::jsonb)) > 0
|
|
190834
|
+
THEN current_handoff.unresolved_issues
|
|
190835
|
+
ELSE EXCLUDED.unresolved_issues END,
|
|
190447
190836
|
risks=CASE
|
|
190448
190837
|
WHEN current_handoff.produced_by = 'agent'
|
|
190449
|
-
THEN current_handoff.risks
|
|
190838
|
+
THEN current_handoff.risks
|
|
190839
|
+
WHEN jsonb_array_length(COALESCE(current_handoff.risks, '[]'::jsonb)) > 0
|
|
190840
|
+
THEN current_handoff.risks
|
|
190841
|
+
ELSE EXCLUDED.risks END,
|
|
190450
190842
|
next_actions=CASE
|
|
190451
190843
|
WHEN current_handoff.produced_by = 'agent'
|
|
190452
|
-
THEN current_handoff.next_actions
|
|
190844
|
+
THEN current_handoff.next_actions
|
|
190845
|
+
WHEN jsonb_array_length(COALESCE(current_handoff.next_actions, '[]'::jsonb)) > 0
|
|
190846
|
+
THEN current_handoff.next_actions
|
|
190847
|
+
ELSE EXCLUDED.next_actions END,
|
|
190453
190848
|
verification_gaps=CASE
|
|
190454
190849
|
WHEN current_handoff.produced_by = 'agent'
|
|
190455
|
-
THEN current_handoff.verification_gaps
|
|
190850
|
+
THEN current_handoff.verification_gaps
|
|
190851
|
+
WHEN jsonb_array_length(COALESCE(current_handoff.verification_gaps, '[]'::jsonb)) > 0
|
|
190852
|
+
THEN current_handoff.verification_gaps
|
|
190853
|
+
ELSE EXCLUDED.verification_gaps END,
|
|
190456
190854
|
user_handoff=CASE
|
|
190457
190855
|
WHEN current_handoff.produced_by = 'agent'
|
|
190458
190856
|
THEN current_handoff.user_handoff
|
|
190459
|
-
ELSE EXCLUDED.user_handoff
|
|
190857
|
+
ELSE COALESCE(current_handoff.user_handoff, EXCLUDED.user_handoff)
|
|
190460
190858
|
END;
|
|
190461
190859
|
NEW.handoff_id := COALESCE(NEW.handoff_id, generated_handoff_id);
|
|
190462
190860
|
RETURN NEW;
|
|
@@ -191259,7 +191657,6 @@ var init_postgres_trace = __esm({
|
|
|
191259
191657
|
}
|
|
191260
191658
|
async putHandoff(input) {
|
|
191261
191659
|
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
191660
|
const client = await this.pool.connect();
|
|
191264
191661
|
try {
|
|
191265
191662
|
await client.query("BEGIN");
|
|
@@ -194218,11 +194615,11 @@ async function startServe(opts) {
|
|
|
194218
194615
|
fs28.writeFile(serverHeartbeatFile, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), pid: process.pid }) + "\n", () => {
|
|
194219
194616
|
});
|
|
194220
194617
|
};
|
|
194618
|
+
const lock = path22.join(opts.dir, "serve.lock");
|
|
194619
|
+
acquireServeLock(lock);
|
|
194221
194620
|
writeServerHeartbeat();
|
|
194222
194621
|
const serverHeartbeatTimer = setInterval(writeServerHeartbeat, 5e3);
|
|
194223
194622
|
serverHeartbeatTimer.unref?.();
|
|
194224
|
-
const lock = path22.join(opts.dir, "serve.lock");
|
|
194225
|
-
acquireServeLock(lock);
|
|
194226
194623
|
let activeTimeoutPolicy;
|
|
194227
194624
|
try {
|
|
194228
194625
|
activeTimeoutPolicy = loadPolicyFromFile(resolvePolicyFilePath());
|
|
@@ -194583,7 +194980,7 @@ async function startServe(opts) {
|
|
|
194583
194980
|
const trajReader = new FsTrajectorySink(opts.dir);
|
|
194584
194981
|
const trace = createTraceDomain({ store: traceStore, readBundleFile: (id, rel) => trajReader.readBundleFile(id, rel) });
|
|
194585
194982
|
const traceHealth = createTraceHealth();
|
|
194586
|
-
const continuityModeRaw = process.env["OASIS_EXECUTION_CONTINUITY_MODE"]?.trim() || "
|
|
194983
|
+
const continuityModeRaw = process.env["OASIS_EXECUTION_CONTINUITY_MODE"]?.trim() || "record";
|
|
194587
194984
|
if (!(continuityModeRaw === "off" || continuityModeRaw === "record" || continuityModeRaw === "resume")) {
|
|
194588
194985
|
throw new Error(`OASIS_EXECUTION_CONTINUITY_MODE must be off|record|resume, received ${continuityModeRaw}`);
|
|
194589
194986
|
}
|
|
@@ -194632,7 +195029,9 @@ async function startServe(opts) {
|
|
|
194632
195029
|
return snap ? nodeConclusionsFromSnapshot(snap) : {};
|
|
194633
195030
|
}
|
|
194634
195031
|
});
|
|
194635
|
-
console.log(
|
|
195032
|
+
console.log(
|
|
195033
|
+
`[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" : ""}`
|
|
195034
|
+
);
|
|
194636
195035
|
const automationStore = pgPool ? await PostgresAutomationStore.open(pgPool, pgSchema) : new MemoryAutomationStore();
|
|
194637
195036
|
console.log(`[serve] \u81EA\u52A8\u5316\u4F53\u7CFB\uFF1A${pgPool ? `Postgres schema=${pgSchema}` : "\u5185\u5B58 dev store"}`);
|
|
194638
195037
|
const automations = createAutomationsDomain({ store: automationStore, kernel });
|
|
@@ -195382,9 +195781,36 @@ async function startServe(opts) {
|
|
|
195382
195781
|
...await materializeSkillFiles({ service: actors.service, actorId, runtimeKind })
|
|
195383
195782
|
}),
|
|
195384
195783
|
uploadChatAttachment: async ({ contentBase64 }) => ({ blobRef: await assets.put(new Uint8Array(Buffer.from(contentBase64, "base64"))) }),
|
|
195385
|
-
onDispatchCommandCompleted: async ({ dispatchId, durableRefs, at }) => {
|
|
195386
|
-
traceStoreSink.checkpoint(dispatchId, {
|
|
195784
|
+
onDispatchCommandCompleted: async ({ dispatchId, command, durableRefs, at, delivered }) => {
|
|
195785
|
+
traceStoreSink.checkpoint(dispatchId, {
|
|
195786
|
+
trigger: "important_output",
|
|
195787
|
+
// 交作业的那条命令带着版本号(`wk:<节点>:<n>`),比笼统的「动过这个产物」有用得多——
|
|
195788
|
+
// 下一趟接手时据它知道上一趟已经交到哪一版。其余持久化命令仍记粗粒度引用。
|
|
195789
|
+
durableRefs: delivered?.refs ?? durableRefs,
|
|
195790
|
+
// 稳定步骤 id = 命令名:同一步骤重跑时 id 一致(checkpoint 契约对 stable id 的要求),
|
|
195791
|
+
// 恢复时下一趟能看出「上一趟已经 propose 过 / 已经 gap 过」。
|
|
195792
|
+
completedStepIds: [command],
|
|
195793
|
+
at
|
|
195794
|
+
});
|
|
195387
195795
|
await traceStoreSink.flush(dispatchId);
|
|
195796
|
+
if (!delivered) return;
|
|
195797
|
+
await recordDeliveredOutputHandoff(
|
|
195798
|
+
{
|
|
195799
|
+
getAttempt: (attemptId) => traceStore.getAttempt(attemptId),
|
|
195800
|
+
getHandoff: (attemptId) => traceStore.getHandoff(attemptId),
|
|
195801
|
+
handoff: (input) => executionContinuity.service.handoff(input)
|
|
195802
|
+
},
|
|
195803
|
+
{
|
|
195804
|
+
attemptId: dispatchId,
|
|
195805
|
+
summary: delivered.summary,
|
|
195806
|
+
refs: delivered.refs,
|
|
195807
|
+
...delivered.unresolvedIssues ? { unresolvedIssues: delivered.unresolvedIssues } : {},
|
|
195808
|
+
...delivered.risks ? { risks: delivered.risks } : {},
|
|
195809
|
+
...delivered.nextActions ? { nextActions: delivered.nextActions } : {},
|
|
195810
|
+
...delivered.verificationGaps ? { verificationGaps: delivered.verificationGaps } : {},
|
|
195811
|
+
at
|
|
195812
|
+
}
|
|
195813
|
+
);
|
|
195388
195814
|
},
|
|
195389
195815
|
dispatchProduce: async ({ artifactId, actorId, part, companyId }) => {
|
|
195390
195816
|
const d = dispatchers.get(companyId ?? defaultCompanyId);
|
|
@@ -196128,7 +196554,7 @@ async function startServe(opts) {
|
|
|
196128
196554
|
const rev = kernelModel.revisions.get(workId);
|
|
196129
196555
|
if (!rev) return;
|
|
196130
196556
|
if (rev.state === "merged") return;
|
|
196131
|
-
const replyIssueId = rev.
|
|
196557
|
+
const replyIssueId = rev.replyIssueId ?? null;
|
|
196132
196558
|
console.log(`[new-engine] dispatchWork ${workId} \u2192 ${rev.artifactId} / ${rev.author}${replyIssueId ? `\uFF08\u56DE\u4FE1 ${replyIssueId}\uFF09` : ""}`);
|
|
196133
196559
|
const art = kernelModel.artifacts.get(rev.artifactId);
|
|
196134
196560
|
const wid = art?.workspace ?? "";
|
|
@@ -196432,7 +196858,7 @@ async function startServe(opts) {
|
|
|
196432
196858
|
artifactId ? await resolveDispatchScope(artifactId) : void 0,
|
|
196433
196859
|
(key) => emitBrokerRefused(actorId, artifactId, key)
|
|
196434
196860
|
);
|
|
196435
|
-
return
|
|
196861
|
+
return process.env["OASIS_EXTERNAL_EFFECT_CAPTURE"] === "1" ? { ...provision, env: { ...provision.env, OASIS_EXTERNAL_EFFECT_CAPTURE: "1" } } : provision;
|
|
196436
196862
|
},
|
|
196437
196863
|
resolveActorContext: async (actorId) => buildActorContext((await runActors()).service, actorId),
|
|
196438
196864
|
// 收尾评审的角色分工段(reviewer 此前彼此不知道对方存在 → 越界 / 重复劳动)。
|
|
@@ -196486,13 +196912,15 @@ async function startServe(opts) {
|
|
|
196486
196912
|
getHandoff: (attemptId) => traceStore.getHandoff(attemptId)
|
|
196487
196913
|
}),
|
|
196488
196914
|
executionContinuityInstructions: continuityMode === "off" ? renderBusinessHandoffProtocol() : renderContinuityProtocol(),
|
|
196489
|
-
|
|
196915
|
+
// ★ 恢复包注入(2026-08-17):从「只有 resume 实验组才给」改成「只要账本开着就给」——
|
|
196916
|
+
// A/B 实验早已结束,record 与 resume 现在行为一致(resume 值保留只为兼容既有部署变量)。
|
|
196917
|
+
...continuityMode !== "off" ? {
|
|
196490
196918
|
resolveExecutionResumePack: async (artifactId, workOrderId, scope) => {
|
|
196491
196919
|
const pack = await executionContinuity.service.resumePack(workOrderId, artifactId, companyId, {
|
|
196492
196920
|
jobKey: scope.jobKey,
|
|
196493
196921
|
part: scope.part ?? null
|
|
196494
196922
|
});
|
|
196495
|
-
return pack ? renderResumePack(pack) : null;
|
|
196923
|
+
return shouldInjectResumePack(pack) ? renderResumePack(pack) : null;
|
|
196496
196924
|
}
|
|
196497
196925
|
} : {},
|
|
196498
196926
|
schema: liveSchemaMap,
|
|
@@ -198612,9 +199040,16 @@ var COMMAND_DECLS = {
|
|
|
198612
199040
|
{ name: "rebase-of", desc: "\u628A\u4E00\u6761 queued revision \u6539 base \u540E\u91CD\u6302" },
|
|
198613
199041
|
{ name: "checkpoint", desc: "\u65AD\u70B9\u7EED\u4F20\u6807\u8BB0" },
|
|
198614
199042
|
{ 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" }
|
|
199043
|
+
{ 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" },
|
|
199044
|
+
{ 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" },
|
|
199045
|
+
{ name: "risks", desc: "\u4E0B\u6E38\u8BE5\u77E5\u9053\u7684\u98CE\u9669\uFF08\u5206\u53F7\u5206\u9694\uFF09" },
|
|
199046
|
+
{ name: "next", desc: "\u5EFA\u8BAE\u4E0B\u6E38\u63A5\u7740\u505A\u4EC0\u4E48\uFF08\u5206\u53F7\u5206\u9694\uFF09" },
|
|
199047
|
+
{ 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
199048
|
],
|
|
198617
|
-
examples: [
|
|
199049
|
+
examples: [
|
|
199050
|
+
'oasis propose artifact:dev:abc-123 --from-dir deliverable/ --reason "\u4FEE\u590D\u767B\u5F55\u9875\u767D\u5C4F"',
|
|
199051
|
+
'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"'
|
|
199052
|
+
]
|
|
198618
199053
|
},
|
|
198619
199054
|
queue: {
|
|
198620
199055
|
group: "\u7248\u672C\u673A\uFF08\xA75\uFF09",
|
|
@@ -199896,6 +200331,11 @@ function needPos(positional, i, usage) {
|
|
|
199896
200331
|
if (v2 === void 0) throw new Error(`\u7528\u6CD5: ${usage}`);
|
|
199897
200332
|
return v2;
|
|
199898
200333
|
}
|
|
200334
|
+
function semicolonList(raw, field) {
|
|
200335
|
+
if (raw === void 0) return {};
|
|
200336
|
+
const items = raw.split(";").map((x2) => x2.trim()).filter(Boolean);
|
|
200337
|
+
return items.length > 0 ? { [field]: items } : {};
|
|
200338
|
+
}
|
|
199899
200339
|
async function readStdin() {
|
|
199900
200340
|
if (process.stdin.isTTY) return "";
|
|
199901
200341
|
const chunks = [];
|
|
@@ -201282,7 +201722,15 @@ ${res.warning}`);
|
|
|
201282
201722
|
...checkpoint ? { continuation: { checkpoint: true, continue: true } } : {},
|
|
201283
201723
|
// 申报单(B 部):显式增补"本版答复的信"(逗号分隔 annotationId)。常规不用填——系统按派发点名集
|
|
201284
201724
|
// 自动盖章;只在中途经 status 发现的信等特例用。
|
|
201285
|
-
...flags.get("resolves") !== void 0 ? { resolves: flags.get("resolves").split(",").map((x2) => x2.trim()).filter(Boolean) } : {}
|
|
201725
|
+
...flags.get("resolves") !== void 0 ? { resolves: flags.get("resolves").split(",").map((x2) => x2.trim()).filter(Boolean) } : {},
|
|
201726
|
+
// 技术交接的四项「系统看不见的东西」(2026-08-17):交作业时顺手带上,服务端写进本节点的
|
|
201727
|
+
// NodeHandoff。**不必再等会话终态另跑 `oasis continuity-handoff`**——那条命令的说明书在
|
|
201728
|
+
// execution/CONTINUITY.md 里,而 adapter 只把 TASK.md 当提示词,线上因此只有 3.6% 的节点写到了。
|
|
201729
|
+
// 分号分隔;一条都不填也完全正常(摘要与交付版本系统自己会记)。
|
|
201730
|
+
...semicolonList(flags.get("unresolved"), "unresolvedIssues"),
|
|
201731
|
+
...semicolonList(flags.get("risks"), "risks"),
|
|
201732
|
+
...semicolonList(flags.get("next"), "nextActions"),
|
|
201733
|
+
...semicolonList(flags.get("gaps"), "verificationGaps")
|
|
201286
201734
|
});
|
|
201287
201735
|
println(message);
|
|
201288
201736
|
break;
|
|
@@ -202556,7 +203004,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
|
|
|
202556
203004
|
}
|
|
202557
203005
|
|
|
202558
203006
|
// src/index.ts
|
|
202559
|
-
var PKG_VERSION = true ? "0.1.
|
|
203007
|
+
var PKG_VERSION = true ? "0.1.108" : "dev";
|
|
202560
203008
|
var OASIS_DIR = path28.join(os11.homedir(), ".oasis");
|
|
202561
203009
|
var CONFIG_FILE = path28.join(OASIS_DIR, "node-config.json");
|
|
202562
203010
|
var PID_FILE = path28.join(OASIS_DIR, "node.pid");
|