oasis_test 0.1.99 → 0.1.101
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 +1137 -209
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2607,7 +2607,53 @@ var init_trace = __esm({
|
|
|
2607
2607
|
});
|
|
2608
2608
|
|
|
2609
2609
|
// ../contract/src/execution-continuity.ts
|
|
2610
|
-
|
|
2610
|
+
function isUserHandoffDeliverable(ref2) {
|
|
2611
|
+
const kind = ref2.kind.trim().toLowerCase();
|
|
2612
|
+
const id = ref2.id.trim();
|
|
2613
|
+
const label = ref2.label?.trim().toLowerCase();
|
|
2614
|
+
if (!id || !USER_HANDOFF_DELIVERABLE_KINDS.has(kind)) return false;
|
|
2615
|
+
if (label && INTERNAL_HANDOFF_LABELS.has(label)) return false;
|
|
2616
|
+
return !["attempt:", "checkpoint:", "handoff:", "op:", "run:", "runtime:"].some((prefix) => id.toLowerCase().startsWith(prefix));
|
|
2617
|
+
}
|
|
2618
|
+
function userHandoffDeliverables(refs) {
|
|
2619
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2620
|
+
return refs.flatMap((ref2) => {
|
|
2621
|
+
if (!isUserHandoffDeliverable(ref2)) return [];
|
|
2622
|
+
const normalized4 = {
|
|
2623
|
+
...ref2,
|
|
2624
|
+
kind: ref2.kind.trim(),
|
|
2625
|
+
id: ref2.id.trim(),
|
|
2626
|
+
...ref2.label === void 0 ? {} : { label: ref2.label.trim() }
|
|
2627
|
+
};
|
|
2628
|
+
const key = `${normalized4.kind.toLowerCase()}\0${normalized4.id}\0${normalized4.version ?? ""}`;
|
|
2629
|
+
if (seen.has(key)) return [];
|
|
2630
|
+
seen.add(key);
|
|
2631
|
+
return [normalized4];
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
function normalizeUserHandoff(outcome, _changedOutputs, authored) {
|
|
2635
|
+
if (!authored) return null;
|
|
2636
|
+
const status = outcome === "succeeded" ? "delivered" : "not_delivered";
|
|
2637
|
+
if (authored.status !== status) {
|
|
2638
|
+
throw new Error(`user handoff status ${authored.status} does not match outcome ${outcome}`);
|
|
2639
|
+
}
|
|
2640
|
+
const summary = authored.summary.trim();
|
|
2641
|
+
if (!summary) throw new Error("user handoff summary is required");
|
|
2642
|
+
if ([...summary].length > 400) {
|
|
2643
|
+
throw new Error("user handoff summary exceeds 400 characters");
|
|
2644
|
+
}
|
|
2645
|
+
const nextStep = authored.nextStep.trim();
|
|
2646
|
+
if ([...nextStep].length > 400) {
|
|
2647
|
+
throw new Error("user handoff next step exceeds 400 characters");
|
|
2648
|
+
}
|
|
2649
|
+
return {
|
|
2650
|
+
status,
|
|
2651
|
+
summary,
|
|
2652
|
+
deliverables: status === "delivered" ? userHandoffDeliverables(authored.deliverables) : [],
|
|
2653
|
+
nextStep
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
var ExecutionAttemptNotFoundError, USER_HANDOFF_DELIVERABLE_KINDS, INTERNAL_HANDOFF_LABELS;
|
|
2611
2657
|
var init_execution_continuity = __esm({
|
|
2612
2658
|
"../contract/src/execution-continuity.ts"() {
|
|
2613
2659
|
"use strict";
|
|
@@ -2618,6 +2664,40 @@ var init_execution_continuity = __esm({
|
|
|
2618
2664
|
this.name = "ExecutionAttemptNotFoundError";
|
|
2619
2665
|
}
|
|
2620
2666
|
};
|
|
2667
|
+
USER_HANDOFF_DELIVERABLE_KINDS = /* @__PURE__ */ new Set([
|
|
2668
|
+
"artifact",
|
|
2669
|
+
"audio",
|
|
2670
|
+
"blob",
|
|
2671
|
+
"code",
|
|
2672
|
+
"commit",
|
|
2673
|
+
"data",
|
|
2674
|
+
"dataset",
|
|
2675
|
+
"diff",
|
|
2676
|
+
"document",
|
|
2677
|
+
"file",
|
|
2678
|
+
"html",
|
|
2679
|
+
"image",
|
|
2680
|
+
"link",
|
|
2681
|
+
"markdown",
|
|
2682
|
+
"markdown-document",
|
|
2683
|
+
"output",
|
|
2684
|
+
"report",
|
|
2685
|
+
"repository",
|
|
2686
|
+
"revision",
|
|
2687
|
+
"text",
|
|
2688
|
+
"url",
|
|
2689
|
+
"video"
|
|
2690
|
+
]);
|
|
2691
|
+
INTERNAL_HANDOFF_LABELS = /* @__PURE__ */ new Set([
|
|
2692
|
+
"checkpoint",
|
|
2693
|
+
"conclude",
|
|
2694
|
+
"continuity-checkpoint",
|
|
2695
|
+
"continuity-handoff",
|
|
2696
|
+
"control",
|
|
2697
|
+
"gap",
|
|
2698
|
+
"op",
|
|
2699
|
+
"operation"
|
|
2700
|
+
]);
|
|
2621
2701
|
}
|
|
2622
2702
|
});
|
|
2623
2703
|
|
|
@@ -3289,6 +3369,22 @@ async function assembleContext(args) {
|
|
|
3289
3369
|
const actorLabels = args.resolveActorLabels ? await args.resolveActorLabels().catch(() => /* @__PURE__ */ new Map()) : /* @__PURE__ */ new Map();
|
|
3290
3370
|
const actorLabel = (id) => id === void 0 ? "" : actorLabels.get(id) ?? id;
|
|
3291
3371
|
const files = {};
|
|
3372
|
+
const upstreamHandoffFiles = /* @__PURE__ */ new Map();
|
|
3373
|
+
if (args.resolveUpstreamHandoff && artifact.workspace) {
|
|
3374
|
+
for (const edge of artifact.inputs) {
|
|
3375
|
+
if (edge.pinned === null) continue;
|
|
3376
|
+
const content = await args.resolveUpstreamHandoff({
|
|
3377
|
+
workOrderId: artifact.workspace,
|
|
3378
|
+
downstreamNodeId: artifact.id,
|
|
3379
|
+
upstreamNodeId: edge.to,
|
|
3380
|
+
pinnedWorkId: edge.pinned
|
|
3381
|
+
});
|
|
3382
|
+
if (content) {
|
|
3383
|
+
const dir = `inputs/${slug2(edge.to)}@${edge.pinned.slice(9, 17)}`;
|
|
3384
|
+
upstreamHandoffFiles.set(`${dir}/${UPSTREAM_HANDOFF_BASENAME}`, content);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3292
3388
|
if (canvasMode && artifact.description) files["DESIGN_BRIEF.md"] = artifact.description;
|
|
3293
3389
|
if (closureReportMode && artifact.workspace) {
|
|
3294
3390
|
const ledger = buildWorkorderLedger(model, artifact.workspace);
|
|
@@ -3928,7 +4024,16 @@ ${m2.spec.split("\n").map((l) => ` > ${l}`).join("\n")}`);
|
|
|
3928
4024
|
``,
|
|
3929
4025
|
`## \u4F60\u7684\u8F93\u5165`,
|
|
3930
4026
|
args.action === "review" ? `\u8BFB \`inputs/\` \u2014\u2014 \u5404\u4E0A\u6E38\u4E00\u4E2A\u6587\u4EF6\u5939\uFF1B**\u636E\u5B83\u6838\u5BF9\u5F53\u524D\u4EA7\u7269\u4E0E\u4E0A\u6E38\u662F\u5426\u4E00\u81F4**\uFF08\u6587\u672C\u7C7B=\u6587\u4EF6\uFF0C\u4EE3\u7801/\u5916\u90E8\u7C7B=\u6307\u9488\uFF09\uFF1A` : `\u8BFB \`inputs/\` \u2014\u2014 \u6BCF\u4E2A\u4E0A\u6E38\u4E00\u4E2A\u6587\u4EF6\u5939\uFF0C\u6309\u91CC\u9762\u5185\u5BB9\u529E\uFF08**\u6587\u672C\u7C7B=\u6587\u4EF6\uFF0C\u4EE3\u7801/\u5916\u90E8\u7C7B=\u6307\u9488**\uFF09\uFF1A`,
|
|
3931
|
-
...artifact.inputs.filter((e) => e.pinned !== null).length === 0 ? [`- \uFF08\u672C\u4EA7\u7269\u6682\u65E0\u5DF2\u5C31\u7EEA\u7684\u4E0A\u6E38\u8F93\u5165\uFF09`] : artifact.inputs.filter((e) => e.pinned !== null).map((e) => `- \`inputs/${slug2(e.to)}@${e.pinned.slice(9, 17)}/\` \u2190 ${e.to}${e.required ? "\uFF08required\uFF09" : ""}`)
|
|
4027
|
+
...artifact.inputs.filter((e) => e.pinned !== null).length === 0 ? [`- \uFF08\u672C\u4EA7\u7269\u6682\u65E0\u5DF2\u5C31\u7EEA\u7684\u4E0A\u6E38\u8F93\u5165\uFF09`] : artifact.inputs.filter((e) => e.pinned !== null).map((e) => `- \`inputs/${slug2(e.to)}@${e.pinned.slice(9, 17)}/\` \u2190 ${e.to}${e.required ? "\uFF08required\uFF09" : ""}`),
|
|
4028
|
+
...upstreamHandoffFiles.size === 0 ? [] : [
|
|
4029
|
+
``,
|
|
4030
|
+
`### \u4E0A\u6E38\u4E1A\u52A1\u4EA4\u63A5\uFF08\u5F00\u5DE5\u524D\u5FC5\u8BFB\uFF09`,
|
|
4031
|
+
`1. \u5148\u8BFB\u6BCF\u4E2A\u4E0A\u6E38\u76EE\u5F55\u4E2D\u7684 \`${UPSTREAM_HANDOFF_BASENAME}\`\u3002`,
|
|
4032
|
+
`2. \u518D\u8BFB\u540C\u76EE\u5F55\u7684 \`CONTENT.md\`\u3001\`PIN.md\` \u548C\u5B9E\u9645\u4E1A\u52A1\u6587\u4EF6\u3002`,
|
|
4033
|
+
`3. \u7528 handoff \u7406\u89E3\u4E0A\u6E38\u5B8C\u6210\u60C5\u51B5\u3001\u53D8\u66F4\u8F93\u51FA\u3001\u672A\u51B3\u4E8B\u9879\u3001\u98CE\u9669\u4E0E\u5EFA\u8BAE\u52A8\u4F5C\u3002`,
|
|
4034
|
+
`4. \u7528 pinned inputs \u4E0E handoff \u4E2D\u7684 durable output \u5F15\u7528\u6838\u9A8C\u5B9E\u9645\u4EA7\u7269\uFF1Bhandoff \u662F\u4EA4\u63A5\u8BF4\u660E\uFF0C\u4E0D\u66FF\u4EE3\u4EA7\u7269\u4E8B\u5B9E\u3002`,
|
|
4035
|
+
...[...upstreamHandoffFiles.keys()].map((path26) => `- \u4EA4\u63A5\uFF1A\`${path26}\``)
|
|
4036
|
+
]
|
|
3932
4037
|
],
|
|
3933
4038
|
...skillsLines.length > 0 ? [``, `## \u4F60\u7684\u6280\u80FD\uFF08\u5DF2\u4E3A\u4F60\u542F\u7528\uFF0C\u6309\u9700\u8C03\u7528\uFF09`, ...skillsLines] : [],
|
|
3934
4039
|
``,
|
|
@@ -4029,6 +4134,17 @@ ${m2.spec.split("\n").map((l) => ` > ${l}`).join("\n")}`);
|
|
|
4029
4134
|
required=${edge.required}
|
|
4030
4135
|
` + (latest && latest !== edge.pinned ? `\u6CE8\u610F\uFF1A\u4E0A\u6E38\u5DF2\u6709\u66F4\u65B0\u91CC\u7A0B\u7891 ${latest}\uFF08\u4F60 pin \u7684\u662F\u65E7\u7248\uFF09
|
|
4031
4136
|
` : "");
|
|
4137
|
+
const handoffPath = `${dir}/${UPSTREAM_HANDOFF_BASENAME}`;
|
|
4138
|
+
const handoff = upstreamHandoffFiles.get(handoffPath);
|
|
4139
|
+
if (handoff !== void 0) {
|
|
4140
|
+
const collision = Object.keys(files).find((file) => file.toLowerCase() === handoffPath.toLowerCase());
|
|
4141
|
+
if (collision) {
|
|
4142
|
+
throw new Error(
|
|
4143
|
+
`upstream input ${edge.to}@${edge.pinned} uses system-reserved filename ${UPSTREAM_HANDOFF_BASENAME} (${collision})`
|
|
4144
|
+
);
|
|
4145
|
+
}
|
|
4146
|
+
files[handoffPath] = handoff;
|
|
4147
|
+
}
|
|
4032
4148
|
}
|
|
4033
4149
|
const limit = args.historyLimit ?? 20;
|
|
4034
4150
|
const opLog = args.oplog ? (await args.oplog.read(artifactId)).slice(-limit).map((op) => `#${op.seq} ${op.timestamp} ${op.kind} by ${op.actor}`).join("\n") : "";
|
|
@@ -4100,7 +4216,7 @@ required=${c.required}
|
|
|
4100
4216
|
}
|
|
4101
4217
|
return { text: lines.join("\n"), files };
|
|
4102
4218
|
}
|
|
4103
|
-
var slug2, WORKDIR_KEEP_BASENAME, WORKDIR_KEEP_DELIVERABLE, WORKDIR_KEEP_INPUTS, WORKDIR_KEEP_BODY, REVIEW_NOTE_MAX_CHARS, decodeText, REVIEW_ROLE_LENS_PLACEHOLDER, REVIEW_GATE_DIVISION_PLACEHOLDER;
|
|
4219
|
+
var slug2, UPSTREAM_HANDOFF_BASENAME, WORKDIR_KEEP_BASENAME, WORKDIR_KEEP_DELIVERABLE, WORKDIR_KEEP_INPUTS, WORKDIR_KEEP_BODY, REVIEW_NOTE_MAX_CHARS, decodeText, REVIEW_ROLE_LENS_PLACEHOLDER, REVIEW_GATE_DIVISION_PLACEHOLDER;
|
|
4104
4220
|
var init_assembler = __esm({
|
|
4105
4221
|
"../core/src/assembler.ts"() {
|
|
4106
4222
|
"use strict";
|
|
@@ -4112,6 +4228,7 @@ var init_assembler = __esm({
|
|
|
4112
4228
|
init_diff();
|
|
4113
4229
|
init_workorder_ledger();
|
|
4114
4230
|
slug2 = (id) => id.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
4231
|
+
UPSTREAM_HANDOFF_BASENAME = "_HANDOFF.md";
|
|
4115
4232
|
WORKDIR_KEEP_BASENAME = ".oasis-keep";
|
|
4116
4233
|
WORKDIR_KEEP_DELIVERABLE = `deliverable/${WORKDIR_KEEP_BASENAME}`;
|
|
4117
4234
|
WORKDIR_KEEP_INPUTS = `inputs/${WORKDIR_KEEP_BASENAME}`;
|
|
@@ -5242,7 +5359,9 @@ var init_dispatcher = __esm({
|
|
|
5242
5359
|
artifactId: args.artifactId,
|
|
5243
5360
|
actor: args.actor,
|
|
5244
5361
|
action: "produce",
|
|
5245
|
-
...args.part !== void 0 ? { part: args.part } : {}
|
|
5362
|
+
...args.part !== void 0 ? { part: args.part } : {},
|
|
5363
|
+
...args.engineWorkId !== void 0 ? { engineWorkId: args.engineWorkId } : {},
|
|
5364
|
+
...args.engineCompanyId !== void 0 ? { engineCompanyId: args.engineCompanyId } : {}
|
|
5246
5365
|
}, args.bypassScheduling === true, args.dispatchId);
|
|
5247
5366
|
}
|
|
5248
5367
|
/** 新引擎回信 work(设计-最终 §回信轮)的 effect 调到此——派发收容回信会话(唯一写动作 `oasis reply`)。
|
|
@@ -5517,6 +5636,7 @@ var init_dispatcher = __esm({
|
|
|
5517
5636
|
...prevBriefSeq !== void 0 ? { prevBriefSeq } : {},
|
|
5518
5637
|
...this.opts.schema !== void 0 ? { schema: this.opts.schema } : {},
|
|
5519
5638
|
...codeRepo ? { codeRepo } : {},
|
|
5639
|
+
...this.opts.resolveUpstreamHandoff !== void 0 ? { resolveUpstreamHandoff: this.opts.resolveUpstreamHandoff } : {},
|
|
5520
5640
|
...this.opts.resolveActorContext !== void 0 ? { resolveActorContext: this.opts.resolveActorContext } : {},
|
|
5521
5641
|
...this.opts.resolveActorLabels !== void 0 ? { resolveActorLabels: this.opts.resolveActorLabels } : {},
|
|
5522
5642
|
...this.opts.resolveRoleBriefs !== void 0 ? { resolveRoleBriefs: this.opts.resolveRoleBriefs } : {},
|
|
@@ -5533,7 +5653,18 @@ var init_dispatcher = __esm({
|
|
|
5533
5653
|
continuityArtifact.workspace,
|
|
5534
5654
|
{ jobKey, ...spec.part !== void 0 ? { part: spec.part } : {} }
|
|
5535
5655
|
);
|
|
5536
|
-
if (resumePack)
|
|
5656
|
+
if (resumePack) {
|
|
5657
|
+
bundle.files["execution/RESUME.md"] = resumePack;
|
|
5658
|
+
bundle.files["TASK.md"] += [
|
|
5659
|
+
"",
|
|
5660
|
+
"## \u6267\u884C\u6062\u590D\uFF08\u4EC5\u5F53\u524D\u8282\u70B9\uFF09",
|
|
5661
|
+
"\u672C\u8F6E\u662F\u540C\u4E00\u8282\u70B9\u4E0A\u4E00\u6B21 Attempt \u4E2D\u65AD\u540E\u7684\u7EE7\u7EED\u6267\u884C\u3002\u5F00\u5DE5\u524D\u5148\u8BFB `execution/RESUME.md`\uFF0C",
|
|
5662
|
+
"\u6838\u5BF9\u6700\u65B0 Checkpoint\u3001\u5DF2\u5B8C\u6210\u6B65\u9AA4\u3001durable outputs\u3001ExternalEffects \u4E0E\u672A\u51B3\u6B65\u9AA4\uFF1B",
|
|
5663
|
+
"\u4ECE\u5C1A\u672A\u5B8C\u6210\u7684\u90E8\u5206\u7EE7\u7EED\uFF0C\u907F\u514D\u91CD\u590D\u5DF2\u7ECF\u786E\u8BA4\u5B8C\u6210\u7684\u6B65\u9AA4\u6216\u5916\u90E8\u526F\u4F5C\u7528\u3002",
|
|
5664
|
+
"\u8FD9\u4EFD\u6062\u590D\u6750\u6599\u4E0D\u662F\u4E0A\u6E38\u8282\u70B9\u7684\u4E1A\u52A1\u4EA4\u63A5\uFF1B\u4E0A\u6E38\u4EA4\u63A5\u4ECD\u4EE5 `inputs/*/_HANDOFF.md` \u4E3A\u51C6\u3002",
|
|
5665
|
+
""
|
|
5666
|
+
].join("\n");
|
|
5667
|
+
}
|
|
5537
5668
|
}
|
|
5538
5669
|
this.assertSpawnAttemptCurrent(jobKey, attempt);
|
|
5539
5670
|
const limits = { wallClockMs: this.wallClockFor(spec.artifactId) };
|
|
@@ -5634,6 +5765,8 @@ var init_dispatcher = __esm({
|
|
|
5634
5765
|
}
|
|
5635
5766
|
const tsess = {
|
|
5636
5767
|
runId: session.id,
|
|
5768
|
+
...spec.engineWorkId !== void 0 ? { engineWorkId: spec.engineWorkId } : {},
|
|
5769
|
+
...spec.engineCompanyId !== void 0 ? { engineCompanyId: spec.engineCompanyId } : {},
|
|
5637
5770
|
// 运行时会话号落账:它是 cwd 目录名、也是 --resume 的键。不记的话 run 与它跑在哪个
|
|
5638
5771
|
// 工作目录之间就断了链(只能靠 grep 包内容 + 比对时间戳去猜)。
|
|
5639
5772
|
...runtimeSessionId !== void 0 ? { runtimeSessionId } : {},
|
|
@@ -6146,10 +6279,12 @@ var init_dispatcher = __esm({
|
|
|
6146
6279
|
});
|
|
6147
6280
|
|
|
6148
6281
|
// ../engine/src/model.ts
|
|
6149
|
-
var DEFAULT_CONFIG, MAX_WORK_DURATION_MS, MAX_REVIEW_DURATION_MS, MAX_REVIEW_RETRIES, deriveWorkId, deriveReplyWorkId, deriveReviewId, deriveReplyId;
|
|
6282
|
+
var BUSINESS_HANDOFF_POLICY_FIELD, BUSINESS_HANDOFF_POLICY_EXEMPT, DEFAULT_CONFIG, MAX_WORK_DURATION_MS, MAX_REVIEW_DURATION_MS, MAX_REVIEW_RETRIES, deriveWorkId, deriveReplyWorkId, deriveReviewId, deriveReplyId;
|
|
6150
6283
|
var init_model = __esm({
|
|
6151
6284
|
"../engine/src/model.ts"() {
|
|
6152
6285
|
"use strict";
|
|
6286
|
+
BUSINESS_HANDOFF_POLICY_FIELD = "businessHandoffPolicy";
|
|
6287
|
+
BUSINESS_HANDOFF_POLICY_EXEMPT = "exempt";
|
|
6153
6288
|
DEFAULT_CONFIG = {
|
|
6154
6289
|
maxRetries: 3
|
|
6155
6290
|
};
|
|
@@ -6185,6 +6320,7 @@ function eventAnchors(e) {
|
|
|
6185
6320
|
return { nodeId: e.nodeId, workId: e.workId };
|
|
6186
6321
|
case "work.started":
|
|
6187
6322
|
case "work.response":
|
|
6323
|
+
case "work.handoff_recorded":
|
|
6188
6324
|
case "work.timeout":
|
|
6189
6325
|
case "work.accept":
|
|
6190
6326
|
case "work.submit_output":
|
|
@@ -6612,6 +6748,7 @@ function scan(state, ctx) {
|
|
|
6612
6748
|
if (wstate === "success" && node.latestAcceptId !== lw.id) {
|
|
6613
6749
|
const reqs = state.requirementsOf(node.id);
|
|
6614
6750
|
if (reqs.length === 0) {
|
|
6751
|
+
if (!businessHandoffBarrierSatisfied(state, node.id, lw.id)) continue;
|
|
6615
6752
|
events.push({
|
|
6616
6753
|
kind: "work.accept",
|
|
6617
6754
|
workorderId: state.workorder.id,
|
|
@@ -6641,6 +6778,7 @@ function tryToRun(state, nodeId, ctx, events) {
|
|
|
6641
6778
|
if (e.kind !== "data" || !e.required) continue;
|
|
6642
6779
|
const upstream = state.node(e.fromNodeId);
|
|
6643
6780
|
if (!upstream?.latestAcceptId) return;
|
|
6781
|
+
if (isBusinessDependencyEdge(state, e) && !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return;
|
|
6644
6782
|
}
|
|
6645
6783
|
if (state.hasQueuedWorkFor(nodeId)) return;
|
|
6646
6784
|
const workId = deriveWorkId(nodeId, state.worksOf(nodeId).length + 1);
|
|
@@ -6742,6 +6880,7 @@ function processReviews(state, nodeId, workId, ctx, events) {
|
|
|
6742
6880
|
}
|
|
6743
6881
|
}
|
|
6744
6882
|
if (shouldAccept) {
|
|
6883
|
+
if (!businessHandoffBarrierSatisfied(state, nodeId, workId)) return;
|
|
6745
6884
|
events.push({
|
|
6746
6885
|
kind: "work.accept",
|
|
6747
6886
|
workorderId: state.workorder.id,
|
|
@@ -6859,8 +6998,28 @@ function checkTerminated(state) {
|
|
|
6859
6998
|
if (state.allIssues().some((i) => i.state === "open")) return false;
|
|
6860
6999
|
const live = state.liveNodes();
|
|
6861
7000
|
if (live.length === 0) return true;
|
|
7001
|
+
for (const edge of state.allEdges()) {
|
|
7002
|
+
if (!isBusinessDependencyEdge(state, edge)) continue;
|
|
7003
|
+
const upstream = state.node(edge.fromNodeId);
|
|
7004
|
+
if (!upstream?.latestWorkId || !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestWorkId)) return false;
|
|
7005
|
+
}
|
|
6862
7006
|
return live.every((n) => n.latestWorkId !== null && n.latestAcceptId === n.latestWorkId);
|
|
6863
7007
|
}
|
|
7008
|
+
function isBusinessDependencyEdge(state, edge) {
|
|
7009
|
+
const downstream = state.node(edge.toNodeId);
|
|
7010
|
+
return edge.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
|
|
7011
|
+
}
|
|
7012
|
+
function businessHandoffBarrierSatisfied(state, nodeId, workId) {
|
|
7013
|
+
const work = state.work(workId);
|
|
7014
|
+
if (!requiresBusinessHandoff(state, nodeId, work)) return true;
|
|
7015
|
+
const node = state.node(nodeId);
|
|
7016
|
+
return node?.latestWorkId === workId && work?.agentHandoffAttemptId !== null && work?.businessHandoffStatus === "delivered";
|
|
7017
|
+
}
|
|
7018
|
+
function requiresBusinessHandoff(state, nodeId, work) {
|
|
7019
|
+
if (!state.edgesFrom(nodeId).some((edge) => isBusinessDependencyEdge(state, edge))) return false;
|
|
7020
|
+
if (typeof work?.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) return false;
|
|
7021
|
+
return true;
|
|
7022
|
+
}
|
|
6864
7023
|
function activate(state, nodeId, _ctx) {
|
|
6865
7024
|
const node = state.node(nodeId);
|
|
6866
7025
|
if (!node) return "cancelled";
|
|
@@ -6993,6 +7152,10 @@ CREATE TABLE IF NOT EXISTS ${schema}.works (
|
|
|
6993
7152
|
reply_to_issue_id text,
|
|
6994
7153
|
output_version_no integer,
|
|
6995
7154
|
conclusion text,
|
|
7155
|
+
-- NodeHandoff \u4E8B\u5B9E\u6E90\u7684\u6700\u5C0F\u8C03\u5EA6\u6295\u5F71\uFF1B\u4EC5 agent-authored handoff \u53EF\u5199\uFF0Csystem reconstruction \u4E0D\u5199\u3002
|
|
7156
|
+
agent_handoff_attempt_id text,
|
|
7157
|
+
business_handoff_status text CHECK (business_handoff_status IN ('delivered','not_delivered')),
|
|
7158
|
+
business_handoff_recorded_at timestamptz,
|
|
6996
7159
|
acceptance_state text CHECK (acceptance_state IN ('draft','accepted','rejected')),
|
|
6997
7160
|
accepted_at timestamptz,
|
|
6998
7161
|
accepted_by text,
|
|
@@ -7155,6 +7318,12 @@ CREATE TABLE IF NOT EXISTS ${schema}.event_handler_log (
|
|
|
7155
7318
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS retry_at timestamptz;
|
|
7156
7319
|
ALTER TABLE ${schema}.reviews ADD COLUMN IF NOT EXISTS retry_at timestamptz;
|
|
7157
7320
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS status text;
|
|
7321
|
+
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS agent_handoff_attempt_id text;
|
|
7322
|
+
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS business_handoff_status text;
|
|
7323
|
+
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS business_handoff_recorded_at timestamptz;
|
|
7324
|
+
ALTER TABLE ${schema}.works DROP CONSTRAINT IF EXISTS works_business_handoff_status_check;
|
|
7325
|
+
ALTER TABLE ${schema}.works ADD CONSTRAINT works_business_handoff_status_check
|
|
7326
|
+
CHECK (business_handoff_status IS NULL OR business_handoff_status IN ('delivered','not_delivered'));
|
|
7158
7327
|
ALTER TABLE ${schema}.reviews ADD COLUMN IF NOT EXISTS status text;
|
|
7159
7328
|
-- \u56DE\u4FE1 work\uFF08\u8BBE\u8BA1-\u6700\u7EC8 \xA7\u56DE\u4FE1\u8F6E\uFF09\uFF1A\u975E\u7A7A = \u5BF9\u8BE5 comment issue \u7684\u56DE\u4FE1\u8F6E\uFF0C\u4E0D\u8FDB\u8282\u70B9\u4E3B\u94FE
|
|
7160
7329
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS reply_to_issue_id text;
|
|
@@ -12568,6 +12737,9 @@ var init_store_postgres = __esm({
|
|
|
12568
12737
|
replyToIssueId: w2.reply_to_issue_id ?? null,
|
|
12569
12738
|
outputVersionNo: w2.output_version_no,
|
|
12570
12739
|
conclusion: w2.conclusion,
|
|
12740
|
+
agentHandoffAttemptId: w2.agent_handoff_attempt_id ?? null,
|
|
12741
|
+
businessHandoffStatus: w2.business_handoff_status ?? null,
|
|
12742
|
+
businessHandoffRecordedAt: iso(w2.business_handoff_recorded_at),
|
|
12571
12743
|
acceptanceState: w2.acceptance_state,
|
|
12572
12744
|
acceptedAt: iso(w2.accepted_at),
|
|
12573
12745
|
acceptedBy: w2.accepted_by,
|
|
@@ -12771,8 +12943,9 @@ var init_store_postgres = __esm({
|
|
|
12771
12943
|
`INSERT INTO ${this.s}.works
|
|
12772
12944
|
(id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
|
|
12773
12945
|
outcome_detail,session_ref,continues_work_id,reply_to_issue_id,output_version_no,
|
|
12774
|
-
node_version,conclusion,
|
|
12775
|
-
|
|
12946
|
+
node_version,conclusion,agent_handoff_attempt_id,business_handoff_status,business_handoff_recorded_at,
|
|
12947
|
+
acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason)
|
|
12948
|
+
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)
|
|
12776
12949
|
ON CONFLICT (id) DO NOTHING`,
|
|
12777
12950
|
[
|
|
12778
12951
|
w2.id,
|
|
@@ -12794,6 +12967,9 @@ var init_store_postgres = __esm({
|
|
|
12794
12967
|
w2.outputVersionNo,
|
|
12795
12968
|
w2.nodeVersion,
|
|
12796
12969
|
w2.conclusion,
|
|
12970
|
+
w2.agentHandoffAttemptId,
|
|
12971
|
+
w2.businessHandoffStatus,
|
|
12972
|
+
w2.businessHandoffRecordedAt,
|
|
12797
12973
|
w2.acceptanceState,
|
|
12798
12974
|
w2.acceptedAt,
|
|
12799
12975
|
w2.acceptedBy,
|
|
@@ -13489,7 +13665,7 @@ var init_workorder = __esm({
|
|
|
13489
13665
|
});
|
|
13490
13666
|
|
|
13491
13667
|
// ../engine/src/handlers/work.ts
|
|
13492
|
-
var workCreate, workKill, workResponse, workStart, workSubmitOutput, workTimeout;
|
|
13668
|
+
var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workTimeout;
|
|
13493
13669
|
var init_work = __esm({
|
|
13494
13670
|
"../engine/src/handlers/work.ts"() {
|
|
13495
13671
|
"use strict";
|
|
@@ -13522,6 +13698,9 @@ var init_work = __esm({
|
|
|
13522
13698
|
replyToIssueId: e.replyToIssueId,
|
|
13523
13699
|
outputVersionNo: null,
|
|
13524
13700
|
conclusion: null,
|
|
13701
|
+
agentHandoffAttemptId: null,
|
|
13702
|
+
businessHandoffStatus: null,
|
|
13703
|
+
businessHandoffRecordedAt: null,
|
|
13525
13704
|
acceptanceState: null,
|
|
13526
13705
|
acceptedAt: null,
|
|
13527
13706
|
acceptedBy: null,
|
|
@@ -13554,6 +13733,9 @@ var init_work = __esm({
|
|
|
13554
13733
|
continuesWorkId: e.continuesWorkId,
|
|
13555
13734
|
outputVersionNo: null,
|
|
13556
13735
|
conclusion: null,
|
|
13736
|
+
agentHandoffAttemptId: null,
|
|
13737
|
+
businessHandoffStatus: null,
|
|
13738
|
+
businessHandoffRecordedAt: null,
|
|
13557
13739
|
acceptanceState: null,
|
|
13558
13740
|
acceptedAt: null,
|
|
13559
13741
|
acceptedBy: null,
|
|
@@ -13637,8 +13819,34 @@ var init_work = __esm({
|
|
|
13637
13819
|
});
|
|
13638
13820
|
return;
|
|
13639
13821
|
}
|
|
13640
|
-
const
|
|
13641
|
-
state.
|
|
13822
|
+
const businessFailed = w2.businessHandoffStatus === "not_delivered";
|
|
13823
|
+
const status = e.outcome === "completed" && hasOutput(w2, state.artifactsOf(e.workId).length) && !businessFailed ? "success" : "failed";
|
|
13824
|
+
state.updateWork(e.workId, {
|
|
13825
|
+
endedAt: ctx.at,
|
|
13826
|
+
outcome: businessFailed ? "failed" : e.outcome,
|
|
13827
|
+
outcomeDetail: businessFailed ? { ...e.detail ?? {}, reason: "agent business handoff not delivered" } : e.detail,
|
|
13828
|
+
status
|
|
13829
|
+
});
|
|
13830
|
+
}
|
|
13831
|
+
};
|
|
13832
|
+
workHandoffRecorded = {
|
|
13833
|
+
name: "work/handoff-recorded",
|
|
13834
|
+
kind: "work.handoff_recorded",
|
|
13835
|
+
apply(e, state, ctx) {
|
|
13836
|
+
const w2 = state.work(e.workId);
|
|
13837
|
+
if (!w2 || e.producedBy !== "agent") return;
|
|
13838
|
+
if (w2.agentHandoffAttemptId && w2.agentHandoffAttemptId !== e.attemptId) return;
|
|
13839
|
+
state.updateWork(e.workId, {
|
|
13840
|
+
agentHandoffAttemptId: e.attemptId,
|
|
13841
|
+
businessHandoffStatus: e.status,
|
|
13842
|
+
businessHandoffRecordedAt: ctx.at,
|
|
13843
|
+
// handoff 可以在 work.response 后到达。明确未交付必须回到现有失败/重试路径。
|
|
13844
|
+
...e.status === "not_delivered" && w2.endedAt && !w2.deadAt ? {
|
|
13845
|
+
status: "failed",
|
|
13846
|
+
outcome: "failed",
|
|
13847
|
+
outcomeDetail: { ...w2.outcomeDetail ?? {}, reason: "agent business handoff not delivered" }
|
|
13848
|
+
} : {}
|
|
13849
|
+
});
|
|
13642
13850
|
}
|
|
13643
13851
|
};
|
|
13644
13852
|
workStart = {
|
|
@@ -13825,6 +14033,7 @@ var init_review = __esm({
|
|
|
13825
14033
|
init_views2();
|
|
13826
14034
|
init_kill();
|
|
13827
14035
|
init_issue();
|
|
14036
|
+
init_activate();
|
|
13828
14037
|
reviewCreate = {
|
|
13829
14038
|
name: "review/create",
|
|
13830
14039
|
kind: "review.create",
|
|
@@ -13963,6 +14172,7 @@ var init_review = __esm({
|
|
|
13963
14172
|
if (!w2 || w2.deadAt || w2.acceptanceState === "accepted") return;
|
|
13964
14173
|
const node = state.node(w2.nodeId);
|
|
13965
14174
|
if (!node) return;
|
|
14175
|
+
if (!businessHandoffBarrierSatisfied(state, w2.nodeId, w2.id)) return;
|
|
13966
14176
|
if (w2.replyToIssueId) {
|
|
13967
14177
|
const issue2 = state.issue(w2.replyToIssueId);
|
|
13968
14178
|
state.updateWork(e.workId, {
|
|
@@ -14089,6 +14299,7 @@ var init_handlers = __esm({
|
|
|
14089
14299
|
erase(workCreate),
|
|
14090
14300
|
erase(workStart),
|
|
14091
14301
|
erase(workSubmitOutput),
|
|
14302
|
+
erase(workHandoffRecorded),
|
|
14092
14303
|
erase(workResponse),
|
|
14093
14304
|
erase(workTimeout),
|
|
14094
14305
|
erase(workKill),
|
|
@@ -14499,13 +14710,9 @@ var init_bus = __esm({
|
|
|
14499
14710
|
// 这里按 store 找它 cancel。找不到 / 不是刚被 kill / 会话已退 → no-op。这是设计「节点重新运行 = 自动
|
|
14500
14711
|
// kill 旧任务」的会话侧落地,否则改版重跑时旧 agent 会空转到墙钟超时、和 bypassScheduling 组合出双会话。
|
|
14501
14712
|
cancelPreviousWork: async (nodeId, currentWorkId) => {
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
if (prev && prev.deadAt) await this.io.cancelSession(prev.id);
|
|
14506
|
-
} catch (err) {
|
|
14507
|
-
console.warn(`[bus] cancelPreviousWork ${nodeId}: ${String(err)}`);
|
|
14508
|
-
}
|
|
14713
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(record8.workorderId));
|
|
14714
|
+
const prev = snap?.works.filter((w2) => w2.nodeId === nodeId && w2.id !== currentWorkId).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt))[0];
|
|
14715
|
+
if (prev && prev.deadAt) await this.io.cancelSession(prev.id);
|
|
14509
14716
|
},
|
|
14510
14717
|
// ★ 设计 §workorder.sealed/paused「按 work.kill 逻辑杀」:killAllWorks 只置状态,会话取消在这里做——
|
|
14511
14718
|
// apply 已把被杀 work/review 置 deadAt/cancelledAt,这里按 store 找它们仍挂着的会话 cancel
|
|
@@ -14757,6 +14964,7 @@ function nodeToArtifact(node, edges, requirements) {
|
|
|
14757
14964
|
if (cleanFields) {
|
|
14758
14965
|
if ("reviewRoles" in cleanFields) delete cleanFields.reviewRoles;
|
|
14759
14966
|
if ("docType" in cleanFields) delete cleanFields.docType;
|
|
14967
|
+
if (BUSINESS_HANDOFF_POLICY_FIELD in cleanFields) delete cleanFields[BUSINESS_HANDOFF_POLICY_FIELD];
|
|
14760
14968
|
}
|
|
14761
14969
|
return {
|
|
14762
14970
|
id: node.id,
|
|
@@ -16779,7 +16987,11 @@ var init_kernel_bridge = __esm({
|
|
|
16779
16987
|
const spawnTypeDef = this.schema.get(op.type);
|
|
16780
16988
|
const spawnOwner = op.owner ?? (spawnTypeDef?.ownerRole ? this.actorForRole(spawnTypeDef.ownerRole) : _actor);
|
|
16781
16989
|
const opFields = op.fields;
|
|
16782
|
-
const mergedFields = opReviewRoles !== void 0
|
|
16990
|
+
const mergedFields = opReviewRoles !== void 0 || op.businessHandoffPolicy !== void 0 ? {
|
|
16991
|
+
...opFields ?? {},
|
|
16992
|
+
...opReviewRoles !== void 0 ? { reviewRoles: opReviewRoles } : {},
|
|
16993
|
+
...op.businessHandoffPolicy !== void 0 ? { [BUSINESS_HANDOFF_POLICY_FIELD]: op.businessHandoffPolicy } : {}
|
|
16994
|
+
} : opFields;
|
|
16783
16995
|
await this.commit({
|
|
16784
16996
|
companyId: "",
|
|
16785
16997
|
workorderId: wid,
|
|
@@ -38635,7 +38847,7 @@ var require_websocket = __commonJS({
|
|
|
38635
38847
|
var http2 = require("http");
|
|
38636
38848
|
var net = require("net");
|
|
38637
38849
|
var tls = require("tls");
|
|
38638
|
-
var { randomBytes: randomBytes6, createHash:
|
|
38850
|
+
var { randomBytes: randomBytes6, createHash: createHash16 } = require("crypto");
|
|
38639
38851
|
var { Duplex, Readable } = require("stream");
|
|
38640
38852
|
var { URL: URL2 } = require("url");
|
|
38641
38853
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -39303,7 +39515,7 @@ var require_websocket = __commonJS({
|
|
|
39303
39515
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
39304
39516
|
return;
|
|
39305
39517
|
}
|
|
39306
|
-
const digest =
|
|
39518
|
+
const digest = createHash16("sha1").update(key + GUID).digest("base64");
|
|
39307
39519
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
39308
39520
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
39309
39521
|
return;
|
|
@@ -39672,7 +39884,7 @@ var require_websocket_server = __commonJS({
|
|
|
39672
39884
|
var EventEmitter = require("events");
|
|
39673
39885
|
var http2 = require("http");
|
|
39674
39886
|
var { Duplex } = require("stream");
|
|
39675
|
-
var { createHash:
|
|
39887
|
+
var { createHash: createHash16 } = require("crypto");
|
|
39676
39888
|
var extension2 = require_extension();
|
|
39677
39889
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
39678
39890
|
var subprotocol2 = require_subprotocol();
|
|
@@ -39979,7 +40191,7 @@ var require_websocket_server = __commonJS({
|
|
|
39979
40191
|
);
|
|
39980
40192
|
}
|
|
39981
40193
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
39982
|
-
const digest =
|
|
40194
|
+
const digest = createHash16("sha1").update(key + GUID).digest("base64");
|
|
39983
40195
|
const headers = [
|
|
39984
40196
|
"HTTP/1.1 101 Switching Protocols",
|
|
39985
40197
|
"Upgrade: websocket",
|
|
@@ -147081,7 +147293,8 @@ function withClosureReport(plan, opts) {
|
|
|
147081
147293
|
title: CLOSURE_REPORT_TITLE,
|
|
147082
147294
|
description: closureReportTaskBook(),
|
|
147083
147295
|
inputs: business.map((op) => ({ to: op.id, required: true })),
|
|
147084
|
-
isFinalOutput: true
|
|
147296
|
+
isFinalOutput: true,
|
|
147297
|
+
businessHandoffPolicy: BUSINESS_HANDOFF_POLICY_EXEMPT
|
|
147085
147298
|
}
|
|
147086
147299
|
]
|
|
147087
147300
|
};
|
|
@@ -147091,6 +147304,7 @@ var init_closure_report = __esm({
|
|
|
147091
147304
|
"../server/src/domains/collab/closure-report.ts"() {
|
|
147092
147305
|
"use strict";
|
|
147093
147306
|
init_src();
|
|
147307
|
+
init_src2();
|
|
147094
147308
|
init_src3();
|
|
147095
147309
|
init_src3();
|
|
147096
147310
|
CLOSURE_SPAWN = (op) => op.action === "spawn" && op.type === DELIVERY_SUMMARY_ARTIFACT_TYPE;
|
|
@@ -163072,7 +163286,8 @@ async function startOasisServer(opts) {
|
|
|
163072
163286
|
{ nodeId: currentRuntimeId, runtimeKind: currentRuntimeKind }
|
|
163073
163287
|
);
|
|
163074
163288
|
const persistedUserMessage = persistTarget ? stripInjectedChatContext(body.message) : body.message;
|
|
163075
|
-
const
|
|
163289
|
+
const needsHistoryInjection = Boolean(persistTarget && (runtimeChanged || !persistTarget.runtimeSessionId));
|
|
163290
|
+
const history = needsHistoryInjection && chatStore && persistTarget ? (await chatStore.listMessages(persistTarget.id, 20)).filter((message) => message.role !== "system" && !(message.role === "assistant" && message.status === "running")).map((message) => ({
|
|
163076
163291
|
role: message.role,
|
|
163077
163292
|
content: message.role === "user" ? stripInjectedChatContext(message.content) : message.content
|
|
163078
163293
|
})) : [];
|
|
@@ -164761,8 +164976,12 @@ function mergeContinuityRefs(...lists) {
|
|
|
164761
164976
|
}
|
|
164762
164977
|
return [...refs.values()];
|
|
164763
164978
|
}
|
|
164764
|
-
function systemHandoffFor(run) {
|
|
164979
|
+
function systemHandoffFor(run, checkpoint = null, effects = []) {
|
|
164765
164980
|
const outcome = handoffOutcomeOf(run.status);
|
|
164981
|
+
const terminalReason = run.errorMessage ?? run.exitReason ?? `status=${outcome}`;
|
|
164982
|
+
const pendingEffects = effects.filter((effect) => effect.status === "pending").length;
|
|
164983
|
+
const failedEffects = effects.filter((effect) => effect.status === "failed").length;
|
|
164984
|
+
const progress = checkpoint ? `checkpoint=${checkpoint.checkpointId} completed=${checkpoint.completedStepIds.length} pending=${checkpoint.pendingSteps.length} recoverability=${checkpoint.recoverability}` : "no valid pre-terminal checkpoint";
|
|
164766
164985
|
return {
|
|
164767
164986
|
handoffId: `handoff:${run.id}`,
|
|
164768
164987
|
attemptId: run.id,
|
|
@@ -164770,12 +164989,16 @@ function systemHandoffFor(run) {
|
|
|
164770
164989
|
part: run.part,
|
|
164771
164990
|
producedBy: "system_reconstructed",
|
|
164772
164991
|
outcome,
|
|
164773
|
-
summary:
|
|
164992
|
+
summary: `[system_reconstructed] attempt=${run.id} outcome=${outcome} reason=${terminalReason}; ${progress}`.slice(0, 400),
|
|
164774
164993
|
changedOutputs: structuredClone(run.outputRefs),
|
|
164775
164994
|
unresolvedIssues: [],
|
|
164776
|
-
risks:
|
|
164777
|
-
|
|
164778
|
-
|
|
164995
|
+
risks: [
|
|
164996
|
+
...run.errorMessage ? [run.errorMessage.slice(0, 400)] : [],
|
|
164997
|
+
...pendingEffects || failedEffects ? [`external effects: pending=${pendingEffects}, failed=${failedEffects}`] : []
|
|
164998
|
+
],
|
|
164999
|
+
nextActions: structuredClone(checkpoint?.pendingSteps ?? []),
|
|
165000
|
+
verificationGaps: checkpoint ? [] : ["No valid pre-terminal checkpoint was available."],
|
|
165001
|
+
userHandoff: null,
|
|
164779
165002
|
createdAt: run.endedAt ?? run.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
164780
165003
|
};
|
|
164781
165004
|
}
|
|
@@ -164877,11 +165100,9 @@ var init_memory_trace_store = __esm({
|
|
|
164877
165100
|
if (merged.nodeId && isTerminalStatus(merged.status)) {
|
|
164878
165101
|
this.ensureTerminalCheckpointFor(merged);
|
|
164879
165102
|
const existingHandoff = this.handoffs.get(id);
|
|
164880
|
-
const
|
|
164881
|
-
|
|
164882
|
-
|
|
164883
|
-
changedOutputs: structuredClone(existingHandoff.producedBy === "agent" ? existingHandoff.changedOutputs : merged.outputRefs)
|
|
164884
|
-
} : systemHandoffFor(merged);
|
|
165103
|
+
const checkpoint = (this.checkpoints.get(id) ?? []).filter((item) => item.trigger !== "attempt_terminal").at(-1) ?? null;
|
|
165104
|
+
const effects = [...this.externalEffects.values()].filter((effect) => effect.attemptId === id);
|
|
165105
|
+
const handoff = existingHandoff?.producedBy === "agent" ? existingHandoff : systemHandoffFor(merged, checkpoint, effects);
|
|
164885
165106
|
this.handoffs.set(id, handoff);
|
|
164886
165107
|
merged.handoffId = handoff.handoffId;
|
|
164887
165108
|
}
|
|
@@ -165074,6 +165295,46 @@ var init_memory_trace_store = __esm({
|
|
|
165074
165295
|
async listExternalEffects(nodeId) {
|
|
165075
165296
|
return [...this.externalEffects.values()].filter((effect) => effect.nodeId === nodeId).sort((a, b2) => (a.occurredAt ?? "").localeCompare(b2.occurredAt ?? "") || a.idempotencyKey.localeCompare(b2.idempotencyKey)).map((effect) => structuredClone(effect));
|
|
165076
165297
|
}
|
|
165298
|
+
sortedExternalEffects(nodeId, status) {
|
|
165299
|
+
const recency = (effect) => {
|
|
165300
|
+
if (effect.occurredAt) return effect.occurredAt;
|
|
165301
|
+
const run = this.runs.get(effect.attemptId);
|
|
165302
|
+
return run?.endedAt ?? run?.updatedAt ?? run?.startedAt ?? "";
|
|
165303
|
+
};
|
|
165304
|
+
return [...this.externalEffects.values()].filter((effect) => effect.nodeId === nodeId && (status === void 0 || effect.status === status)).sort((a, b2) => recency(b2).localeCompare(recency(a)) || b2.idempotencyKey.localeCompare(a.idempotencyKey)).map((effect) => structuredClone(effect));
|
|
165305
|
+
}
|
|
165306
|
+
async getExternalEffectsForResume(nodeId, jobKey, part, succeededLimit, failedLimit) {
|
|
165307
|
+
const inScope = (effect) => {
|
|
165308
|
+
const run = this.runs.get(effect.attemptId);
|
|
165309
|
+
return Boolean(run && executionJobKey(run) === jobKey && run.part === part);
|
|
165310
|
+
};
|
|
165311
|
+
const pending = this.sortedExternalEffects(nodeId, "pending").filter(inScope);
|
|
165312
|
+
const succeeded = this.sortedExternalEffects(nodeId, "succeeded").filter(inScope);
|
|
165313
|
+
const failed = this.sortedExternalEffects(nodeId, "failed").filter(inScope);
|
|
165314
|
+
const includedSucceeded = succeeded.slice(0, succeededLimit);
|
|
165315
|
+
const includedFailed = failed.slice(0, failedLimit);
|
|
165316
|
+
return {
|
|
165317
|
+
effects: [...pending, ...includedSucceeded, ...includedFailed],
|
|
165318
|
+
summary: {
|
|
165319
|
+
total: pending.length + succeeded.length + failed.length,
|
|
165320
|
+
totalByStatus: { pending: pending.length, succeeded: succeeded.length, failed: failed.length },
|
|
165321
|
+
includedByStatus: {
|
|
165322
|
+
pending: pending.length,
|
|
165323
|
+
succeeded: includedSucceeded.length,
|
|
165324
|
+
failed: includedFailed.length
|
|
165325
|
+
}
|
|
165326
|
+
}
|
|
165327
|
+
};
|
|
165328
|
+
}
|
|
165329
|
+
async queryExternalEffects(nodeId, options) {
|
|
165330
|
+
const effects = this.sortedExternalEffects(nodeId, options.status);
|
|
165331
|
+
return {
|
|
165332
|
+
total: effects.length,
|
|
165333
|
+
offset: options.offset,
|
|
165334
|
+
limit: options.limit,
|
|
165335
|
+
items: effects.slice(options.offset, options.offset + options.limit)
|
|
165336
|
+
};
|
|
165337
|
+
}
|
|
165077
165338
|
async putHandoff(input) {
|
|
165078
165339
|
const run = this.runs.get(input.attemptId);
|
|
165079
165340
|
if (!run?.nodeId) throw new ExecutionAttemptNotFoundError(input.attemptId);
|
|
@@ -165095,6 +165356,11 @@ var init_memory_trace_store = __esm({
|
|
|
165095
165356
|
risks: structuredClone(input.risks ?? []),
|
|
165096
165357
|
nextActions: structuredClone(input.nextActions ?? []),
|
|
165097
165358
|
verificationGaps: structuredClone(input.verificationGaps ?? []),
|
|
165359
|
+
userHandoff: normalizeUserHandoff(
|
|
165360
|
+
input.outcome,
|
|
165361
|
+
input.changedOutputs ?? run.outputRefs,
|
|
165362
|
+
input.userHandoff
|
|
165363
|
+
),
|
|
165098
165364
|
createdAt: input.createdAt ?? this.now()
|
|
165099
165365
|
};
|
|
165100
165366
|
this.handoffs.set(input.attemptId, handoff);
|
|
@@ -165145,13 +165411,17 @@ var init_memory_trace_store = __esm({
|
|
|
165145
165411
|
this.handoffs.set(handoff.attemptId, structuredClone({
|
|
165146
165412
|
...handoff,
|
|
165147
165413
|
jobKey: handoff.jobKey ?? (run ? executionJobKey(run) : `attempt:${handoff.attemptId}`),
|
|
165148
|
-
part: handoff.part ?? run?.part ?? null
|
|
165414
|
+
part: handoff.part ?? run?.part ?? null,
|
|
165415
|
+
userHandoff: handoff.userHandoff ?? null
|
|
165149
165416
|
}));
|
|
165150
165417
|
}
|
|
165151
165418
|
for (const [id, run] of this.runs) {
|
|
165152
165419
|
if (!run.nodeId || !isTerminalStatus(run.status)) continue;
|
|
165153
165420
|
this.ensureTerminalCheckpointFor(run);
|
|
165154
|
-
const
|
|
165421
|
+
const existingHandoff = this.handoffs.get(id);
|
|
165422
|
+
const checkpoint = (this.checkpoints.get(id) ?? []).filter((item) => item.trigger !== "attempt_terminal").at(-1) ?? null;
|
|
165423
|
+
const effects = [...this.externalEffects.values()].filter((effect) => effect.attemptId === id);
|
|
165424
|
+
const handoff = existingHandoff?.producedBy === "agent" ? existingHandoff : systemHandoffFor(run, checkpoint, effects);
|
|
165155
165425
|
this.handoffs.set(id, handoff);
|
|
165156
165426
|
this.runs.set(id, { ...run, handoffId: handoff.handoffId });
|
|
165157
165427
|
}
|
|
@@ -165936,6 +166206,13 @@ var init_test_engine = __esm({
|
|
|
165936
166206
|
}
|
|
165937
166207
|
});
|
|
165938
166208
|
|
|
166209
|
+
// ../testkit/src/engine-handoff.ts
|
|
166210
|
+
var init_engine_handoff = __esm({
|
|
166211
|
+
"../testkit/src/engine-handoff.ts"() {
|
|
166212
|
+
"use strict";
|
|
166213
|
+
}
|
|
166214
|
+
});
|
|
166215
|
+
|
|
165939
166216
|
// ../testkit/src/index.ts
|
|
165940
166217
|
var init_src7 = __esm({
|
|
165941
166218
|
"../testkit/src/index.ts"() {
|
|
@@ -165952,6 +166229,7 @@ var init_src7 = __esm({
|
|
|
165952
166229
|
init_memory_control_plane_store();
|
|
165953
166230
|
init_memory_actor_memory_store();
|
|
165954
166231
|
init_test_engine();
|
|
166232
|
+
init_engine_handoff();
|
|
165955
166233
|
}
|
|
165956
166234
|
});
|
|
165957
166235
|
|
|
@@ -170212,7 +170490,9 @@ var init_sink = __esm({
|
|
|
170212
170490
|
// 「这条 run 跑在哪个 cwd」才有据可查,Session 列也不再对 dispatch run 恒空。
|
|
170213
170491
|
baseMetadata: {
|
|
170214
170492
|
bundleManifest: session.bundleManifest,
|
|
170215
|
-
...session.runtimeSessionId ? { runtimeSessionId: session.runtimeSessionId } : {}
|
|
170493
|
+
...session.runtimeSessionId ? { runtimeSessionId: session.runtimeSessionId } : {},
|
|
170494
|
+
...session.engineWorkId ? { engineWorkId: session.engineWorkId } : {},
|
|
170495
|
+
...session.engineCompanyId ? { engineCompanyId: session.engineCompanyId } : {}
|
|
170216
170496
|
},
|
|
170217
170497
|
observedModel: null,
|
|
170218
170498
|
checkpointState: {},
|
|
@@ -170427,7 +170707,21 @@ var init_sink = __esm({
|
|
|
170427
170707
|
stream: "runtime",
|
|
170428
170708
|
message: `exit code=${update.exit.code ?? "killed"} ops=${update.opRefs.length}`
|
|
170429
170709
|
}]);
|
|
170430
|
-
|
|
170710
|
+
let terminalDurableRefs = [];
|
|
170711
|
+
let checkpointPrevious = null;
|
|
170712
|
+
if (this.continuityMode !== "off" && this.store.appendCheckpoint && this.store.listCheckpoints) {
|
|
170713
|
+
checkpointPrevious = await this.latestCheckpoint(st);
|
|
170714
|
+
const terminalInput = this.checkpointInput(st, checkpointPrevious, "attempt_terminal", update.exit.at);
|
|
170715
|
+
terminalDurableRefs = terminalInput.durableRefs;
|
|
170716
|
+
}
|
|
170717
|
+
const opOutputRefs = update.opRefs.map((ref2) => ({
|
|
170718
|
+
kind: "op",
|
|
170719
|
+
id: ref2.id,
|
|
170720
|
+
version: String(ref2.seq),
|
|
170721
|
+
label: ref2.kind
|
|
170722
|
+
}));
|
|
170723
|
+
const hasDurableOutput = update.opRefs.length > 0 || terminalDurableRefs.length > 0;
|
|
170724
|
+
const status = update.exit.wallclockKilled ? "timeout" : update.exit.reason === "timeout" ? "timeout" : update.exit.reason === "cancelled" ? "cancelled" : update.exit.reason === "output-limit" && hasDurableOutput ? "partial" : update.exit.reason && update.exit.reason !== "clean" || update.exit.code !== null && update.exit.code !== 0 ? "failed" : hasDurableOutput ? "succeeded" : update.exit.reason === "clean" ? "no-output" : "failed";
|
|
170431
170725
|
const isErr = status === "failed" || status === "timeout";
|
|
170432
170726
|
const errorFields = isErr ? { errorCode: update.exit.reason ?? "error", errorMessage: formatExitError(update.exit) } : {};
|
|
170433
170727
|
const model = typeof update.exit.model === "string" && update.exit.model.trim() ? update.exit.model.trim() : st.observedModel;
|
|
@@ -170435,17 +170729,16 @@ var init_sink = __esm({
|
|
|
170435
170729
|
if (exitRtId) st.baseMetadata = { ...st.baseMetadata, runtimeSessionId: exitRtId };
|
|
170436
170730
|
const exitWorkdir = typeof update.exit.workdirRef === "string" && update.exit.workdirRef.trim() ? update.exit.workdirRef.trim() : void 0;
|
|
170437
170731
|
if (exitWorkdir) st.baseMetadata = { ...st.baseMetadata, workdirRef: exitWorkdir };
|
|
170438
|
-
const outputRefs =
|
|
170439
|
-
|
|
170440
|
-
id
|
|
170441
|
-
|
|
170442
|
-
|
|
170443
|
-
}
|
|
170732
|
+
const outputRefs = opOutputRefs.slice();
|
|
170733
|
+
for (const ref2 of terminalDurableRefs) {
|
|
170734
|
+
if (!outputRefs.some((item) => item.kind === ref2.kind && item.id === ref2.id && item.version === ref2.version)) {
|
|
170735
|
+
outputRefs.push(ref2);
|
|
170736
|
+
}
|
|
170737
|
+
}
|
|
170444
170738
|
if (this.continuityMode !== "off" && this.store.appendCheckpoint && this.store.listCheckpoints) {
|
|
170445
|
-
const
|
|
170446
|
-
const terminalInput = this.checkpointInput(st, previous, "attempt_terminal", update.exit.at);
|
|
170739
|
+
const terminalInput = this.checkpointInput(st, checkpointPrevious, "attempt_terminal", update.exit.at);
|
|
170447
170740
|
const durableRefs = [...terminalInput.durableRefs];
|
|
170448
|
-
for (const ref2 of
|
|
170741
|
+
for (const ref2 of opOutputRefs) {
|
|
170449
170742
|
if (!durableRefs.some((item) => item.kind === ref2.kind && item.id === ref2.id && item.version === ref2.version)) {
|
|
170450
170743
|
durableRefs.push(ref2);
|
|
170451
170744
|
}
|
|
@@ -170455,7 +170748,7 @@ var init_sink = __esm({
|
|
|
170455
170748
|
pendingSteps: terminalInput.pendingSteps,
|
|
170456
170749
|
completedStepIds: terminalInput.completedStepIds,
|
|
170457
170750
|
durableRefs,
|
|
170458
|
-
recoverability:
|
|
170751
|
+
recoverability: hasDurableOutput ? "full" : terminalInput.recoverability,
|
|
170459
170752
|
trigger: "attempt_terminal",
|
|
170460
170753
|
createdAt: update.exit.at
|
|
170461
170754
|
});
|
|
@@ -170465,7 +170758,10 @@ var init_sink = __esm({
|
|
|
170465
170758
|
endedAt: update.exit.at,
|
|
170466
170759
|
durationMs: update.exit.durationMs,
|
|
170467
170760
|
exitCode: update.exit.code,
|
|
170468
|
-
|
|
170761
|
+
// Attempt terminal => NodeHandoff is unconditional, including the `off` cohort. Keep the
|
|
170762
|
+
// durable output facts on the Attempt in every cohort so storage can reconstruct an honest
|
|
170763
|
+
// system handoff after an abnormal exit without depending on the continuity experiment.
|
|
170764
|
+
outputRefs,
|
|
170469
170765
|
// 退出事件带回的真实 runtime 会话 id 属血统,不随实验开关丢弃(ADR-0131)。
|
|
170470
170766
|
...exitRtId ? { runtimeSessionId: exitRtId } : {},
|
|
170471
170767
|
...errorFields,
|
|
@@ -180602,6 +180898,11 @@ function isRefArray(value) {
|
|
|
180602
180898
|
return typeof ref2["kind"] === "string" && typeof ref2["id"] === "string" && (ref2["version"] === void 0 || typeof ref2["version"] === "string") && (ref2["label"] === void 0 || typeof ref2["label"] === "string");
|
|
180603
180899
|
});
|
|
180604
180900
|
}
|
|
180901
|
+
function isUserHandoff(value) {
|
|
180902
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
180903
|
+
const handoff = value;
|
|
180904
|
+
return (handoff["status"] === "delivered" || handoff["status"] === "not_delivered") && typeof handoff["summary"] === "string" && handoff["summary"].trim().length > 0 && typeof handoff["nextStep"] === "string" && isRefArray(handoff["deliverables"]);
|
|
180905
|
+
}
|
|
180605
180906
|
function resolveAttemptWriter(req, requestedAttemptId) {
|
|
180606
180907
|
const dispatchId = req.auth.dispatch?.dispatchId;
|
|
180607
180908
|
if (requestedAttemptId === "current") {
|
|
@@ -180621,6 +180922,11 @@ function executionContinuityDomain(service) {
|
|
|
180621
180922
|
if (!view) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u5DE5\u5355\uFF1A${req.params.id}`);
|
|
180622
180923
|
return { status: 200, body: view };
|
|
180623
180924
|
});
|
|
180925
|
+
router.get("/api/execution-continuity/attempts/:id/recovery", async (req) => {
|
|
180926
|
+
const view = await service.recoveryView(req.params.id, req.auth.companyId);
|
|
180927
|
+
if (!view) throw new ApiError(404, "NOT_FOUND", `\u8BE5 Attempt \u6CA1\u6709\u5DF2\u6CE8\u5165\u7684\u91CD\u6D3E ResumePack\uFF1A${req.params.id}`);
|
|
180928
|
+
return { status: 200, body: view };
|
|
180929
|
+
});
|
|
180624
180930
|
router.get("/api/execution-continuity/workorders/:id/nodes/:nodeId/resume-pack", async (req) => {
|
|
180625
180931
|
const pack = await service.resumePack(
|
|
180626
180932
|
req.params.id,
|
|
@@ -180631,6 +180937,25 @@ function executionContinuityDomain(service) {
|
|
|
180631
180937
|
if (!pack) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u8282\u70B9\uFF1A${req.params.nodeId}`);
|
|
180632
180938
|
return { status: 200, body: pack };
|
|
180633
180939
|
});
|
|
180940
|
+
router.get("/api/execution-continuity/workorders/:id/nodes/:nodeId/external-effects", async (req) => {
|
|
180941
|
+
const statusRaw = req.query.get("status");
|
|
180942
|
+
if (statusRaw !== null && !EXTERNAL_EFFECT_STATUSES.includes(statusRaw)) {
|
|
180943
|
+
throw new ApiError(400, "BAD_REQUEST", "status \u5FC5\u987B\u662F pending / succeeded / failed");
|
|
180944
|
+
}
|
|
180945
|
+
const limit = Number(req.query.get("limit") ?? "50");
|
|
180946
|
+
const offset = Number(req.query.get("offset") ?? "0");
|
|
180947
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200 || !Number.isInteger(offset) || offset < 0) {
|
|
180948
|
+
throw new ApiError(400, "BAD_REQUEST", "limit \u5FC5\u987B\u662F 1..200\uFF0Coffset \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570");
|
|
180949
|
+
}
|
|
180950
|
+
const page = await service.externalEffectHistory(
|
|
180951
|
+
req.params.id,
|
|
180952
|
+
req.params.nodeId,
|
|
180953
|
+
req.auth.companyId,
|
|
180954
|
+
{ ...statusRaw ? { status: statusRaw } : {}, limit, offset }
|
|
180955
|
+
);
|
|
180956
|
+
if (!page) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u8282\u70B9\uFF1A${req.params.nodeId}`);
|
|
180957
|
+
return { status: 200, body: page };
|
|
180958
|
+
});
|
|
180634
180959
|
router.post("/api/execution-continuity/attempts/:id/checkpoints", async (req) => {
|
|
180635
180960
|
const attemptId = resolveAttemptWriter(req, req.params.id);
|
|
180636
180961
|
const b2 = req.body ?? {};
|
|
@@ -180705,6 +181030,9 @@ function executionContinuityDomain(service) {
|
|
|
180705
181030
|
if (b2.changedOutputs !== void 0 && !isRefArray(b2.changedOutputs) || b2.unresolvedIssues !== void 0 && !isStringArray(b2.unresolvedIssues) || b2.risks !== void 0 && !isStringArray(b2.risks) || b2.nextActions !== void 0 && !isStringArray(b2.nextActions) || b2.verificationGaps !== void 0 && !isStringArray(b2.verificationGaps)) {
|
|
180706
181031
|
throw new ApiError(400, "BAD_REQUEST", "handoff arrays contain invalid values");
|
|
180707
181032
|
}
|
|
181033
|
+
if (!isUserHandoff(b2.userHandoff)) {
|
|
181034
|
+
throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11\u5408\u6CD5\u7684 userHandoff\uFF1BAgent \u5FC5\u987B\u63D0\u4EA4\u672C\u6B21\u5DE5\u4F5C\u7684\u4E1A\u52A1\u4EA4\u63A5\u6458\u8981");
|
|
181035
|
+
}
|
|
180708
181036
|
const handoff = await service.handoff({
|
|
180709
181037
|
attemptId,
|
|
180710
181038
|
producedBy: "agent",
|
|
@@ -180714,13 +181042,14 @@ function executionContinuityDomain(service) {
|
|
|
180714
181042
|
unresolvedIssues: Array.isArray(b2.unresolvedIssues) ? b2.unresolvedIssues : [],
|
|
180715
181043
|
risks: Array.isArray(b2.risks) ? b2.risks : [],
|
|
180716
181044
|
nextActions: Array.isArray(b2.nextActions) ? b2.nextActions : [],
|
|
180717
|
-
verificationGaps: Array.isArray(b2.verificationGaps) ? b2.verificationGaps : []
|
|
181045
|
+
verificationGaps: Array.isArray(b2.verificationGaps) ? b2.verificationGaps : [],
|
|
181046
|
+
userHandoff: b2.userHandoff
|
|
180718
181047
|
});
|
|
180719
181048
|
return { status: 200, body: handoff };
|
|
180720
181049
|
});
|
|
180721
181050
|
};
|
|
180722
181051
|
}
|
|
180723
|
-
var HANDOFF_OUTCOMES, AGENT_CHECKPOINT_TRIGGERS;
|
|
181052
|
+
var HANDOFF_OUTCOMES, AGENT_CHECKPOINT_TRIGGERS, EXTERNAL_EFFECT_STATUSES;
|
|
180724
181053
|
var init_routes8 = __esm({
|
|
180725
181054
|
"../server/src/domains/execution-continuity/routes.ts"() {
|
|
180726
181055
|
"use strict";
|
|
@@ -180734,6 +181063,7 @@ var init_routes8 = __esm({
|
|
|
180734
181063
|
"orphaned"
|
|
180735
181064
|
];
|
|
180736
181065
|
AGENT_CHECKPOINT_TRIGGERS = ["agent", "plan_completed", "stage_completed"];
|
|
181066
|
+
EXTERNAL_EFFECT_STATUSES = ["pending", "succeeded", "failed"];
|
|
180737
181067
|
}
|
|
180738
181068
|
});
|
|
180739
181069
|
|
|
@@ -180746,15 +181076,8 @@ function renderResumePack(pack) {
|
|
|
180746
181076
|
`Node: ${pack.node.title} (${pack.node.nodeId})`,
|
|
180747
181077
|
`Job: ${pack.jobKey ?? "unspecified"}`,
|
|
180748
181078
|
`Part: ${pack.part ?? "whole-node"}`,
|
|
180749
|
-
pack.node.description ? `Goal: ${pack.node.description}` : ""
|
|
180750
|
-
"",
|
|
180751
|
-
"## Upstream handoffs"
|
|
181079
|
+
pack.node.description ? `Goal: ${pack.node.description}` : ""
|
|
180752
181080
|
].filter(Boolean);
|
|
180753
|
-
if (pack.upstream.length === 0) lines.push("- None");
|
|
180754
|
-
for (const item of pack.upstream) {
|
|
180755
|
-
lines.push(`- ${item.node.title}: ${item.handoff?.summary || "No terminal handoff yet"}`);
|
|
180756
|
-
for (const issue2 of item.handoff?.unresolvedIssues ?? []) lines.push(` - unresolved: ${issue2}`);
|
|
180757
|
-
}
|
|
180758
181081
|
lines.push("", "## Previous attempt");
|
|
180759
181082
|
if (!pack.previousAttempt) lines.push("- This is the first attempt.");
|
|
180760
181083
|
else {
|
|
@@ -180766,14 +181089,40 @@ function renderResumePack(pack) {
|
|
|
180766
181089
|
}
|
|
180767
181090
|
}
|
|
180768
181091
|
lines.push("", "## External effects");
|
|
180769
|
-
|
|
181092
|
+
const effectSummary = pack.externalEffectSummary;
|
|
181093
|
+
if (effectSummary.total === 0) lines.push("- None recorded");
|
|
181094
|
+
else {
|
|
181095
|
+
const totals = effectSummary.totalByStatus;
|
|
181096
|
+
const included = effectSummary.includedByStatus;
|
|
181097
|
+
lines.push(
|
|
181098
|
+
`- ${effectSummary.total} total; showing all ${included.pending} pending, latest ${included.succeeded} of ${totals.succeeded} succeeded, and latest ${included.failed} of ${totals.failed} retryable failed. Older records remain available through the history query.`
|
|
181099
|
+
);
|
|
181100
|
+
}
|
|
180770
181101
|
for (const effect of pack.externalEffects) {
|
|
180771
181102
|
const action = effect.status === "succeeded" ? "do not repeat" : effect.status === "pending" ? "execution uncertain; reconcile before retry" : "failed; retry allowed";
|
|
180772
181103
|
lines.push(`- [${effect.status}/${effect.source}] ${effect.kind} \u2192 ${effect.target} (${action}; key ${effect.idempotencyKey})`);
|
|
180773
181104
|
}
|
|
181105
|
+
const snapshot = Buffer.from(JSON.stringify(pack), "utf8").toString("base64url");
|
|
181106
|
+
lines.push("", `${RESUME_SNAPSHOT_PREFIX}${snapshot}${RESUME_SNAPSHOT_SUFFIX}`);
|
|
180774
181107
|
return `${lines.join("\n")}
|
|
180775
181108
|
`;
|
|
180776
181109
|
}
|
|
181110
|
+
function parseResumePackSnapshot(content) {
|
|
181111
|
+
const start = content.lastIndexOf(RESUME_SNAPSHOT_PREFIX);
|
|
181112
|
+
if (start < 0) return null;
|
|
181113
|
+
const encodedStart = start + RESUME_SNAPSHOT_PREFIX.length;
|
|
181114
|
+
const end = content.indexOf(RESUME_SNAPSHOT_SUFFIX, encodedStart);
|
|
181115
|
+
if (end < 0) return null;
|
|
181116
|
+
try {
|
|
181117
|
+
const value = JSON.parse(Buffer.from(content.slice(encodedStart, end), "base64url").toString("utf8"));
|
|
181118
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
181119
|
+
const pack = value;
|
|
181120
|
+
if (pack.scenario !== "first-run" && pack.scenario !== "redispatch" || !pack.node || typeof pack.node.nodeId !== "string" || !Array.isArray(pack.node.dependsOn) || !Array.isArray(pack.upstream) || !Array.isArray(pack.externalEffects) || !pack.externalEffectSummary) return null;
|
|
181121
|
+
return pack;
|
|
181122
|
+
} catch {
|
|
181123
|
+
return null;
|
|
181124
|
+
}
|
|
181125
|
+
}
|
|
180777
181126
|
function renderCoordinatorView(view) {
|
|
180778
181127
|
const lines = [
|
|
180779
181128
|
"# Coordinator View",
|
|
@@ -180793,6 +181142,22 @@ function renderCoordinatorView(view) {
|
|
|
180793
181142
|
`- State: ${node.nodeState}; attempt status: ${node.status}; attempt: ${node.attemptNo || "none"}`,
|
|
180794
181143
|
`- Actor: ${node.actorId ?? "unassigned"}; role: ${node.role ?? "unassigned"}`
|
|
180795
181144
|
);
|
|
181145
|
+
if (node.recoverySource) {
|
|
181146
|
+
const source = node.recoverySource;
|
|
181147
|
+
lines.push(
|
|
181148
|
+
`- Recovery source: Attempt ${source.recoveredFromAttempt.attemptNo ?? "?"} \u2192 Attempt ${source.currentAttempt.attemptNo ?? "?"}`
|
|
181149
|
+
);
|
|
181150
|
+
if (source.recoveryCheckpoint) {
|
|
181151
|
+
lines.push(` - Recovery checkpoint: #${source.recoveryCheckpoint.seq} (${source.recoveryCheckpoint.recoverability})`);
|
|
181152
|
+
for (const step of source.recoveryCheckpoint.pendingSteps) lines.push(` - Recovery pending: ${step}`);
|
|
181153
|
+
}
|
|
181154
|
+
if (source.recoveryHandoff?.producedBy === "agent") {
|
|
181155
|
+
lines.push(` - Recovery Agent handoff: ${source.recoveryHandoff.summary}`);
|
|
181156
|
+
} else if (source.recoveryHandoff?.producedBy === "system_reconstructed") {
|
|
181157
|
+
lines.push(` - Recovery system reconstruction (not an Agent conclusion): ${source.recoveryHandoff.summary}`);
|
|
181158
|
+
}
|
|
181159
|
+
for (const risk of source.recoveryHandoff?.risks ?? []) lines.push(` - Recovery risk: ${risk}`);
|
|
181160
|
+
}
|
|
180796
181161
|
if (node.blockedBy.length > 0) lines.push(`- Blocked by: ${node.blockedBy.join(", ")}`);
|
|
180797
181162
|
if (node.progress) {
|
|
180798
181163
|
lines.push(`- Latest checkpoint: ${node.progress.recoverability} at ${node.progress.lastCheckpointAt}`);
|
|
@@ -180847,12 +181212,42 @@ succeeds, use \`oasis continuity-effect\` with a stable key that the same node w
|
|
|
180847
181212
|
|
|
180848
181213
|
## Handoff
|
|
180849
181214
|
|
|
180850
|
-
Before terminal exit, run \`oasis continuity-handoff\` with a concise summary (maximum 400
|
|
181215
|
+
Before terminal exit, run \`oasis continuity-handoff\` with a concise technical summary (maximum 400
|
|
180851
181216
|
characters), changed durable outputs, unresolved issues, risks, next actions, and verification gaps.
|
|
180852
|
-
|
|
180853
|
-
|
|
181217
|
+
You must also provide \`--user-summary\` as short business-facing language for the people receiving
|
|
181218
|
+
the work. Say what you completed, what result you formed, and what downstream work can use; if not
|
|
181219
|
+
delivered, say only why and what must happen next. Use \`--user-next-step\` when downstream needs an
|
|
181220
|
+
instruction. List only openable business results in \`--user-deliverables\`; keep technical refs in
|
|
181221
|
+
\`--outputs\`, and never list conclude, gap, checkpoint, or op as a user deliverable. Do not use a
|
|
181222
|
+
generic success/output-count template or mention attempts, checkpoints, runtimes, recovery,
|
|
181223
|
+
infrastructure paths, or internal diagnostics there.
|
|
181224
|
+
The system reconstructs an internal handoff if the process dies first, but reconstructed content is
|
|
181225
|
+
never shown as a business handoff.
|
|
181226
|
+
`;
|
|
181227
|
+
}
|
|
181228
|
+
function renderBusinessHandoffProtocol() {
|
|
181229
|
+
return `# Business handoff protocol
|
|
181230
|
+
|
|
181231
|
+
Before terminal exit, run \`oasis continuity-handoff\`. The NodeHandoff and its userHandoff are a
|
|
181232
|
+
required completion barrier for business downstream work. A successful handoff must use outcome
|
|
181233
|
+
\`succeeded\`, include a non-empty \`--user-summary\` saying what was done, what result was formed,
|
|
181234
|
+
and what downstream can use, and may have zero files. If the work cannot be delivered, submit a
|
|
181235
|
+
non-success outcome with a user summary explaining why delivery failed and \`--user-next-step\`;
|
|
181236
|
+
that records the handoff but does not release downstream work. List only openable business results
|
|
181237
|
+
in \`--user-deliverables\`. Never use a generic success/output-count template or expose attempts,
|
|
181238
|
+
checkpoints, runtimes, recovery, infrastructure paths, or internal diagnostics in userHandoff.
|
|
180854
181239
|
`;
|
|
180855
181240
|
}
|
|
181241
|
+
function emptyExternalEffectProjection() {
|
|
181242
|
+
return {
|
|
181243
|
+
effects: [],
|
|
181244
|
+
summary: {
|
|
181245
|
+
total: 0,
|
|
181246
|
+
totalByStatus: { pending: 0, succeeded: 0, failed: 0 },
|
|
181247
|
+
includedByStatus: { pending: 0, succeeded: 0, failed: 0 }
|
|
181248
|
+
}
|
|
181249
|
+
};
|
|
181250
|
+
}
|
|
180856
181251
|
function latestAttemptByNode(attempts) {
|
|
180857
181252
|
const out = /* @__PURE__ */ new Map();
|
|
180858
181253
|
for (const attempt of attempts) {
|
|
@@ -180862,10 +181257,11 @@ function latestAttemptByNode(attempts) {
|
|
|
180862
181257
|
}
|
|
180863
181258
|
return out;
|
|
180864
181259
|
}
|
|
180865
|
-
function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs) {
|
|
181260
|
+
function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, openToolCall) {
|
|
180866
181261
|
if (attempt.status === "running" || attempt.status === "queued") {
|
|
180867
|
-
const
|
|
180868
|
-
|
|
181262
|
+
const lastActivityAt = attempt.lastProgressAt ?? checkpoint?.createdAt ?? attempt.startedAt;
|
|
181263
|
+
if (openToolCall) return "running";
|
|
181264
|
+
return now.getTime() - Date.parse(lastActivityAt) <= stalledAfterMs ? "running" : "stalled";
|
|
180869
181265
|
}
|
|
180870
181266
|
if (attempt.status === "succeeded") return "succeeded";
|
|
180871
181267
|
if (attempt.status === "no-output") return handoff?.unresolvedIssues.length ? "blocked" : "no_change";
|
|
@@ -180888,22 +181284,26 @@ function waitingDescendants(source, reverse, states) {
|
|
|
180888
181284
|
return [...found].sort();
|
|
180889
181285
|
}
|
|
180890
181286
|
function todoAction(node) {
|
|
180891
|
-
if (node.handoffProducer === "agent" && node.nextActions[0]) return `Agent Handoff
|
|
180892
|
-
if (node.nodeState === "ready") return "\
|
|
180893
|
-
if (node.nodeState === "blocked" && node.unresolvedIssues[0]) return `\
|
|
180894
|
-
if (!node.progress) return `\
|
|
180895
|
-
if (node.progress.recoverability === "full") return `\
|
|
180896
|
-
if (node.progress.recoverability === "partial") return `\
|
|
180897
|
-
return `\
|
|
180898
|
-
}
|
|
180899
|
-
var DONE_STATES, BLOCKING_STATES, HANDOFF_OUTCOMES2, TODO_RANK, ExecutionContinuityService;
|
|
181287
|
+
if (node.handoffProducer === "agent" && node.nextActions[0]) return `Agent Handoff: ${node.nextActions[0]}`;
|
|
181288
|
+
if (node.nodeState === "ready") return "\u8282\u70B9 ready\uFF0C\u7B49\u5F85\u8C03\u5EA6";
|
|
181289
|
+
if (node.nodeState === "blocked" && node.unresolvedIssues[0]) return `\u8282\u70B9 blocked\uFF1A${node.unresolvedIssues[0]}`;
|
|
181290
|
+
if (!node.progress) return `\u8282\u70B9 ${node.nodeState}\uFF0C\u65E0 checkpoint`;
|
|
181291
|
+
if (node.progress.recoverability === "full") return `\u8282\u70B9 ${node.nodeState}\uFF0Ccheckpoint \u5B8C\u6574\u53EF\u6062\u590D`;
|
|
181292
|
+
if (node.progress.recoverability === "partial") return `\u8282\u70B9 ${node.nodeState}\uFF0Ccheckpoint \u90E8\u5206\u53EF\u6062\u590D\uFF0C\u9700\u5173\u6CE8\u5DF2\u6709\u4EA7\u51FA`;
|
|
181293
|
+
return `\u8282\u70B9 ${node.nodeState}\uFF0Ccheckpoint \u4E0D\u53EF\u6062\u590D`;
|
|
181294
|
+
}
|
|
181295
|
+
var import_node_crypto32, DONE_STATES, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, TODO_RANK, ExecutionContinuityService;
|
|
180900
181296
|
var init_service7 = __esm({
|
|
180901
181297
|
"../server/src/domains/execution-continuity/service.ts"() {
|
|
180902
181298
|
"use strict";
|
|
180903
181299
|
init_src();
|
|
181300
|
+
import_node_crypto32 = require("node:crypto");
|
|
180904
181301
|
DONE_STATES = /* @__PURE__ */ new Set(["succeeded", "no_change"]);
|
|
180905
181302
|
BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
|
|
180906
181303
|
HANDOFF_OUTCOMES2 = /* @__PURE__ */ new Set(["succeeded", "no-output", "failed", "cancelled", "timeout", "orphaned"]);
|
|
181304
|
+
RESUME_EXTERNAL_EFFECT_LIMIT = 20;
|
|
181305
|
+
RESUME_SNAPSHOT_PREFIX = "<!-- oasis-resume-pack-snapshot:v1:";
|
|
181306
|
+
RESUME_SNAPSHOT_SUFFIX = " -->";
|
|
180907
181307
|
TODO_RANK = {
|
|
180908
181308
|
stalled: 0,
|
|
180909
181309
|
failed: 1,
|
|
@@ -180918,6 +181318,8 @@ var init_service7 = __esm({
|
|
|
180918
181318
|
stalledAfterMs;
|
|
180919
181319
|
attemptVisibilityWaitMs;
|
|
180920
181320
|
onCheckpointState;
|
|
181321
|
+
readBundleFile;
|
|
181322
|
+
onAgentHandoffPersisted;
|
|
180921
181323
|
constructor(opts) {
|
|
180922
181324
|
this.store = opts.store;
|
|
180923
181325
|
this.mode = opts.mode;
|
|
@@ -180926,6 +181328,8 @@ var init_service7 = __esm({
|
|
|
180926
181328
|
this.stalledAfterMs = opts.stalledAfterMs ?? 25 * 6e4;
|
|
180927
181329
|
this.attemptVisibilityWaitMs = opts.attemptVisibilityWaitMs ?? 500;
|
|
180928
181330
|
this.onCheckpointState = opts.onCheckpointState;
|
|
181331
|
+
this.readBundleFile = opts.readBundleFile;
|
|
181332
|
+
this.onAgentHandoffPersisted = opts.onAgentHandoffPersisted;
|
|
180929
181333
|
}
|
|
180930
181334
|
assertWritable() {
|
|
180931
181335
|
if (this.mode === "off") throw new Error("execution continuity is disabled");
|
|
@@ -181001,12 +181405,22 @@ var init_service7 = __esm({
|
|
|
181001
181405
|
});
|
|
181002
181406
|
}
|
|
181003
181407
|
async handoff(input) {
|
|
181004
|
-
this.assertWritable();
|
|
181005
181408
|
if (!HANDOFF_OUTCOMES2.has(input.outcome)) throw new Error(`invalid handoff outcome: ${input.outcome}`);
|
|
181006
181409
|
if (!input.summary.trim()) throw new Error("handoff summary is required");
|
|
181007
181410
|
if ([...input.summary].length > 400) throw new Error("handoff summary exceeds 400 characters");
|
|
181008
|
-
|
|
181009
|
-
|
|
181411
|
+
if (input.producedBy === "agent" && !input.userHandoff) {
|
|
181412
|
+
throw new Error("agent-authored handoff requires userHandoff");
|
|
181413
|
+
}
|
|
181414
|
+
const userHandoff = input.userHandoff ? normalizeUserHandoff(input.outcome, input.changedOutputs ?? [], input.userHandoff) : void 0;
|
|
181415
|
+
const attempt = await this.awaitAttempt(input.attemptId);
|
|
181416
|
+
const handoff = await this.afterAttemptVisible(() => this.store.putHandoff({
|
|
181417
|
+
...input,
|
|
181418
|
+
...userHandoff ? { userHandoff } : {}
|
|
181419
|
+
}));
|
|
181420
|
+
if (handoff.producedBy === "agent" && handoff.userHandoff) {
|
|
181421
|
+
await this.onAgentHandoffPersisted?.(attempt, handoff);
|
|
181422
|
+
}
|
|
181423
|
+
return handoff;
|
|
181010
181424
|
}
|
|
181011
181425
|
async afterAttemptVisible(write) {
|
|
181012
181426
|
const deadline = Date.now() + this.attemptVisibilityWaitMs;
|
|
@@ -181028,38 +181442,86 @@ var init_service7 = __esm({
|
|
|
181028
181442
|
const jobKey = scope.jobKey ?? currentAttempt?.jobKey ?? null;
|
|
181029
181443
|
const part = scope.part !== void 0 ? scope.part : currentAttempt?.part ?? null;
|
|
181030
181444
|
const attempts = allAttempts.filter((attempt) => attempt.id !== scope.excludeAttemptId);
|
|
181031
|
-
const
|
|
181032
|
-
|
|
181033
|
-
const [previousHandoff, latestCheckpoint,
|
|
181034
|
-
|
|
181035
|
-
|
|
181036
|
-
this.store.
|
|
181445
|
+
const previousAttempt = attempts.filter((attempt) => attempt.nodeId === nodeId).filter((attempt) => !currentAttempt || (attempt.attemptNo ?? 0) < (currentAttempt.attemptNo ?? 0)).filter((attempt) => jobKey === null || attempt.jobKey === jobKey && attempt.part === part).sort((a, b2) => (b2.attemptNo ?? 0) - (a.attemptNo ?? 0))[0] ?? null;
|
|
181446
|
+
if (!previousAttempt) return null;
|
|
181447
|
+
const [previousHandoff, latestCheckpoint, externalEffectProjection] = await Promise.all([
|
|
181448
|
+
this.store.getHandoff(previousAttempt.id),
|
|
181449
|
+
this.store.getLatestCheckpoint(previousAttempt.id),
|
|
181450
|
+
jobKey === null ? Promise.resolve(emptyExternalEffectProjection()) : this.store.getExternalEffectsForResume(
|
|
181451
|
+
nodeId,
|
|
181452
|
+
jobKey,
|
|
181453
|
+
part,
|
|
181454
|
+
RESUME_EXTERNAL_EFFECT_LIMIT,
|
|
181455
|
+
RESUME_EXTERNAL_EFFECT_LIMIT
|
|
181456
|
+
)
|
|
181037
181457
|
]);
|
|
181038
|
-
const upstream = await Promise.all(node.dependsOn.map(async (upstreamId) => {
|
|
181039
|
-
const upstreamNode = nodes.find((item) => item.nodeId === upstreamId) ?? {
|
|
181040
|
-
nodeId: upstreamId,
|
|
181041
|
-
title: upstreamId,
|
|
181042
|
-
dependsOn: []
|
|
181043
|
-
};
|
|
181044
|
-
const attempt = latest.get(upstreamId) ?? null;
|
|
181045
|
-
return {
|
|
181046
|
-
node: upstreamNode,
|
|
181047
|
-
attempt,
|
|
181048
|
-
handoff: attempt ? await this.store.getHandoff(attempt.id) : null
|
|
181049
|
-
};
|
|
181050
|
-
}));
|
|
181051
181458
|
return {
|
|
181052
|
-
scenario:
|
|
181459
|
+
scenario: "redispatch",
|
|
181053
181460
|
jobKey,
|
|
181054
181461
|
part,
|
|
181055
181462
|
node,
|
|
181056
|
-
upstream,
|
|
181463
|
+
upstream: [],
|
|
181057
181464
|
previousAttempt,
|
|
181058
181465
|
previousHandoff,
|
|
181059
181466
|
latestCheckpoint,
|
|
181060
|
-
externalEffects
|
|
181467
|
+
externalEffects: externalEffectProjection.effects,
|
|
181468
|
+
externalEffectSummary: externalEffectProjection.summary
|
|
181061
181469
|
};
|
|
181062
181470
|
}
|
|
181471
|
+
async recoveryView(attemptId, companyId) {
|
|
181472
|
+
const attempt = await this.store.getAttempt(attemptId);
|
|
181473
|
+
if (!attempt?.workOrderId || !attempt.nodeId || attempt.continuityMode !== "resume") return null;
|
|
181474
|
+
const recoverySnapshot = this.readRecoverySnapshot(attempt);
|
|
181475
|
+
if (!recoverySnapshot) return null;
|
|
181476
|
+
const graph = await this.resolveGraph(attempt.workOrderId, companyId);
|
|
181477
|
+
if (!graph.some((node) => node.nodeId === attempt.nodeId)) return null;
|
|
181478
|
+
return {
|
|
181479
|
+
workOrderId: attempt.workOrderId,
|
|
181480
|
+
node: recoverySnapshot.node,
|
|
181481
|
+
attempt,
|
|
181482
|
+
recoverySnapshot
|
|
181483
|
+
};
|
|
181484
|
+
}
|
|
181485
|
+
/**
|
|
181486
|
+
* Resolves a Chat Session's durable WorkOrder links to the newest current node Attempt that has
|
|
181487
|
+
* a verified historical recovery input. This deliberately does not inspect Chat run ids: Chat
|
|
181488
|
+
* runs and Dispatcher Attempts are separate identities.
|
|
181489
|
+
*/
|
|
181490
|
+
async latestRecoveryAttemptForWorkOrders(workOrderIds, companyId) {
|
|
181491
|
+
const candidates = [];
|
|
181492
|
+
for (const workOrderId of [...new Set(workOrderIds)]) {
|
|
181493
|
+
const graph = await this.resolveGraph(workOrderId, companyId);
|
|
181494
|
+
if (graph.length === 0) continue;
|
|
181495
|
+
const nodeIds = new Set(graph.map((node) => node.nodeId));
|
|
181496
|
+
const latest = latestAttemptByNode(await this.store.listAttempts(workOrderId));
|
|
181497
|
+
for (const attempt of latest.values()) {
|
|
181498
|
+
if (!attempt.nodeId || !nodeIds.has(attempt.nodeId) || !this.readRecoverySnapshot(attempt)) continue;
|
|
181499
|
+
candidates.push({ workOrderId, nodeId: attempt.nodeId, attemptId: attempt.id, startedAt: attempt.startedAt });
|
|
181500
|
+
}
|
|
181501
|
+
}
|
|
181502
|
+
candidates.sort((a, b2) => b2.startedAt.localeCompare(a.startedAt) || b2.attemptId.localeCompare(a.attemptId));
|
|
181503
|
+
const candidate = candidates[0];
|
|
181504
|
+
return candidate ? { workOrderId: candidate.workOrderId, nodeId: candidate.nodeId, attemptId: candidate.attemptId } : null;
|
|
181505
|
+
}
|
|
181506
|
+
readRecoverySnapshot(attempt) {
|
|
181507
|
+
if (!attempt.nodeId || attempt.continuityMode !== "resume") return null;
|
|
181508
|
+
const metadata = attempt.metadata;
|
|
181509
|
+
const manifest = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata["bundleManifest"] : null;
|
|
181510
|
+
const expectedHash = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest["execution/RESUME.md"] : null;
|
|
181511
|
+
if (typeof expectedHash !== "string" || !this.readBundleFile) return null;
|
|
181512
|
+
const injectedResume = this.readBundleFile(attempt.id, "execution/RESUME.md");
|
|
181513
|
+
if (!injectedResume) return null;
|
|
181514
|
+
const actualHash = (0, import_node_crypto32.createHash)("sha256").update(injectedResume).digest("hex");
|
|
181515
|
+
if (actualHash !== expectedHash) return null;
|
|
181516
|
+
const recoverySnapshot = parseResumePackSnapshot(injectedResume);
|
|
181517
|
+
if (!recoverySnapshot || recoverySnapshot.scenario !== "redispatch" || !recoverySnapshot.previousAttempt || recoverySnapshot.node.nodeId !== attempt.nodeId || recoverySnapshot.jobKey !== attempt.jobKey || recoverySnapshot.part !== attempt.part) return null;
|
|
181518
|
+
return recoverySnapshot;
|
|
181519
|
+
}
|
|
181520
|
+
async externalEffectHistory(workOrderId, nodeId, companyId, options) {
|
|
181521
|
+
const nodes = await this.resolveGraph(workOrderId, companyId);
|
|
181522
|
+
if (!nodes.some((node) => node.nodeId === nodeId)) return null;
|
|
181523
|
+
return this.store.queryExternalEffects(nodeId, options);
|
|
181524
|
+
}
|
|
181063
181525
|
async coordinatorView(workOrderId, companyId) {
|
|
181064
181526
|
const graph = await this.resolveGraph(workOrderId, companyId);
|
|
181065
181527
|
if (graph.length === 0) return null;
|
|
@@ -181067,6 +181529,7 @@ var init_service7 = __esm({
|
|
|
181067
181529
|
const latest = latestAttemptByNode(attempts);
|
|
181068
181530
|
const handoffs = /* @__PURE__ */ new Map();
|
|
181069
181531
|
const checkpoints = /* @__PURE__ */ new Map();
|
|
181532
|
+
const recoverySnapshots = /* @__PURE__ */ new Map();
|
|
181070
181533
|
await Promise.all([...latest.values()].map(async (attempt) => {
|
|
181071
181534
|
const [handoff, points] = await Promise.all([
|
|
181072
181535
|
this.store.getHandoff(attempt.id),
|
|
@@ -181074,6 +181537,7 @@ var init_service7 = __esm({
|
|
|
181074
181537
|
]);
|
|
181075
181538
|
handoffs.set(attempt.id, handoff);
|
|
181076
181539
|
checkpoints.set(attempt.id, points);
|
|
181540
|
+
recoverySnapshots.set(attempt.id, this.readRecoverySnapshot(attempt));
|
|
181077
181541
|
}));
|
|
181078
181542
|
const stateByNode = /* @__PURE__ */ new Map();
|
|
181079
181543
|
for (const node of graph) {
|
|
@@ -181088,11 +181552,21 @@ var init_service7 = __esm({
|
|
|
181088
181552
|
const attempt = latest.get(node.nodeId) ?? null;
|
|
181089
181553
|
const handoff = attempt ? handoffs.get(attempt.id) ?? null : null;
|
|
181090
181554
|
const checkpoint = attempt ? checkpoints.get(attempt.id) ?? null : null;
|
|
181555
|
+
const recoverySnapshot = attempt ? recoverySnapshots.get(attempt.id) ?? null : null;
|
|
181556
|
+
const recoverySource = attempt && recoverySnapshot?.previousAttempt ? {
|
|
181557
|
+
currentAttempt: attempt,
|
|
181558
|
+
recoveredFromAttempt: recoverySnapshot.previousAttempt,
|
|
181559
|
+
recoveryCheckpoint: recoverySnapshot.latestCheckpoint,
|
|
181560
|
+
recoveryHandoff: recoverySnapshot.previousHandoff
|
|
181561
|
+
} : null;
|
|
181091
181562
|
const nodeState = stateByNode.get(node.nodeId);
|
|
181563
|
+
const wasRecovered = recoverySource !== null;
|
|
181564
|
+
const executionState = wasRecovered && nodeState === "failed" ? "recovery_failed" : BLOCKING_STATES.has(nodeState) ? "awaiting_coordinator" : wasRecovered ? "recovered" : nodeState === "running" ? "running" : null;
|
|
181092
181565
|
const includeProgress = checkpoint && ["running", "stalled", "blocked", "failed"].includes(nodeState);
|
|
181093
181566
|
return {
|
|
181094
181567
|
nodeId: node.nodeId,
|
|
181095
181568
|
title: node.title,
|
|
181569
|
+
attemptId: attempt?.id ?? null,
|
|
181096
181570
|
nodeState,
|
|
181097
181571
|
status: attempt?.status ?? "not_started",
|
|
181098
181572
|
actorId: attempt?.actorId ?? null,
|
|
@@ -181105,6 +181579,10 @@ var init_service7 = __esm({
|
|
|
181105
181579
|
risks: handoff?.risks ?? [],
|
|
181106
181580
|
verificationGaps: handoff?.verificationGaps ?? [],
|
|
181107
181581
|
changedOutputs: handoff?.changedOutputs ?? [],
|
|
181582
|
+
executionState,
|
|
181583
|
+
latestCheckpoint: checkpoint,
|
|
181584
|
+
handoff,
|
|
181585
|
+
recoverySource,
|
|
181108
181586
|
...includeProgress ? { progress: {
|
|
181109
181587
|
pendingSteps: checkpoint.pendingSteps,
|
|
181110
181588
|
recoverability: checkpoint.recoverability,
|
|
@@ -181126,9 +181604,132 @@ var init_service7 = __esm({
|
|
|
181126
181604
|
"blocked",
|
|
181127
181605
|
"failed"
|
|
181128
181606
|
].map((state) => [state, nodes.filter((node) => node.nodeState === state).length]));
|
|
181129
|
-
|
|
181130
|
-
|
|
181131
|
-
|
|
181607
|
+
const collaborationStatus = nodes.length > 0 && nodes.every((node) => DONE_STATES.has(node.nodeState)) ? "completed" : nodes.some((node) => BLOCKING_STATES.has(node.nodeState)) ? "needs_coordination" : "in_progress";
|
|
181608
|
+
const handoffSummary = [...nodes].sort((a, b2) => Number(BLOCKING_STATES.has(b2.nodeState)) - Number(BLOCKING_STATES.has(a.nodeState))).map((node) => node.summary.trim()).find(Boolean) ?? "";
|
|
181609
|
+
return { workOrderId, mode: this.mode, collaborationStatus, handoffSummary, progress, nodes, dependencyBlocks, todo: todos };
|
|
181610
|
+
}
|
|
181611
|
+
};
|
|
181612
|
+
}
|
|
181613
|
+
});
|
|
181614
|
+
|
|
181615
|
+
// ../server/src/domains/execution-continuity/handoff-work-bridge.ts
|
|
181616
|
+
function createAgentHandoffWorkBridge(opts) {
|
|
181617
|
+
return async (attempt, handoff) => {
|
|
181618
|
+
const metadata = attempt.metadata && typeof attempt.metadata === "object" && !Array.isArray(attempt.metadata) ? attempt.metadata : {};
|
|
181619
|
+
const workId = typeof metadata["engineWorkId"] === "string" ? metadata["engineWorkId"] : null;
|
|
181620
|
+
const companyId = typeof metadata["engineCompanyId"] === "string" ? metadata["engineCompanyId"] : opts.getDefaultCompanyId();
|
|
181621
|
+
if (!workId || !attempt.workOrderId || !handoff.userHandoff) {
|
|
181622
|
+
throw new Error(`agent handoff ${attempt.id} is missing engine Work lineage`);
|
|
181623
|
+
}
|
|
181624
|
+
const companyEngine = await opts.resolveEngine(companyId);
|
|
181625
|
+
const engineBus = companyEngine.kernel.getBus?.();
|
|
181626
|
+
if (!engineBus) throw new Error(`engine bus unavailable for ${companyId}`);
|
|
181627
|
+
await engineBus.submitAndProcess({
|
|
181628
|
+
companyId,
|
|
181629
|
+
workorderId: attempt.workOrderId,
|
|
181630
|
+
actorId: attempt.actorId,
|
|
181631
|
+
event: {
|
|
181632
|
+
kind: "work.handoff_recorded",
|
|
181633
|
+
workorderId: attempt.workOrderId,
|
|
181634
|
+
workId,
|
|
181635
|
+
attemptId: attempt.id,
|
|
181636
|
+
producedBy: "agent",
|
|
181637
|
+
status: handoff.userHandoff.status
|
|
181638
|
+
},
|
|
181639
|
+
dedupKey: `agent-handoff:${attempt.id}:${handoff.userHandoff.status}`
|
|
181640
|
+
});
|
|
181641
|
+
};
|
|
181642
|
+
}
|
|
181643
|
+
var init_handoff_work_bridge = __esm({
|
|
181644
|
+
"../server/src/domains/execution-continuity/handoff-work-bridge.ts"() {
|
|
181645
|
+
"use strict";
|
|
181646
|
+
}
|
|
181647
|
+
});
|
|
181648
|
+
|
|
181649
|
+
// ../server/src/domains/execution-continuity/upstream-handoff-input.ts
|
|
181650
|
+
function renderRefs(refs) {
|
|
181651
|
+
return refs.map(
|
|
181652
|
+
(ref2) => `- ${ref2.kind}:${ref2.id}${ref2.version ? `@${ref2.version}` : ""}${ref2.label ? ` \u2014 ${ref2.label}` : ""}`
|
|
181653
|
+
);
|
|
181654
|
+
}
|
|
181655
|
+
function renderList(title, values) {
|
|
181656
|
+
return [
|
|
181657
|
+
`## ${title}`,
|
|
181658
|
+
...values.length > 0 ? values.map((value) => `- ${value}`) : ["- None"],
|
|
181659
|
+
""
|
|
181660
|
+
];
|
|
181661
|
+
}
|
|
181662
|
+
function renderUpstreamNodeHandoff(handoff, source) {
|
|
181663
|
+
if (handoff.producedBy !== "agent") {
|
|
181664
|
+
throw new Error(`upstream business handoff ${handoff.handoffId} must be Agent-authored`);
|
|
181665
|
+
}
|
|
181666
|
+
return [
|
|
181667
|
+
"# Upstream NodeHandoff",
|
|
181668
|
+
"",
|
|
181669
|
+
`Upstream node: ${source.upstreamNodeId}`,
|
|
181670
|
+
`Pinned work: ${source.pinnedWorkId}`,
|
|
181671
|
+
`Attempt: ${handoff.attemptId}`,
|
|
181672
|
+
`Handoff: ${handoff.handoffId}`,
|
|
181673
|
+
`Job: ${handoff.jobKey}`,
|
|
181674
|
+
`Part: ${handoff.part ?? "whole-node"}`,
|
|
181675
|
+
`Outcome: ${handoff.outcome}`,
|
|
181676
|
+
"Produced by: agent",
|
|
181677
|
+
`Created at: ${handoff.createdAt}`,
|
|
181678
|
+
"",
|
|
181679
|
+
"## Summary",
|
|
181680
|
+
handoff.summary,
|
|
181681
|
+
"",
|
|
181682
|
+
"## Changed durable outputs",
|
|
181683
|
+
...handoff.changedOutputs.length > 0 ? renderRefs(handoff.changedOutputs) : ["- None"],
|
|
181684
|
+
"",
|
|
181685
|
+
...renderList("Unresolved issues", handoff.unresolvedIssues),
|
|
181686
|
+
...renderList("Risks", handoff.risks),
|
|
181687
|
+
...renderList("Next actions", handoff.nextActions),
|
|
181688
|
+
...renderList("Verification gaps", handoff.verificationGaps),
|
|
181689
|
+
"This handoff explains the upstream Agent's work. Verify referenced outputs against the pinned files in this directory and their authoritative durable systems.",
|
|
181690
|
+
""
|
|
181691
|
+
].join("\n");
|
|
181692
|
+
}
|
|
181693
|
+
function createUpstreamHandoffInputResolver(opts) {
|
|
181694
|
+
return async (input) => {
|
|
181695
|
+
const snapshot = await opts.loadWorkorder(input.workOrderId);
|
|
181696
|
+
if (!snapshot) throw new Error(`workorder not found while assembling upstream handoff: ${input.workOrderId}`);
|
|
181697
|
+
const edge = snapshot.edges.find(
|
|
181698
|
+
(candidate) => candidate.fromNodeId === input.upstreamNodeId && candidate.toNodeId === input.downstreamNodeId
|
|
181699
|
+
);
|
|
181700
|
+
const downstream = snapshot.nodes.find((node) => node.id === input.downstreamNodeId);
|
|
181701
|
+
const businessEdge = edge?.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
|
|
181702
|
+
if (!businessEdge) return null;
|
|
181703
|
+
if (edge.pinnedWorkId !== input.pinnedWorkId) {
|
|
181704
|
+
throw new Error(
|
|
181705
|
+
`upstream handoff pin drift for ${input.upstreamNodeId}\u2192${input.downstreamNodeId}: bundle=${input.pinnedWorkId}, edge=${edge.pinnedWorkId ?? "none"}`
|
|
181706
|
+
);
|
|
181707
|
+
}
|
|
181708
|
+
const work = snapshot.works.find(
|
|
181709
|
+
(candidate) => candidate.id === input.pinnedWorkId && candidate.nodeId === input.upstreamNodeId
|
|
181710
|
+
);
|
|
181711
|
+
if (!work) throw new Error(`pinned upstream Work not found: ${input.pinnedWorkId}`);
|
|
181712
|
+
if (typeof work.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) {
|
|
181713
|
+
return null;
|
|
181714
|
+
}
|
|
181715
|
+
if (!work.agentHandoffAttemptId || work.businessHandoffStatus !== "delivered") {
|
|
181716
|
+
throw new Error(`pinned upstream Work ${work.id} has no delivered Agent handoff projection`);
|
|
181717
|
+
}
|
|
181718
|
+
const handoff = await opts.getHandoff(work.agentHandoffAttemptId);
|
|
181719
|
+
if (!handoff) throw new Error(`Agent NodeHandoff not found for Attempt ${work.agentHandoffAttemptId}`);
|
|
181720
|
+
if (handoff.attemptId !== work.agentHandoffAttemptId || handoff.producedBy !== "agent" || handoff.outcome !== "succeeded" || handoff.userHandoff?.status !== "delivered") {
|
|
181721
|
+
throw new Error(`invalid Agent NodeHandoff for pinned Work ${work.id}`);
|
|
181722
|
+
}
|
|
181723
|
+
return renderUpstreamNodeHandoff(handoff, {
|
|
181724
|
+
upstreamNodeId: input.upstreamNodeId,
|
|
181725
|
+
pinnedWorkId: input.pinnedWorkId
|
|
181726
|
+
});
|
|
181727
|
+
};
|
|
181728
|
+
}
|
|
181729
|
+
var init_upstream_handoff_input = __esm({
|
|
181730
|
+
"../server/src/domains/execution-continuity/upstream-handoff-input.ts"() {
|
|
181731
|
+
"use strict";
|
|
181732
|
+
init_src2();
|
|
181132
181733
|
}
|
|
181133
181734
|
});
|
|
181134
181735
|
|
|
@@ -181144,6 +181745,8 @@ var init_execution_continuity2 = __esm({
|
|
|
181144
181745
|
init_service7();
|
|
181145
181746
|
init_service7();
|
|
181146
181747
|
init_routes8();
|
|
181748
|
+
init_handoff_work_bridge();
|
|
181749
|
+
init_upstream_handoff_input();
|
|
181147
181750
|
}
|
|
181148
181751
|
});
|
|
181149
181752
|
|
|
@@ -181257,7 +181860,7 @@ function createChatSessionsDomain(opts) {
|
|
|
181257
181860
|
if (!text.trim()) throw new ApiError(400, "BAD_REQUEST", "text required");
|
|
181258
181861
|
const now = Date.now();
|
|
181259
181862
|
sweepDrafts(now);
|
|
181260
|
-
const id = `cd_${(0,
|
|
181863
|
+
const id = `cd_${(0, import_node_crypto33.randomUUID)()}`;
|
|
181261
181864
|
drafts.set(id, { text, actor: req.auth.actor, expiresAt: now + DRAFT_TTL_MS });
|
|
181262
181865
|
return { status: 201, body: { id, expiresInMs: DRAFT_TTL_MS } };
|
|
181263
181866
|
});
|
|
@@ -181290,7 +181893,7 @@ function createChatSessionsDomain(opts) {
|
|
|
181290
181893
|
const runtimeId = await currentRuntimeId(body.aiActorId) ?? "unknown";
|
|
181291
181894
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
181292
181895
|
const session = {
|
|
181293
|
-
id: (0,
|
|
181896
|
+
id: (0, import_node_crypto33.randomUUID)(),
|
|
181294
181897
|
humanActorId: req.auth.actor,
|
|
181295
181898
|
aiActorId: body.aiActorId,
|
|
181296
181899
|
runtimeId,
|
|
@@ -181363,7 +181966,7 @@ function createChatSessionsDomain(opts) {
|
|
|
181363
181966
|
if (body.role !== "user" && body.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
|
|
181364
181967
|
const role = body.role;
|
|
181365
181968
|
const msg = await store.appendMessage({
|
|
181366
|
-
id: (0,
|
|
181969
|
+
id: (0, import_node_crypto33.randomUUID)(),
|
|
181367
181970
|
sessionId: req.params.id,
|
|
181368
181971
|
role,
|
|
181369
181972
|
content: role === "user" ? stripInjectedChatContext(body.content) : body.content,
|
|
@@ -181400,6 +182003,18 @@ function createChatSessionsDomain(opts) {
|
|
|
181400
182003
|
const items = allSummaries.filter((s2) => workOrderIds.includes(s2.id));
|
|
181401
182004
|
return { status: 200, body: { items } };
|
|
181402
182005
|
});
|
|
182006
|
+
router.get("/api/chat-sessions/:id/execution-recovery-attempt", async (req) => {
|
|
182007
|
+
const session = await store.getSession(req.params.id);
|
|
182008
|
+
if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
|
|
182009
|
+
const workOrderIds = await store.listWorkOrdersForSession(req.params.id);
|
|
182010
|
+
const candidate = workOrderIds.length && opts.resolveExecutionRecoveryAttempt ? await opts.resolveExecutionRecoveryAttempt(workOrderIds, req.auth.companyId) : null;
|
|
182011
|
+
return {
|
|
182012
|
+
status: 200,
|
|
182013
|
+
body: {
|
|
182014
|
+
attempt: candidate ? { chatSessionId: session.id, ...candidate } : null
|
|
182015
|
+
}
|
|
182016
|
+
};
|
|
182017
|
+
});
|
|
181403
182018
|
async function locateWorkdir(sessionId, actor) {
|
|
181404
182019
|
const session = await store.getSession(sessionId);
|
|
181405
182020
|
if (!session || session.humanActorId !== actor) throw new ApiError(404, "NOT_FOUND", "session not found");
|
|
@@ -181456,11 +182071,11 @@ function createChatSessionsDomain(opts) {
|
|
|
181456
182071
|
});
|
|
181457
182072
|
};
|
|
181458
182073
|
}
|
|
181459
|
-
var
|
|
182074
|
+
var import_node_crypto33, WORKDIR_READ_MAX_BYTES;
|
|
181460
182075
|
var init_chat_sessions = __esm({
|
|
181461
182076
|
"../server/src/domains/chat-sessions/index.ts"() {
|
|
181462
182077
|
"use strict";
|
|
181463
|
-
|
|
182078
|
+
import_node_crypto33 = require("node:crypto");
|
|
181464
182079
|
init_chat_session();
|
|
181465
182080
|
init_router();
|
|
181466
182081
|
init_workorders();
|
|
@@ -183197,7 +183812,7 @@ function playbooksDomain(opts) {
|
|
|
183197
183812
|
opts.overrides.set(companyOf(req), ref2, nodeKey, nextOverride);
|
|
183198
183813
|
try {
|
|
183199
183814
|
await opts.audit?.({
|
|
183200
|
-
id: `reg_${(0,
|
|
183815
|
+
id: `reg_${(0, import_node_crypto34.randomUUID)()}`,
|
|
183201
183816
|
actor: req.auth.actor,
|
|
183202
183817
|
kind: "registry_change",
|
|
183203
183818
|
target: `playbook:${ref2}#${nodeKey}`,
|
|
@@ -183214,11 +183829,11 @@ function playbooksDomain(opts) {
|
|
|
183214
183829
|
});
|
|
183215
183830
|
};
|
|
183216
183831
|
}
|
|
183217
|
-
var
|
|
183832
|
+
var import_node_crypto34;
|
|
183218
183833
|
var init_routes10 = __esm({
|
|
183219
183834
|
"../server/src/domains/playbooks/routes.ts"() {
|
|
183220
183835
|
"use strict";
|
|
183221
|
-
|
|
183836
|
+
import_node_crypto34 = require("node:crypto");
|
|
183222
183837
|
init_router();
|
|
183223
183838
|
init_registry3();
|
|
183224
183839
|
init_planner();
|
|
@@ -183351,11 +183966,11 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
|
|
|
183351
183966
|
entry.appendWaiters.push({ resolve: resolve9, timer });
|
|
183352
183967
|
});
|
|
183353
183968
|
}
|
|
183354
|
-
var
|
|
183969
|
+
var import_node_crypto35, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
|
|
183355
183970
|
var init_daemon_adapter = __esm({
|
|
183356
183971
|
"../server/src/daemon-adapter.ts"() {
|
|
183357
183972
|
"use strict";
|
|
183358
|
-
|
|
183973
|
+
import_node_crypto35 = require("node:crypto");
|
|
183359
183974
|
STASH_TTL_MS = 5 * 6e4;
|
|
183360
183975
|
STASH_MAX_FRAMES_PER_ID = 500;
|
|
183361
183976
|
STASH_MAX_IDS = 50;
|
|
@@ -183370,7 +183985,7 @@ var init_daemon_adapter = __esm({
|
|
|
183370
183985
|
this.stashEnabled = opts.stashUnknownFrames ?? false;
|
|
183371
183986
|
hub.addMessageListener((_daemonId, msg) => this.onDaemonMessage(msg));
|
|
183372
183987
|
hub.addDisconnectListener((daemonId) => this.onNodeDown(daemonId));
|
|
183373
|
-
hub.addConnectListener((daemon) => this.onNodeUp(daemon
|
|
183988
|
+
hub.addConnectListener((daemon) => this.onNodeUp(daemon));
|
|
183374
183989
|
}
|
|
183375
183990
|
pending = /* @__PURE__ */ new Map();
|
|
183376
183991
|
nodeLostGraceMs;
|
|
@@ -183456,48 +184071,73 @@ var init_daemon_adapter = __esm({
|
|
|
183456
184071
|
}
|
|
183457
184072
|
return byActor;
|
|
183458
184073
|
}
|
|
183459
|
-
/**
|
|
183460
|
-
|
|
184074
|
+
/** 派发送达/启动观测。供新 Engine 在 session_started 之前保存 nodeId 并建立 fencing 路由。 */
|
|
184075
|
+
deliveryStatus(dispatchId) {
|
|
184076
|
+
const entry = this.pending.get(dispatchId);
|
|
184077
|
+
return entry ? { state: entry.deliveryState, nodeId: entry.nodeId } : void 0;
|
|
184078
|
+
}
|
|
184079
|
+
/** 收到 dispatch_received(或兼容旧节点的更强 started/event/output 证据)即取消送达超时。 */
|
|
184080
|
+
confirmDelivery(dispatchId, state) {
|
|
183461
184081
|
const entry = this.pending.get(dispatchId);
|
|
183462
184082
|
if (entry?.ackTimer) {
|
|
183463
184083
|
clearTimeout(entry.ackTimer);
|
|
183464
184084
|
entry.ackTimer = void 0;
|
|
183465
184085
|
}
|
|
184086
|
+
if (entry) entry.deliveryState = state;
|
|
183466
184087
|
}
|
|
183467
184088
|
onNodeDown(daemonId) {
|
|
183468
184089
|
if (this.reapTimers.has(daemonId)) return;
|
|
183469
184090
|
if (![...this.pending.values()].some((e) => e.nodeId === daemonId && !e.exited)) return;
|
|
183470
184091
|
const timer = setTimeout(() => {
|
|
183471
184092
|
this.reapTimers.delete(daemonId);
|
|
183472
|
-
this.
|
|
184093
|
+
this.markNodeUnknown(daemonId);
|
|
183473
184094
|
}, this.nodeLostGraceMs);
|
|
183474
184095
|
timer.unref?.();
|
|
183475
184096
|
this.reapTimers.set(daemonId, timer);
|
|
183476
184097
|
}
|
|
183477
|
-
onNodeUp(
|
|
184098
|
+
onNodeUp(daemon) {
|
|
184099
|
+
const daemonId = daemon.daemonId;
|
|
183478
184100
|
const timer = this.reapTimers.get(daemonId);
|
|
183479
184101
|
if (timer) {
|
|
183480
184102
|
clearTimeout(timer);
|
|
183481
184103
|
this.reapTimers.delete(daemonId);
|
|
183482
184104
|
}
|
|
183483
|
-
|
|
183484
|
-
|
|
183485
|
-
|
|
183486
|
-
const
|
|
184105
|
+
if (!daemon.meta || daemon.meta.pendingDispatchIds === void 0) return;
|
|
184106
|
+
for (const dispatchId of daemon.meta.pendingDispatchIds) this.confirmDelivery(dispatchId, "received");
|
|
184107
|
+
for (const session of daemon.meta.activeSessions ?? []) this.confirmDelivery(session.dispatchId, "started");
|
|
184108
|
+
const present = /* @__PURE__ */ new Set([
|
|
184109
|
+
...daemon.meta.pendingDispatchIds ?? [],
|
|
184110
|
+
...(daemon.meta.activeSessions ?? []).map((s2) => s2.dispatchId)
|
|
184111
|
+
]);
|
|
184112
|
+
const absent = [];
|
|
183487
184113
|
for (const [dispatchId, entry] of this.pending) {
|
|
183488
|
-
if (entry.nodeId === daemonId && !entry.exited)
|
|
184114
|
+
if (entry.nodeId === daemonId && !entry.exited && !present.has(dispatchId)) absent.push(dispatchId);
|
|
184115
|
+
}
|
|
184116
|
+
for (const dispatchId of absent) {
|
|
184117
|
+
this.log(`[dispatch-delivery] ${dispatchId}\uFF1A\u8282\u70B9 ${daemonId} \u91CD\u8FDE\u5E76\u660E\u786E\u786E\u8BA4 session/pending \u5747\u4E0D\u5B58\u5728 \u2192 terminal`);
|
|
184118
|
+
this.settle(dispatchId, {
|
|
184119
|
+
code: null,
|
|
184120
|
+
reason: "server-unreachable",
|
|
184121
|
+
errorMessage: `\u8282\u70B9 ${daemonId} \u91CD\u8FDE\u540E\u7684\u5B58\u5728\u6027\u5FEB\u7167\u786E\u8BA4\u539F dispatch \u4E0D\u5B58\u5728`
|
|
184122
|
+
});
|
|
183489
184123
|
}
|
|
183490
|
-
|
|
183491
|
-
|
|
183492
|
-
|
|
183493
|
-
|
|
183494
|
-
for (const dispatchId of
|
|
183495
|
-
|
|
184124
|
+
}
|
|
184125
|
+
/** 节点失联超宽限期:只报警,不把「暂时够不到」升级成「runtime 已死」。 */
|
|
184126
|
+
markNodeUnknown(daemonId) {
|
|
184127
|
+
const unknown2 = [];
|
|
184128
|
+
for (const [dispatchId, entry] of this.pending) {
|
|
184129
|
+
if (entry.nodeId === daemonId && !entry.exited) unknown2.push(dispatchId);
|
|
183496
184130
|
}
|
|
184131
|
+
if (unknown2.length === 0) return;
|
|
184132
|
+
this.log(`[dispatch-delivery] \u8282\u70B9 ${daemonId} \u5931\u8054\u8D85 ${this.nodeLostGraceMs}ms\uFF1B${unknown2.length} \u4E2A\u6D3E\u53D1\u4FDD\u6301\u975E\u7EC8\u6001\uFF0C\u7B49\u5F85\u5B58\u5728\u6027\u786E\u8BA4\u6216 Work SLA\uFF1A${unknown2.join(", ")}`);
|
|
183497
184133
|
}
|
|
183498
184134
|
onDaemonMessage(msg) {
|
|
183499
|
-
if (msg.type === "
|
|
183500
|
-
this.
|
|
184135
|
+
if (msg.type === "dispatch_received") {
|
|
184136
|
+
this.confirmDelivery(msg.dispatchId, "received");
|
|
184137
|
+
const entry = this.pending.get(msg.dispatchId);
|
|
184138
|
+
if (entry) this.health?.recordReachable(entry.nodeId);
|
|
184139
|
+
} else if (msg.type === "session_started") {
|
|
184140
|
+
this.confirmDelivery(msg.dispatchId, "started");
|
|
183501
184141
|
const entry = this.pending.get(msg.dispatchId);
|
|
183502
184142
|
if (entry) this.health?.recordReachable(entry.nodeId);
|
|
183503
184143
|
} else if (msg.type === "session_exited") {
|
|
@@ -183505,12 +184145,12 @@ var init_daemon_adapter = __esm({
|
|
|
183505
184145
|
if (this.pending.has(msg.dispatchId)) this.settle(msg.dispatchId, info);
|
|
183506
184146
|
else if (this.stashEnabled) this.stashFrame(msg.dispatchId, { type: "exit", info });
|
|
183507
184147
|
} else if (msg.type === "session_event") {
|
|
183508
|
-
this.
|
|
184148
|
+
this.confirmDelivery(msg.dispatchId, "started");
|
|
183509
184149
|
const entry = this.pending.get(msg.dispatchId);
|
|
183510
184150
|
if (!entry && this.stashEnabled) this.stashFrame(msg.dispatchId, { type: "event", event: msg.event });
|
|
183511
184151
|
for (const cb of entry?.telemetryCbs ?? []) cb(msg.event);
|
|
183512
184152
|
} else if (msg.type === "session_output") {
|
|
183513
|
-
this.
|
|
184153
|
+
this.confirmDelivery(msg.dispatchId, "started");
|
|
183514
184154
|
const entry = this.pending.get(msg.dispatchId);
|
|
183515
184155
|
if (!entry && this.stashEnabled) this.stashFrame(msg.dispatchId, { type: "output", chunk: msg.chunk });
|
|
183516
184156
|
for (const cb of entry?.outputCbs ?? []) cb(msg.chunk);
|
|
@@ -183526,7 +184166,7 @@ var init_daemon_adapter = __esm({
|
|
|
183526
184166
|
async spawn(job) {
|
|
183527
184167
|
const nodeId = job.binding?.nodeId;
|
|
183528
184168
|
if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
|
|
183529
|
-
const dispatchId = job.dispatchId ?? `dispatch:${(0,
|
|
184169
|
+
const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto35.randomUUID)()}`;
|
|
183530
184170
|
const entry = {
|
|
183531
184171
|
nodeId,
|
|
183532
184172
|
...job.binding?.runtimeKind ? { runtimeKind: job.binding.runtimeKind } : {},
|
|
@@ -183535,6 +184175,7 @@ var init_daemon_adapter = __esm({
|
|
|
183535
184175
|
telemetryCbs: [],
|
|
183536
184176
|
outputCbs: [],
|
|
183537
184177
|
exited: void 0,
|
|
184178
|
+
deliveryState: "awaiting_ack",
|
|
183538
184179
|
ackTimer: void 0,
|
|
183539
184180
|
appendWaiters: []
|
|
183540
184181
|
};
|
|
@@ -183545,9 +184186,10 @@ var init_daemon_adapter = __esm({
|
|
|
183545
184186
|
} else if (this.ackTimeoutMs > 0) {
|
|
183546
184187
|
const timer = setTimeout(() => {
|
|
183547
184188
|
const e = this.pending.get(dispatchId);
|
|
183548
|
-
if (!e || e.exited) return;
|
|
183549
|
-
|
|
183550
|
-
|
|
184189
|
+
if (!e || e.exited || e.deliveryState !== "awaiting_ack") return;
|
|
184190
|
+
e.ackTimer = void 0;
|
|
184191
|
+
e.deliveryState = "delivery_unknown";
|
|
184192
|
+
this.log(`[dispatch-delivery] ${dispatchId}\uFF08${job.artifactId}\uFF09\u6D3E\u53D1\u540E ${this.ackTimeoutMs}ms \u672A\u89C1 dispatch_received \u2192 delivery_unknown\uFF08\u975E\u7EC8\u6001\uFF0C\u4E0D\u91CD\u6D3E\uFF09`);
|
|
183551
184193
|
}, this.ackTimeoutMs);
|
|
183552
184194
|
timer.unref?.();
|
|
183553
184195
|
entry.ackTimer = timer;
|
|
@@ -183586,6 +184228,7 @@ var init_daemon_adapter = __esm({
|
|
|
183586
184228
|
telemetryCbs: [],
|
|
183587
184229
|
outputCbs: [],
|
|
183588
184230
|
exited: stashed.exit,
|
|
184231
|
+
deliveryState: "started",
|
|
183589
184232
|
ackTimer: void 0,
|
|
183590
184233
|
appendWaiters: []
|
|
183591
184234
|
};
|
|
@@ -183689,7 +184332,7 @@ var init_daemon_adapter = __esm({
|
|
|
183689
184332
|
}));
|
|
183690
184333
|
}
|
|
183691
184334
|
request(nodeId, buildFrame) {
|
|
183692
|
-
const requestId = (0,
|
|
184335
|
+
const requestId = (0, import_node_crypto35.randomUUID)();
|
|
183693
184336
|
const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
|
|
183694
184337
|
if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
|
|
183695
184338
|
return new Promise((resolve9) => {
|
|
@@ -183721,6 +184364,31 @@ var init_daemon_adapter = __esm({
|
|
|
183721
184364
|
}
|
|
183722
184365
|
});
|
|
183723
184366
|
|
|
184367
|
+
// ../server/src/dispatch-fence.ts
|
|
184368
|
+
function isExplicitlyDispatchFenced(run) {
|
|
184369
|
+
const fence = metadataObject(run.metadata).dispatchFence;
|
|
184370
|
+
return fence !== null && typeof fence === "object" && !Array.isArray(fence);
|
|
184371
|
+
}
|
|
184372
|
+
async function fenceDispatch(trace, dispatchId, reason, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
184373
|
+
const run = await trace.getRun(dispatchId);
|
|
184374
|
+
if (!run) return false;
|
|
184375
|
+
if (isExplicitlyDispatchFenced(run)) return true;
|
|
184376
|
+
await trace.updateRun(dispatchId, {
|
|
184377
|
+
metadata: {
|
|
184378
|
+
...metadataObject(run.metadata),
|
|
184379
|
+
dispatchFence: { at, reason }
|
|
184380
|
+
}
|
|
184381
|
+
});
|
|
184382
|
+
return true;
|
|
184383
|
+
}
|
|
184384
|
+
var metadataObject;
|
|
184385
|
+
var init_dispatch_fence = __esm({
|
|
184386
|
+
"../server/src/dispatch-fence.ts"() {
|
|
184387
|
+
"use strict";
|
|
184388
|
+
metadataObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
184389
|
+
}
|
|
184390
|
+
});
|
|
184391
|
+
|
|
183724
184392
|
// ../server/src/side-map.ts
|
|
183725
184393
|
var import_node_fs16, import_node_path21, FileSideMap;
|
|
183726
184394
|
var init_side_map = __esm({
|
|
@@ -183772,13 +184440,13 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
|
|
|
183772
184440
|
if (sha(body) === lastPushedHash) return { kind: "echo" };
|
|
183773
184441
|
return { kind: "human-edit", content: body, updatedBy: page.updatedBy };
|
|
183774
184442
|
}
|
|
183775
|
-
var
|
|
184443
|
+
var import_node_crypto36, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
|
|
183776
184444
|
var init_engine = __esm({
|
|
183777
184445
|
"../server/src/mirror/engine.ts"() {
|
|
183778
184446
|
"use strict";
|
|
183779
|
-
|
|
184447
|
+
import_node_crypto36 = require("node:crypto");
|
|
183780
184448
|
init_src();
|
|
183781
|
-
sha = (s2) => (0,
|
|
184449
|
+
sha = (s2) => (0, import_node_crypto36.createHash)("sha256").update(s2, "utf8").digest("hex");
|
|
183782
184450
|
enc3 = (s2) => new TextEncoder().encode(s2);
|
|
183783
184451
|
dec = (b2) => new TextDecoder().decode(b2);
|
|
183784
184452
|
MirrorEngine = class {
|
|
@@ -183977,11 +184645,11 @@ function humanExitCause(exit) {
|
|
|
183977
184645
|
const base = (exit.reason ? label[exit.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
|
|
183978
184646
|
return exit.errorMessage ? `${base}\uFF1A${exit.errorMessage.slice(0, 200)}` : base;
|
|
183979
184647
|
}
|
|
183980
|
-
var
|
|
184648
|
+
var import_node_crypto37, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
|
|
183981
184649
|
var init_worker = __esm({
|
|
183982
184650
|
"../server/src/coordinator/worker.ts"() {
|
|
183983
184651
|
"use strict";
|
|
183984
|
-
|
|
184652
|
+
import_node_crypto37 = require("node:crypto");
|
|
183985
184653
|
init_src3();
|
|
183986
184654
|
init_identity();
|
|
183987
184655
|
AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
|
|
@@ -184227,7 +184895,7 @@ ${HIGH_RISK_COMMANDS}`;
|
|
|
184227
184895
|
return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
|
|
184228
184896
|
}
|
|
184229
184897
|
evaluationKey(kind, artifactId, evidence) {
|
|
184230
|
-
const fingerprint = (0,
|
|
184898
|
+
const fingerprint = (0, import_node_crypto37.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
|
|
184231
184899
|
return `${kind}:${artifactId}:${fingerprint}`;
|
|
184232
184900
|
}
|
|
184233
184901
|
artifactEvidence(id) {
|
|
@@ -184601,7 +185269,7 @@ ${ctx.nodeFault}
|
|
|
184601
185269
|
this.deps.log?.(`[coordinator] ${workspace} \u5168\u5C40\u4E0A\u4E0B\u6587\u8BFB\u53D6\u5931\u8D25\uFF0C\u7EE7\u7EED\u6CBF\u7528\u539F\u534F\u8C03\u8005\u4EFB\u52A1\uFF1A${String(error2)}`);
|
|
184602
185270
|
}
|
|
184603
185271
|
}
|
|
184604
|
-
const runtimeSessionId = opts?.resumeSessionId ?? (0,
|
|
185272
|
+
const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto37.randomUUID)();
|
|
184605
185273
|
const job = {
|
|
184606
185274
|
actor: actorId,
|
|
184607
185275
|
actorToken: token,
|
|
@@ -184889,6 +185557,7 @@ var init_src8 = __esm({
|
|
|
184889
185557
|
init_overrides();
|
|
184890
185558
|
init_scheduler();
|
|
184891
185559
|
init_daemon_adapter();
|
|
185560
|
+
init_dispatch_fence();
|
|
184892
185561
|
init_node_health();
|
|
184893
185562
|
init_side_map();
|
|
184894
185563
|
init_engine();
|
|
@@ -184907,11 +185576,11 @@ var init_src8 = __esm({
|
|
|
184907
185576
|
});
|
|
184908
185577
|
|
|
184909
185578
|
// ../storage/src/postgres.ts
|
|
184910
|
-
var
|
|
185579
|
+
var import_node_crypto38, ident3, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
|
|
184911
185580
|
var init_postgres = __esm({
|
|
184912
185581
|
"../storage/src/postgres.ts"() {
|
|
184913
185582
|
"use strict";
|
|
184914
|
-
|
|
185583
|
+
import_node_crypto38 = require("node:crypto");
|
|
184915
185584
|
init_esm();
|
|
184916
185585
|
init_src();
|
|
184917
185586
|
ident3 = (s2) => {
|
|
@@ -185090,7 +185759,7 @@ var init_postgres = __esm({
|
|
|
185090
185759
|
return new _PostgresBlobStore(pool, schema);
|
|
185091
185760
|
}
|
|
185092
185761
|
async put(bytes) {
|
|
185093
|
-
const hash = (0,
|
|
185762
|
+
const hash = (0, import_node_crypto38.createHash)("sha256").update(bytes).digest("hex");
|
|
185094
185763
|
await this.pool.query(
|
|
185095
185764
|
`INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
|
|
185096
185765
|
[hash, Buffer.from(bytes), bytes.byteLength, sniffContentType(bytes) ?? null]
|
|
@@ -187711,10 +188380,12 @@ var init_postgres_trace = __esm({
|
|
|
187711
188380
|
risks jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
187712
188381
|
next_actions jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
187713
188382
|
verification_gaps jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
188383
|
+
user_handoff jsonb,
|
|
187714
188384
|
created_at text NOT NULL
|
|
187715
188385
|
)`);
|
|
187716
188386
|
await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS job_key text NOT NULL DEFAULT ''`);
|
|
187717
188387
|
await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS part text`);
|
|
188388
|
+
await pool.query(`ALTER TABLE "${s2}".node_handoffs ADD COLUMN IF NOT EXISTS user_handoff jsonb`);
|
|
187718
188389
|
await pool.query(`
|
|
187719
188390
|
UPDATE "${s2}".node_handoffs h
|
|
187720
188391
|
SET job_key=COALESCE(NULLIF(h.job_key, ''), r.job_key, 'attempt:' || h.attempt_id), part=r.part
|
|
@@ -187775,7 +188446,19 @@ var init_postgres_trace = __esm({
|
|
|
187775
188446
|
FOR EACH ROW EXECUTE FUNCTION "${s2}".ensure_terminal_execution_checkpoint()`);
|
|
187776
188447
|
await pool.query(`
|
|
187777
188448
|
CREATE OR REPLACE FUNCTION "${s2}".ensure_terminal_node_handoff() RETURNS trigger AS $$
|
|
187778
|
-
DECLARE
|
|
188449
|
+
DECLARE
|
|
188450
|
+
mapped_outcome text;
|
|
188451
|
+
generated_handoff_id text;
|
|
188452
|
+
terminal_reason text;
|
|
188453
|
+
latest_checkpoint_id text;
|
|
188454
|
+
latest_pending jsonb;
|
|
188455
|
+
latest_completed jsonb;
|
|
188456
|
+
latest_recoverability text;
|
|
188457
|
+
pending_effects integer;
|
|
188458
|
+
failed_effects integer;
|
|
188459
|
+
reconstructed_summary text;
|
|
188460
|
+
reconstructed_risks jsonb;
|
|
188461
|
+
reconstructed_gaps jsonb;
|
|
187779
188462
|
BEGIN
|
|
187780
188463
|
IF NEW.node_id IS NULL OR NEW.status IN ('queued', 'running') THEN RETURN NEW; END IF;
|
|
187781
188464
|
mapped_outcome := CASE NEW.status
|
|
@@ -187788,24 +188471,82 @@ var init_postgres_trace = __esm({
|
|
|
187788
188471
|
ELSE 'failed'
|
|
187789
188472
|
END;
|
|
187790
188473
|
generated_handoff_id := 'handoff:' || NEW.id;
|
|
188474
|
+
terminal_reason := COALESCE(NEW.error_message, NEW.exit_reason, 'status=' || mapped_outcome);
|
|
188475
|
+
-- attempt_terminal \u662F\u7CFB\u7EDF\u5728\u540C\u4E00\u7EC8\u6001\u4E8B\u52A1\u5185\u8865\u7684\u5FEB\u7167\uFF1B\u91CD\u5EFA\u4F9D\u636E\u53EA\u8BA4\u7EC8\u6001\u4E4B\u524D\u5DF2\u7ECF\u5B58\u5728\u7684\u6709\u6548 checkpoint\u3002
|
|
188476
|
+
SELECT checkpoint_id, pending_steps, completed_step_ids, recoverability
|
|
188477
|
+
INTO latest_checkpoint_id, latest_pending, latest_completed, latest_recoverability
|
|
188478
|
+
FROM "${s2}".execution_checkpoints
|
|
188479
|
+
WHERE attempt_id=NEW.id AND trigger <> 'attempt_terminal'
|
|
188480
|
+
ORDER BY seq DESC LIMIT 1;
|
|
188481
|
+
SELECT
|
|
188482
|
+
COUNT(*) FILTER (WHERE status='pending')::int,
|
|
188483
|
+
COUNT(*) FILTER (WHERE status='failed')::int
|
|
188484
|
+
INTO pending_effects, failed_effects
|
|
188485
|
+
FROM "${s2}".external_effects WHERE attempt_id=NEW.id;
|
|
188486
|
+
reconstructed_summary := left(
|
|
188487
|
+
'[system_reconstructed] attempt=' || NEW.id || ' outcome=' || mapped_outcome ||
|
|
188488
|
+
' reason=' || terminal_reason || '; ' ||
|
|
188489
|
+
CASE WHEN latest_checkpoint_id IS NULL
|
|
188490
|
+
THEN 'no valid pre-terminal checkpoint'
|
|
188491
|
+
ELSE 'checkpoint=' || latest_checkpoint_id ||
|
|
188492
|
+
' completed=' || jsonb_array_length(COALESCE(latest_completed, '[]'::jsonb)) ||
|
|
188493
|
+
' pending=' || jsonb_array_length(COALESCE(latest_pending, '[]'::jsonb)) ||
|
|
188494
|
+
' recoverability=' || latest_recoverability
|
|
188495
|
+
END,
|
|
188496
|
+
400
|
|
188497
|
+
);
|
|
188498
|
+
reconstructed_risks :=
|
|
188499
|
+
CASE WHEN NEW.error_message IS NULL THEN '[]'::jsonb
|
|
188500
|
+
ELSE jsonb_build_array(left(NEW.error_message, 400)) END ||
|
|
188501
|
+
CASE WHEN COALESCE(pending_effects, 0) = 0 AND COALESCE(failed_effects, 0) = 0 THEN '[]'::jsonb
|
|
188502
|
+
ELSE jsonb_build_array('external effects: pending=' || COALESCE(pending_effects, 0) ||
|
|
188503
|
+
', failed=' || COALESCE(failed_effects, 0)) END;
|
|
188504
|
+
reconstructed_gaps := CASE WHEN latest_checkpoint_id IS NULL
|
|
188505
|
+
THEN jsonb_build_array('No valid pre-terminal checkpoint was available.')
|
|
188506
|
+
ELSE '[]'::jsonb END;
|
|
187791
188507
|
INSERT INTO "${s2}".node_handoffs AS current_handoff
|
|
187792
188508
|
(handoff_id, attempt_id, job_key, part, produced_by, outcome, summary, changed_outputs,
|
|
187793
|
-
unresolved_issues, risks, next_actions, verification_gaps, created_at)
|
|
188509
|
+
unresolved_issues, risks, next_actions, verification_gaps, user_handoff, created_at)
|
|
187794
188510
|
VALUES
|
|
187795
188511
|
(generated_handoff_id, NEW.id, COALESCE(NEW.job_key, NEW.trigger_ref, 'attempt:' || NEW.id), NEW.part,
|
|
187796
188512
|
'system_reconstructed', mapped_outcome,
|
|
187797
|
-
|
|
187798
|
-
|
|
187799
|
-
|
|
187800
|
-
ELSE 'Execution ended with status ' || mapped_outcome || '.' END,
|
|
187801
|
-
NEW.output_refs, '[]'::jsonb,
|
|
187802
|
-
CASE WHEN NEW.error_message IS NULL THEN '[]'::jsonb ELSE jsonb_build_array(left(NEW.error_message, 400)) END,
|
|
187803
|
-
'[]'::jsonb, '[]'::jsonb, COALESCE(NEW.ended_at, NEW.updated_at, NEW.started_at))
|
|
188513
|
+
reconstructed_summary, NEW.output_refs, '[]'::jsonb, reconstructed_risks,
|
|
188514
|
+
COALESCE(latest_pending, '[]'::jsonb), reconstructed_gaps, NULL,
|
|
188515
|
+
COALESCE(NEW.ended_at, NEW.updated_at, NEW.started_at))
|
|
187804
188516
|
ON CONFLICT (attempt_id) DO UPDATE SET
|
|
187805
|
-
|
|
188517
|
+
produced_by=CASE
|
|
188518
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188519
|
+
THEN current_handoff.produced_by
|
|
188520
|
+
ELSE EXCLUDED.produced_by
|
|
188521
|
+
END,
|
|
188522
|
+
outcome=CASE WHEN current_handoff.produced_by = 'agent'
|
|
188523
|
+
THEN current_handoff.outcome ELSE EXCLUDED.outcome END,
|
|
188524
|
+
summary=CASE
|
|
188525
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188526
|
+
THEN current_handoff.summary
|
|
188527
|
+
ELSE EXCLUDED.summary
|
|
188528
|
+
END,
|
|
187806
188529
|
changed_outputs=CASE
|
|
187807
|
-
WHEN current_handoff.produced_by = 'agent'
|
|
188530
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188531
|
+
THEN current_handoff.changed_outputs
|
|
187808
188532
|
ELSE EXCLUDED.changed_outputs
|
|
188533
|
+
END,
|
|
188534
|
+
unresolved_issues=CASE
|
|
188535
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188536
|
+
THEN current_handoff.unresolved_issues ELSE EXCLUDED.unresolved_issues END,
|
|
188537
|
+
risks=CASE
|
|
188538
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188539
|
+
THEN current_handoff.risks ELSE EXCLUDED.risks END,
|
|
188540
|
+
next_actions=CASE
|
|
188541
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188542
|
+
THEN current_handoff.next_actions ELSE EXCLUDED.next_actions END,
|
|
188543
|
+
verification_gaps=CASE
|
|
188544
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188545
|
+
THEN current_handoff.verification_gaps ELSE EXCLUDED.verification_gaps END,
|
|
188546
|
+
user_handoff=CASE
|
|
188547
|
+
WHEN current_handoff.produced_by = 'agent'
|
|
188548
|
+
THEN current_handoff.user_handoff
|
|
188549
|
+
ELSE EXCLUDED.user_handoff
|
|
187809
188550
|
END;
|
|
187810
188551
|
NEW.handoff_id := COALESCE(NEW.handoff_id, generated_handoff_id);
|
|
187811
188552
|
RETURN NEW;
|
|
@@ -188511,6 +189252,101 @@ var init_postgres_trace = __esm({
|
|
|
188511
189252
|
);
|
|
188512
189253
|
return r.rows.map(rowToExternalEffect);
|
|
188513
189254
|
}
|
|
189255
|
+
async getExternalEffectsForResume(nodeId, jobKey, part, succeededLimit, failedLimit) {
|
|
189256
|
+
const recency = "COALESCE(e.occurred_at, r.ended_at, r.updated_at, r.started_at, '')";
|
|
189257
|
+
const client = await this.pool.connect();
|
|
189258
|
+
try {
|
|
189259
|
+
await client.query("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY");
|
|
189260
|
+
const counts2 = await client.query(
|
|
189261
|
+
`SELECT COUNT(*)::int AS total,
|
|
189262
|
+
COUNT(*) FILTER (WHERE e.status='pending')::int AS pending,
|
|
189263
|
+
COUNT(*) FILTER (WHERE e.status='succeeded')::int AS succeeded,
|
|
189264
|
+
COUNT(*) FILTER (WHERE e.status='failed')::int AS failed
|
|
189265
|
+
FROM ${this.s}.external_effects e
|
|
189266
|
+
INNER JOIN ${this.s}.agent_runs r ON r.id=e.attempt_id
|
|
189267
|
+
WHERE e.node_id=$1
|
|
189268
|
+
AND COALESCE(r.job_key, r.trigger_ref, 'attempt:' || r.id)=$2
|
|
189269
|
+
AND r.part IS NOT DISTINCT FROM $3`,
|
|
189270
|
+
[nodeId, jobKey, part]
|
|
189271
|
+
);
|
|
189272
|
+
const pending = await client.query(
|
|
189273
|
+
`SELECT e.* FROM ${this.s}.external_effects e
|
|
189274
|
+
LEFT JOIN ${this.s}.agent_runs r ON r.id=e.attempt_id
|
|
189275
|
+
WHERE e.node_id=$1 AND e.status='pending'
|
|
189276
|
+
AND COALESCE(r.job_key, r.trigger_ref, 'attempt:' || r.id)=$2
|
|
189277
|
+
AND r.part IS NOT DISTINCT FROM $3
|
|
189278
|
+
ORDER BY ${recency} DESC, e.idempotency_key DESC`,
|
|
189279
|
+
[nodeId, jobKey, part]
|
|
189280
|
+
);
|
|
189281
|
+
const succeeded = await client.query(
|
|
189282
|
+
`SELECT e.* FROM ${this.s}.external_effects e
|
|
189283
|
+
LEFT JOIN ${this.s}.agent_runs r ON r.id=e.attempt_id
|
|
189284
|
+
WHERE e.node_id=$1 AND e.status='succeeded'
|
|
189285
|
+
AND COALESCE(r.job_key, r.trigger_ref, 'attempt:' || r.id)=$2
|
|
189286
|
+
AND r.part IS NOT DISTINCT FROM $3
|
|
189287
|
+
ORDER BY ${recency} DESC, e.idempotency_key DESC LIMIT $4`,
|
|
189288
|
+
[nodeId, jobKey, part, succeededLimit]
|
|
189289
|
+
);
|
|
189290
|
+
const failed = await client.query(
|
|
189291
|
+
`SELECT e.* FROM ${this.s}.external_effects e
|
|
189292
|
+
LEFT JOIN ${this.s}.agent_runs r ON r.id=e.attempt_id
|
|
189293
|
+
WHERE e.node_id=$1 AND e.status='failed'
|
|
189294
|
+
AND COALESCE(r.job_key, r.trigger_ref, 'attempt:' || r.id)=$2
|
|
189295
|
+
AND r.part IS NOT DISTINCT FROM $3
|
|
189296
|
+
ORDER BY ${recency} DESC, e.idempotency_key DESC LIMIT $4`,
|
|
189297
|
+
[nodeId, jobKey, part, failedLimit]
|
|
189298
|
+
);
|
|
189299
|
+
const totals = counts2.rows[0];
|
|
189300
|
+
const totalByStatus = {
|
|
189301
|
+
pending: Number(totals?.["pending"] ?? 0),
|
|
189302
|
+
succeeded: Number(totals?.["succeeded"] ?? 0),
|
|
189303
|
+
failed: Number(totals?.["failed"] ?? 0)
|
|
189304
|
+
};
|
|
189305
|
+
const projection = {
|
|
189306
|
+
effects: [...pending.rows, ...succeeded.rows, ...failed.rows].map(rowToExternalEffect),
|
|
189307
|
+
summary: {
|
|
189308
|
+
total: Number(totals?.["total"] ?? 0),
|
|
189309
|
+
totalByStatus,
|
|
189310
|
+
includedByStatus: {
|
|
189311
|
+
pending: pending.rowCount ?? 0,
|
|
189312
|
+
succeeded: succeeded.rowCount ?? 0,
|
|
189313
|
+
failed: failed.rowCount ?? 0
|
|
189314
|
+
}
|
|
189315
|
+
}
|
|
189316
|
+
};
|
|
189317
|
+
await client.query("COMMIT");
|
|
189318
|
+
return projection;
|
|
189319
|
+
} catch (error2) {
|
|
189320
|
+
await client.query("ROLLBACK").catch(() => {
|
|
189321
|
+
});
|
|
189322
|
+
throw error2;
|
|
189323
|
+
} finally {
|
|
189324
|
+
client.release();
|
|
189325
|
+
}
|
|
189326
|
+
}
|
|
189327
|
+
async queryExternalEffects(nodeId, options) {
|
|
189328
|
+
const recency = "COALESCE(e.occurred_at, r.ended_at, r.updated_at, r.started_at, '')";
|
|
189329
|
+
const [count2, page] = await Promise.all([
|
|
189330
|
+
this.pool.query(
|
|
189331
|
+
`SELECT COUNT(*)::int AS total FROM ${this.s}.external_effects
|
|
189332
|
+
WHERE node_id=$1 AND ($2::text IS NULL OR status=$2)`,
|
|
189333
|
+
[nodeId, options.status ?? null]
|
|
189334
|
+
),
|
|
189335
|
+
this.pool.query(
|
|
189336
|
+
`SELECT e.* FROM ${this.s}.external_effects e
|
|
189337
|
+
LEFT JOIN ${this.s}.agent_runs r ON r.id=e.attempt_id
|
|
189338
|
+
WHERE e.node_id=$1 AND ($2::text IS NULL OR e.status=$2)
|
|
189339
|
+
ORDER BY ${recency} DESC, e.idempotency_key DESC LIMIT $3 OFFSET $4`,
|
|
189340
|
+
[nodeId, options.status ?? null, options.limit, options.offset]
|
|
189341
|
+
)
|
|
189342
|
+
]);
|
|
189343
|
+
return {
|
|
189344
|
+
total: Number(count2.rows[0]?.total ?? 0),
|
|
189345
|
+
offset: options.offset,
|
|
189346
|
+
limit: options.limit,
|
|
189347
|
+
items: page.rows.map(rowToExternalEffect)
|
|
189348
|
+
};
|
|
189349
|
+
}
|
|
188514
189350
|
async putHandoff(input) {
|
|
188515
189351
|
if (!input.summary.trim()) throw new Error("handoff summary is required");
|
|
188516
189352
|
if ([...input.summary].length > 400) throw new Error("handoff summary exceeds 400 characters");
|
|
@@ -188537,18 +189373,24 @@ var init_postgres_trace = __esm({
|
|
|
188537
189373
|
risks: input.risks ?? [],
|
|
188538
189374
|
nextActions: input.nextActions ?? [],
|
|
188539
189375
|
verificationGaps: input.verificationGaps ?? [],
|
|
189376
|
+
userHandoff: normalizeUserHandoff(
|
|
189377
|
+
input.outcome,
|
|
189378
|
+
input.changedOutputs ?? run.outputRefs,
|
|
189379
|
+
input.userHandoff
|
|
189380
|
+
),
|
|
188540
189381
|
createdAt: input.createdAt ?? this.now()
|
|
188541
189382
|
};
|
|
188542
189383
|
const saved = await client.query(
|
|
188543
189384
|
`INSERT INTO ${this.s}.node_handoffs AS current_handoff
|
|
188544
189385
|
(handoff_id, attempt_id, job_key, part, produced_by, outcome, summary, changed_outputs,
|
|
188545
|
-
unresolved_issues, risks, next_actions, verification_gaps, created_at)
|
|
188546
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9::jsonb,$10::jsonb,$11::jsonb,$12::jsonb,$13)
|
|
189386
|
+
unresolved_issues, risks, next_actions, verification_gaps, user_handoff, created_at)
|
|
189387
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9::jsonb,$10::jsonb,$11::jsonb,$12::jsonb,$13::jsonb,$14)
|
|
188547
189388
|
ON CONFLICT (attempt_id) DO UPDATE SET
|
|
188548
189389
|
produced_by=EXCLUDED.produced_by, outcome=EXCLUDED.outcome, summary=EXCLUDED.summary,
|
|
188549
189390
|
changed_outputs=EXCLUDED.changed_outputs, unresolved_issues=EXCLUDED.unresolved_issues,
|
|
188550
189391
|
risks=EXCLUDED.risks, next_actions=EXCLUDED.next_actions,
|
|
188551
|
-
verification_gaps=EXCLUDED.verification_gaps,
|
|
189392
|
+
verification_gaps=EXCLUDED.verification_gaps, user_handoff=EXCLUDED.user_handoff,
|
|
189393
|
+
created_at=EXCLUDED.created_at
|
|
188552
189394
|
WHERE current_handoff.produced_by <> 'agent' OR EXCLUDED.produced_by = 'agent'
|
|
188553
189395
|
RETURNING *`,
|
|
188554
189396
|
[
|
|
@@ -188564,6 +189406,7 @@ var init_postgres_trace = __esm({
|
|
|
188564
189406
|
JSON.stringify(handoff.risks),
|
|
188565
189407
|
JSON.stringify(handoff.nextActions),
|
|
188566
189408
|
JSON.stringify(handoff.verificationGaps),
|
|
189409
|
+
JSON.stringify(handoff.userHandoff),
|
|
188567
189410
|
handoff.createdAt
|
|
188568
189411
|
]
|
|
188569
189412
|
);
|
|
@@ -189074,6 +189917,7 @@ var init_postgres_trace = __esm({
|
|
|
189074
189917
|
risks: row.risks ?? [],
|
|
189075
189918
|
nextActions: row.next_actions ?? [],
|
|
189076
189919
|
verificationGaps: row.verification_gaps ?? [],
|
|
189920
|
+
userHandoff: row.user_handoff ?? null,
|
|
189077
189921
|
createdAt: row.created_at
|
|
189078
189922
|
});
|
|
189079
189923
|
rowToEvent2 = (row) => ({
|
|
@@ -189753,11 +190597,11 @@ var init_postgres_chat_sessions = __esm({
|
|
|
189753
190597
|
});
|
|
189754
190598
|
|
|
189755
190599
|
// ../storage/src/postgres-nodes.ts
|
|
189756
|
-
var
|
|
190600
|
+
var import_node_crypto39, ident12, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
|
|
189757
190601
|
var init_postgres_nodes = __esm({
|
|
189758
190602
|
"../storage/src/postgres-nodes.ts"() {
|
|
189759
190603
|
"use strict";
|
|
189760
|
-
|
|
190604
|
+
import_node_crypto39 = require("node:crypto");
|
|
189761
190605
|
init_esm();
|
|
189762
190606
|
ident12 = (s2) => {
|
|
189763
190607
|
if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
|
|
@@ -189896,7 +190740,7 @@ var init_postgres_nodes = __esm({
|
|
|
189896
190740
|
return store;
|
|
189897
190741
|
}
|
|
189898
190742
|
issue(nodeId) {
|
|
189899
|
-
const token = `ont_${(0,
|
|
190743
|
+
const token = `ont_${(0, import_node_crypto39.randomBytes)(24).toString("base64url")}`;
|
|
189900
190744
|
this.cache.set(token, nodeId);
|
|
189901
190745
|
void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
|
|
189902
190746
|
return token;
|
|
@@ -190107,11 +190951,11 @@ var init_postgres_type_registry = __esm({
|
|
|
190107
190951
|
});
|
|
190108
190952
|
|
|
190109
190953
|
// ../storage/src/postgres-actor-memory.ts
|
|
190110
|
-
var
|
|
190954
|
+
var import_node_crypto40, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
|
|
190111
190955
|
var init_postgres_actor_memory = __esm({
|
|
190112
190956
|
"../storage/src/postgres-actor-memory.ts"() {
|
|
190113
190957
|
"use strict";
|
|
190114
|
-
|
|
190958
|
+
import_node_crypto40 = require("node:crypto");
|
|
190115
190959
|
init_src();
|
|
190116
190960
|
matchClause = (q) => q.requireMatch && q.keywords.length > 0 ? " WHERE m > 0" : "";
|
|
190117
190961
|
ident15 = (s2) => {
|
|
@@ -190316,7 +191160,7 @@ var init_postgres_actor_memory = __esm({
|
|
|
190316
191160
|
}
|
|
190317
191161
|
async write(input, now) {
|
|
190318
191162
|
if (input.memId === void 0) {
|
|
190319
|
-
const memId = `mem:${(0,
|
|
191163
|
+
const memId = `mem:${(0, import_node_crypto40.randomUUID)()}`;
|
|
190320
191164
|
const r2 = await this.pool.query(
|
|
190321
191165
|
`INSERT INTO ${this.s}.actor_memories
|
|
190322
191166
|
(mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
|
|
@@ -190832,14 +191676,14 @@ var import_node_child_process16 = require("node:child_process");
|
|
|
190832
191676
|
var fs29 = __toESM(require("node:fs"), 1);
|
|
190833
191677
|
var os9 = __toESM(require("node:os"), 1);
|
|
190834
191678
|
var path23 = __toESM(require("node:path"), 1);
|
|
190835
|
-
var
|
|
191679
|
+
var import_node_crypto45 = require("node:crypto");
|
|
190836
191680
|
|
|
190837
191681
|
// ../cli/src/serve.ts
|
|
190838
191682
|
var fs25 = __toESM(require("node:fs"), 1);
|
|
190839
191683
|
var os5 = __toESM(require("node:os"), 1);
|
|
190840
191684
|
var path19 = __toESM(require("node:path"), 1);
|
|
190841
191685
|
var import_node_child_process12 = require("node:child_process");
|
|
190842
|
-
var
|
|
191686
|
+
var import_node_crypto41 = require("node:crypto");
|
|
190843
191687
|
var import_node_url6 = require("node:url");
|
|
190844
191688
|
init_src3();
|
|
190845
191689
|
init_src8();
|
|
@@ -191692,6 +192536,7 @@ async function startServe(opts) {
|
|
|
191692
192536
|
const executionContinuity = createExecutionContinuityDomain({
|
|
191693
192537
|
store: traceStore,
|
|
191694
192538
|
mode: continuityMode,
|
|
192539
|
+
readBundleFile: (attemptId, relativePath) => trajReader.readBundleFile(attemptId, relativePath),
|
|
191695
192540
|
onCheckpointState: async (attemptId, state) => {
|
|
191696
192541
|
try {
|
|
191697
192542
|
traceStoreSink.checkpointState(attemptId, state);
|
|
@@ -191700,6 +192545,10 @@ async function startServe(opts) {
|
|
|
191700
192545
|
console.error(`[execution-continuity] checkpoint state mirror failed for ${attemptId}:`, error2);
|
|
191701
192546
|
}
|
|
191702
192547
|
},
|
|
192548
|
+
onAgentHandoffPersisted: createAgentHandoffWorkBridge({
|
|
192549
|
+
getDefaultCompanyId: () => defaultCompanyId,
|
|
192550
|
+
resolveEngine: (companyId) => getEngine(companyId)
|
|
192551
|
+
}),
|
|
191703
192552
|
resolveGraph: async (workOrderId, companyId) => {
|
|
191704
192553
|
const companyEngine = await getEngine(companyId ?? defaultCompanyId);
|
|
191705
192554
|
return [...companyEngine.kernel.model.artifacts.values()].filter((artifact) => artifact.workspace === workOrderId).map((artifact) => ({
|
|
@@ -192178,7 +193027,7 @@ async function startServe(opts) {
|
|
|
192178
193027
|
}));
|
|
192179
193028
|
}
|
|
192180
193029
|
const limits = { wallClockMs: SESSION_WALL_CLOCK_MS.planner };
|
|
192181
|
-
const artifactId = `artifact:planner:${(0,
|
|
193030
|
+
const artifactId = `artifact:planner:${(0, import_node_crypto41.randomUUID)()}`;
|
|
192182
193031
|
const handle = await chatRemoteAdapter.spawn({
|
|
192183
193032
|
actor: planner.id,
|
|
192184
193033
|
actorToken: issueSessionToken(planner.id, { artifactId, action: "plan-workorder", limits, binding }),
|
|
@@ -192265,7 +193114,15 @@ async function startServe(opts) {
|
|
|
192265
193114
|
executionContinuity.register,
|
|
192266
193115
|
automations.register,
|
|
192267
193116
|
playbooks.register,
|
|
192268
|
-
...chatSessionStore ? [createChatSessionsDomain({
|
|
193117
|
+
...chatSessionStore ? [createChatSessionsDomain({
|
|
193118
|
+
store: chatSessionStore,
|
|
193119
|
+
registry: registryStore,
|
|
193120
|
+
trace: traceStore,
|
|
193121
|
+
kernel,
|
|
193122
|
+
artifactState: artifactStateStore,
|
|
193123
|
+
workdir: () => workdirBridge ?? void 0,
|
|
193124
|
+
resolveExecutionRecoveryAttempt: (workOrderIds, companyId) => executionContinuity.service.latestRecoveryAttemptForWorkOrders(workOrderIds, companyId)
|
|
193125
|
+
})] : []
|
|
192269
193126
|
],
|
|
192270
193127
|
workorderDrafts,
|
|
192271
193128
|
assistants: assistantsService,
|
|
@@ -192496,7 +193353,9 @@ async function startServe(opts) {
|
|
|
192496
193353
|
*/
|
|
192497
193354
|
isDispatchFenced: async (dispatchId) => {
|
|
192498
193355
|
const r = await traceStore.getRun(dispatchId).catch(() => null);
|
|
192499
|
-
if (!r
|
|
193356
|
+
if (!r) return false;
|
|
193357
|
+
if (isExplicitlyDispatchFenced(r)) return true;
|
|
193358
|
+
if (r.status === "running") return false;
|
|
192500
193359
|
return INFERRED_DEATH.has(r.exitReason ?? "");
|
|
192501
193360
|
},
|
|
192502
193361
|
retainedSession: (key) => key.annotationId && key.artifactId ? allDispatchers().map((d) => d.retainedProduceSessionFor(key.artifactId)).find(Boolean) : coordinatorRef?.retainedSessionFor(key),
|
|
@@ -192552,10 +193411,10 @@ async function startServe(opts) {
|
|
|
192552
193411
|
{ nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
|
|
192553
193412
|
{ nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
|
|
192554
193413
|
) : false;
|
|
192555
|
-
const runtimeSessionId = sessionId ?? (0,
|
|
193414
|
+
const runtimeSessionId = sessionId ?? (0, import_node_crypto41.randomUUID)();
|
|
192556
193415
|
const resumeRuntimeSession = Boolean(sessionId);
|
|
192557
|
-
const traceRunId = `chat-run:${(0,
|
|
192558
|
-
const artifactId = `artifact:chat:${(0,
|
|
193416
|
+
const traceRunId = `chat-run:${(0, import_node_crypto41.randomUUID)()}`;
|
|
193417
|
+
const artifactId = `artifact:chat:${(0, import_node_crypto41.randomUUID)()}`;
|
|
192559
193418
|
const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
192560
193419
|
let lastProgressTouchMs = 0;
|
|
192561
193420
|
const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
|
|
@@ -192950,6 +193809,9 @@ async function startServe(opts) {
|
|
|
192950
193809
|
const prodMapping = dispatchToWork.get(msg.dispatchId);
|
|
192951
193810
|
if (prodMapping && busRef) {
|
|
192952
193811
|
workToSession.set(prodMapping.workId, { dispatchId: msg.dispatchId, daemonId });
|
|
193812
|
+
if (prodMapping.nodeId !== daemonId) {
|
|
193813
|
+
dispatchToWork.set(msg.dispatchId, { ...prodMapping, nodeId: daemonId });
|
|
193814
|
+
}
|
|
192953
193815
|
busRef.submit({
|
|
192954
193816
|
companyId: "",
|
|
192955
193817
|
workorderId: prodMapping.workorderId,
|
|
@@ -193077,6 +193939,7 @@ async function startServe(opts) {
|
|
|
193077
193939
|
const dispatchedWorks = /* @__PURE__ */ new Set();
|
|
193078
193940
|
const dispatchedReviews = /* @__PURE__ */ new Set();
|
|
193079
193941
|
const dispatchToWork = /* @__PURE__ */ new Map();
|
|
193942
|
+
const workToSession = /* @__PURE__ */ new Map();
|
|
193080
193943
|
async function rebuildDispatchMappings(store) {
|
|
193081
193944
|
try {
|
|
193082
193945
|
const ids2 = await store.transaction((tx) => tx.listActiveWorkorders());
|
|
@@ -193102,7 +193965,6 @@ async function startServe(opts) {
|
|
|
193102
193965
|
console.warn(`[new-engine] \u91CD\u5EFA dispatch \u6620\u5C04\u5931\u8D25: ${String(err)}`);
|
|
193103
193966
|
}
|
|
193104
193967
|
}
|
|
193105
|
-
const workToSession = /* @__PURE__ */ new Map();
|
|
193106
193968
|
const reviewToSession = /* @__PURE__ */ new Map();
|
|
193107
193969
|
const dispatchToReview = /* @__PURE__ */ new Map();
|
|
193108
193970
|
let busRef = null;
|
|
@@ -193168,7 +194030,7 @@ async function startServe(opts) {
|
|
|
193168
194030
|
console.log(`[new-engine] dispatchWork ${workId} \u2192 ${rev.artifactId} / ${rev.author}${replyIssueId ? `\uFF08\u56DE\u4FE1 ${replyIssueId}\uFF09` : ""}`);
|
|
193169
194031
|
const art = kernelModel.artifacts.get(rev.artifactId);
|
|
193170
194032
|
const wid = art?.workspace ?? "";
|
|
193171
|
-
const ledgerId = `dispatch:${(0,
|
|
194033
|
+
const ledgerId = `dispatch:${(0, import_node_crypto41.randomUUID)()}`;
|
|
193172
194034
|
try {
|
|
193173
194035
|
await dispatchLedger.insertOpen({
|
|
193174
194036
|
id: ledgerId,
|
|
@@ -193184,7 +194046,7 @@ async function startServe(opts) {
|
|
|
193184
194046
|
}
|
|
193185
194047
|
let result;
|
|
193186
194048
|
try {
|
|
193187
|
-
result = replyIssueId && newEngineReplyWork ? await newEngineReplyWork(rev.artifactId, rev.author, replyIssueId, ledgerId) : await newEngineProduce(rev.artifactId, rev.author, ledgerId);
|
|
194049
|
+
result = replyIssueId && newEngineReplyWork ? await newEngineReplyWork(rev.artifactId, rev.author, replyIssueId, ledgerId) : await newEngineProduce(rev.artifactId, rev.author, workId, ledgerId);
|
|
193188
194050
|
} catch (err) {
|
|
193189
194051
|
dispatchLedger.close(ledgerId, { outcome: "failed", reason: `dispatch error: ${err instanceof Error ? err.message : String(err)}` }).catch(ledgerWarn("close(error)"));
|
|
193190
194052
|
console.log(`[new-engine] dispatchWork ${workId} \u6D3E\u53D1\u5F02\u5E38\uFF1A${String(err)} \u2192 \u7F6E work failed`);
|
|
@@ -193205,7 +194067,13 @@ async function startServe(opts) {
|
|
|
193205
194067
|
return;
|
|
193206
194068
|
}
|
|
193207
194069
|
if (result?.dispatchId) {
|
|
193208
|
-
|
|
194070
|
+
const delivery = dispatchRemoteAdapter?.deliveryStatus(result.dispatchId);
|
|
194071
|
+
dispatchToWork.set(result.dispatchId, {
|
|
194072
|
+
workId,
|
|
194073
|
+
workorderId: wid,
|
|
194074
|
+
...delivery ? { nodeId: delivery.nodeId } : {}
|
|
194075
|
+
});
|
|
194076
|
+
if (delivery) workToSession.set(workId, { dispatchId: result.dispatchId, daemonId: delivery.nodeId });
|
|
193209
194077
|
dispatchedWorks.add(workId);
|
|
193210
194078
|
} else if (result?.kind === "spawn-failed" || result?.kind === "predispatch-timeout") {
|
|
193211
194079
|
dispatchLedger.close(ledgerId, { outcome: "failed", reason: `dispatch ${result.kind}` }).catch(ledgerWarn("close(spawn-failed)"));
|
|
@@ -193238,7 +194106,7 @@ async function startServe(opts) {
|
|
|
193238
194106
|
const kernelModel = newKernel.model;
|
|
193239
194107
|
const art = kernelModel.artifacts.get(nodeId);
|
|
193240
194108
|
const revWid = art?.workspace ?? "";
|
|
193241
|
-
const ledgerId = `dispatch:${(0,
|
|
194109
|
+
const ledgerId = `dispatch:${(0, import_node_crypto41.randomUUID)()}`;
|
|
193242
194110
|
try {
|
|
193243
194111
|
await dispatchLedger.insertOpen({
|
|
193244
194112
|
id: ledgerId,
|
|
@@ -193269,11 +194137,16 @@ async function startServe(opts) {
|
|
|
193269
194137
|
},
|
|
193270
194138
|
async cancelSession(ref2) {
|
|
193271
194139
|
const workSess = workToSession.get(ref2);
|
|
193272
|
-
|
|
193273
|
-
|
|
193274
|
-
|
|
194140
|
+
const persistedWorkDispatch = [...dispatchToWork.entries()].find(([, mapping]) => mapping.workId === ref2);
|
|
194141
|
+
if ((workSess || persistedWorkDispatch) && hub) {
|
|
194142
|
+
const dispatchId = workSess?.dispatchId ?? persistedWorkDispatch[0];
|
|
194143
|
+
const daemonId = workSess?.daemonId ?? persistedWorkDispatch[1].nodeId;
|
|
194144
|
+
console.log(`[new-engine] cancelSession workId=${ref2} dispatchId=${dispatchId} daemon=${daemonId ?? "unknown"}`);
|
|
194145
|
+
const fenced = await fenceDispatch(traceStore, dispatchId, "engine-cancel-before-successor");
|
|
194146
|
+
if (!fenced) throw new Error(`\u627E\u4E0D\u5230 dispatch run ${dispatchId}\uFF0C\u62D2\u7EDD\u5728\u65E0 fencing \u65F6\u521B\u5EFA\u540E\u7EE7`);
|
|
194147
|
+
if (daemonId) hub.dispatch(daemonId, { type: "kill", dispatchId });
|
|
193275
194148
|
workToSession.delete(ref2);
|
|
193276
|
-
dispatchLedger.close(
|
|
194149
|
+
dispatchLedger.close(dispatchId, { outcome: "cancelled", reason: "session-cancelled" }).catch(ledgerWarn("close(cancel-work)"));
|
|
193277
194150
|
return;
|
|
193278
194151
|
}
|
|
193279
194152
|
const reviewSess = reviewToSession.get(ref2);
|
|
@@ -193496,7 +194369,15 @@ async function startServe(opts) {
|
|
|
193496
194369
|
const out = await actorMemoryService.list({ actorId, artifactId }, { scope: "all", keywords: queryTerms, limit: MEMORY_INJECT_MAX, offset: 0 }).catch(() => null);
|
|
193497
194370
|
return out?.items ?? [];
|
|
193498
194371
|
},
|
|
193499
|
-
|
|
194372
|
+
resolveUpstreamHandoff: createUpstreamHandoffInputResolver({
|
|
194373
|
+
loadWorkorder: async (workOrderId) => {
|
|
194374
|
+
const store = companyEngine.kernel.getStore?.();
|
|
194375
|
+
if (!store) throw new Error(`engine store unavailable for ${companyId}`);
|
|
194376
|
+
return store.transaction((tx) => tx.loadWorkorder(workOrderId));
|
|
194377
|
+
},
|
|
194378
|
+
getHandoff: (attemptId) => traceStore.getHandoff(attemptId)
|
|
194379
|
+
}),
|
|
194380
|
+
executionContinuityInstructions: continuityMode === "off" ? renderBusinessHandoffProtocol() : renderContinuityProtocol(),
|
|
193500
194381
|
...continuityMode === "resume" ? {
|
|
193501
194382
|
resolveExecutionResumePack: async (artifactId, workOrderId, scope) => {
|
|
193502
194383
|
const pack = await executionContinuity.service.resumePack(workOrderId, artifactId, companyId, {
|
|
@@ -193577,8 +194458,15 @@ async function startServe(opts) {
|
|
|
193577
194458
|
});
|
|
193578
194459
|
const slot = { dispatcher: d, logged: 0, ticking: false };
|
|
193579
194460
|
dispatchers.set(companyId, slot);
|
|
193580
|
-
newEngineProduce = async (artifactId, actorId, dispatchId) => {
|
|
193581
|
-
const result = await d.requestProduce({
|
|
194461
|
+
newEngineProduce = async (artifactId, actorId, workId, dispatchId) => {
|
|
194462
|
+
const result = await d.requestProduce({
|
|
194463
|
+
artifactId,
|
|
194464
|
+
actor: actorId,
|
|
194465
|
+
bypassScheduling: true,
|
|
194466
|
+
engineWorkId: workId,
|
|
194467
|
+
engineCompanyId: companyId,
|
|
194468
|
+
...dispatchId !== void 0 ? { dispatchId } : {}
|
|
194469
|
+
});
|
|
193582
194470
|
return { kind: result.kind, dispatchId: result.kind === "dispatched" ? result.dispatchId : void 0 };
|
|
193583
194471
|
};
|
|
193584
194472
|
newEngineReplyWork = async (artifactId, actorId, issueId, dispatchId) => {
|
|
@@ -194258,7 +195146,7 @@ var SessionManager = class {
|
|
|
194258
195146
|
// ../cli/src/daemon/ws-client.ts
|
|
194259
195147
|
init_wrapper();
|
|
194260
195148
|
var import_node_os9 = require("node:os");
|
|
194261
|
-
var
|
|
195149
|
+
var import_node_crypto42 = require("node:crypto");
|
|
194262
195150
|
init_src6();
|
|
194263
195151
|
|
|
194264
195152
|
// ../cli/src/daemon/detect-adapters.ts
|
|
@@ -194610,6 +195498,10 @@ var DaemonWsClient = class {
|
|
|
194610
195498
|
stopped = false;
|
|
194611
195499
|
/** 收到 update 但有活跃会话时置真:延后到所有会话结束再自更新,避免打断执行中的 agent。 */
|
|
194612
195500
|
pendingUpdate = false;
|
|
195501
|
+
/** 已回 dispatch_received、尚未完成 runtime spawn 的派发;也随 hello 上报供重连对账。 */
|
|
195502
|
+
pendingDispatchIds = /* @__PURE__ */ new Set();
|
|
195503
|
+
/** setup 期间收到 kill 的派发。spawn 返回后先杀句柄,不得再宣告 session_started。 */
|
|
195504
|
+
cancelledPendingDispatchIds = /* @__PURE__ */ new Set();
|
|
194613
195505
|
/**
|
|
194614
195506
|
* 回执 outbox(proposal run-ledger §4.2):**服务端据以判决的帧**发出后先留一份,收到
|
|
194615
195507
|
* 服务端 ack 才删;ws 抖动坏窗口发失败/丢失的帧,在重连时全量重发——投递升级 at-least-once,
|
|
@@ -194738,11 +195630,19 @@ var DaemonWsClient = class {
|
|
|
194738
195630
|
const { dispatchId, job } = msg;
|
|
194739
195631
|
const t0 = Date.now();
|
|
194740
195632
|
log2("[node-cli]", `\u2190 dispatch ${dispatchId} artifact=${job.artifactId}`);
|
|
195633
|
+
if (this.pendingDispatchIds.has(dispatchId) || this.sessions.get(dispatchId)) {
|
|
195634
|
+
this.send({ type: "dispatch_received", dispatchId });
|
|
195635
|
+
break;
|
|
195636
|
+
}
|
|
195637
|
+
this.pendingDispatchIds.add(dispatchId);
|
|
195638
|
+
this.send({ type: "dispatch_received", dispatchId });
|
|
194741
195639
|
try {
|
|
194742
195640
|
if (this.preflight) {
|
|
194743
195641
|
const preflightFailure = await preflightOasisAccess(job);
|
|
194744
195642
|
if (preflightFailure) {
|
|
194745
195643
|
log2("[node-cli]", ` preflight failed: ${preflightFailure.reason}`);
|
|
195644
|
+
this.pendingDispatchIds.delete(dispatchId);
|
|
195645
|
+
this.cancelledPendingDispatchIds.delete(dispatchId);
|
|
194746
195646
|
this.sendKeyFrame(dispatchId, { type: "session_exited", dispatchId, sessionId: "preflight-failed", info: preflightFailure });
|
|
194747
195647
|
return;
|
|
194748
195648
|
}
|
|
@@ -194763,8 +195663,8 @@ var DaemonWsClient = class {
|
|
|
194763
195663
|
actor: job.actor,
|
|
194764
195664
|
...job.action ? { action: job.action } : {}
|
|
194765
195665
|
});
|
|
194766
|
-
|
|
194767
|
-
|
|
195666
|
+
this.pendingDispatchIds.delete(dispatchId);
|
|
195667
|
+
const cancelledBeforeStart = this.cancelledPendingDispatchIds.delete(dispatchId);
|
|
194768
195668
|
handle.onOutput?.((chunk) => this.sendStream({ type: "session_output", dispatchId, chunk }));
|
|
194769
195669
|
handle.onTelemetry?.((event) => this.sendStream({ type: "session_event", dispatchId, event }));
|
|
194770
195670
|
handle.onExit((info) => {
|
|
@@ -194773,7 +195673,16 @@ var DaemonWsClient = class {
|
|
|
194773
195673
|
this.sendKeyFrame(dispatchId, { type: "session_exited", dispatchId, sessionId: handle.id, info });
|
|
194774
195674
|
this.maybeRunPendingUpdate();
|
|
194775
195675
|
});
|
|
195676
|
+
if (cancelledBeforeStart) {
|
|
195677
|
+
log2("[node-cli]", ` dispatch ${dispatchId} \u5728 setup \u671F\u95F4\u5DF2\u88AB fencing\uFF0Cspawn \u540E\u7ACB\u5373 kill`);
|
|
195678
|
+
await handle.kill();
|
|
195679
|
+
} else {
|
|
195680
|
+
log2("[node-cli]", ` \u2192 session_started id=${handle.id} setup=${Date.now() - t0}ms`);
|
|
195681
|
+
this.sendKeyFrame(dispatchId, { type: "session_started", dispatchId, sessionId: handle.id, jobArtifactId: job.artifactId });
|
|
195682
|
+
}
|
|
194776
195683
|
} catch (err) {
|
|
195684
|
+
this.pendingDispatchIds.delete(dispatchId);
|
|
195685
|
+
this.cancelledPendingDispatchIds.delete(dispatchId);
|
|
194777
195686
|
log2("[node-cli]", ` spawn failed: ${err}`);
|
|
194778
195687
|
const errorMessage = String(err?.stack ?? err).slice(0, 8e3);
|
|
194779
195688
|
this.sendKeyFrame(dispatchId, { type: "session_exited", dispatchId, sessionId: "spawn-failed", info: { code: null, reason: "error", errorMessage } });
|
|
@@ -194783,7 +195692,9 @@ var DaemonWsClient = class {
|
|
|
194783
195692
|
case "kill": {
|
|
194784
195693
|
log2("[node-cli]", `\u2190 kill dispatch=${msg.dispatchId}`);
|
|
194785
195694
|
const killed = await this.sessions.kill(msg.dispatchId);
|
|
194786
|
-
if (!killed) {
|
|
195695
|
+
if (!killed && this.pendingDispatchIds.has(msg.dispatchId)) {
|
|
195696
|
+
this.cancelledPendingDispatchIds.add(msg.dispatchId);
|
|
195697
|
+
} else if (!killed) {
|
|
194787
195698
|
this.sendKeyFrame(msg.dispatchId, { type: "session_exited", dispatchId: msg.dispatchId, sessionId: "missing-session", info: { code: null, reason: "timeout" } });
|
|
194788
195699
|
}
|
|
194789
195700
|
break;
|
|
@@ -194856,7 +195767,7 @@ var DaemonWsClient = class {
|
|
|
194856
195767
|
*/
|
|
194857
195768
|
async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
|
|
194858
195769
|
if (artifactIds.length === 0) return {};
|
|
194859
|
-
const requestId = (0,
|
|
195770
|
+
const requestId = (0, import_node_crypto42.randomUUID)();
|
|
194860
195771
|
return new Promise((resolve9, reject) => {
|
|
194861
195772
|
const timer = setTimeout(() => {
|
|
194862
195773
|
this.gcPending.delete(requestId);
|
|
@@ -194939,7 +195850,9 @@ var DaemonWsClient = class {
|
|
|
194939
195850
|
...this.daemonVersion ? { daemonVersion: this.daemonVersion } : {},
|
|
194940
195851
|
connectorReadiness: inspectConnectorRuntimeReadiness(),
|
|
194941
195852
|
...this.nodeName ? { nodeName: this.nodeName } : {},
|
|
194942
|
-
...activeSessions.length > 0 ? { activeSessions } : {}
|
|
195853
|
+
...activeSessions.length > 0 ? { activeSessions } : {},
|
|
195854
|
+
// 始终带字段(空数组也带)作为新协议能力标志;服务端仅对支持该字段的节点做缺席确认。
|
|
195855
|
+
pendingDispatchIds: [...this.pendingDispatchIds]
|
|
194943
195856
|
};
|
|
194944
195857
|
}
|
|
194945
195858
|
};
|
|
@@ -195143,7 +196056,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
|
|
|
195143
196056
|
// ../cli/src/daemon/machine-id.ts
|
|
195144
196057
|
var import_node_child_process14 = require("node:child_process");
|
|
195145
196058
|
var import_node_fs20 = require("node:fs");
|
|
195146
|
-
var
|
|
196059
|
+
var import_node_crypto43 = require("node:crypto");
|
|
195147
196060
|
var import_node_os12 = require("node:os");
|
|
195148
196061
|
function linuxMachineId() {
|
|
195149
196062
|
for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
@@ -195209,7 +196122,7 @@ var defaultSources = {
|
|
|
195209
196122
|
function resolveNodeId(sources = {}) {
|
|
195210
196123
|
const s2 = { ...defaultSources, ...sources };
|
|
195211
196124
|
const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
|
|
195212
|
-
const digest = (0,
|
|
196125
|
+
const digest = (0, import_node_crypto43.createHash)("sha256").update(material).digest("hex").slice(0, 12);
|
|
195213
196126
|
return `node-${digest}`;
|
|
195214
196127
|
}
|
|
195215
196128
|
|
|
@@ -195655,18 +196568,21 @@ var COMMAND_DECLS = {
|
|
|
195655
196568
|
examples: ["oasis continuity-effect --kind send-message --target chat:release --key release-v1"]
|
|
195656
196569
|
},
|
|
195657
196570
|
"continuity-handoff": {
|
|
195658
|
-
usage: "oasis continuity-handoff --outcome <status> --summary <text> [--outputs <json-array>] [--unresolved <json-string-array>] [--risks <json-string-array>] [--next <json-string-array>] [--verification-gaps <json-string-array>]",
|
|
195659
|
-
description: "\u4E3A\u5F53\u524D\u6D3E\u53D1 attempt \u5199\
|
|
196571
|
+
usage: "oasis continuity-handoff --outcome <status> --summary <text> --user-summary <text> [--user-deliverables <json-array>] [--user-next-step <text>] [--outputs <json-array>] [--unresolved <json-string-array>] [--risks <json-string-array>] [--next <json-string-array>] [--verification-gaps <json-string-array>]",
|
|
196572
|
+
description: "\u4E3A\u5F53\u524D\u6D3E\u53D1 attempt \u5199\u6280\u672F\u578B NodeHandoff\uFF0C\u5E76\u5355\u72EC\u5199\u4F9B\u4F7F\u7528\u8005\u67E5\u770B\u7684 userHandoff\u3002",
|
|
195660
196573
|
flags: [
|
|
195661
196574
|
{ name: "outcome", desc: "succeeded | no-output | failed | cancelled | timeout | orphaned", required: true },
|
|
195662
196575
|
{ name: "summary", desc: "\u4E0D\u8D85\u8FC7 400 \u5B57\u7684\u6458\u8981", required: true },
|
|
196576
|
+
{ name: "user-summary", desc: "\u505A\u4E86\u4EC0\u4E48\u3001\u5F62\u6210\u4EC0\u4E48\u7ED3\u679C\u3001\u4E0B\u6E38\u53EF\u57FA\u4E8E\u4EC0\u4E48\u7EE7\u7EED\uFF1B\u5931\u8D25\u65F6\u5199\u672A\u4EA4\u4ED8\u539F\u56E0", required: true },
|
|
196577
|
+
{ name: "user-deliverables", desc: "\u53EF\u6253\u5F00\u7684\u4E1A\u52A1\u4EA4\u4ED8\u7269\u5F15\u7528 JSON array\uFF1B\u4E0D\u8981\u586B conclude/gap/checkpoint/op" },
|
|
196578
|
+
{ name: "user-next-step", desc: "\u7ED9\u4E0B\u6E38\u6216\u5904\u7406\u963B\u585E\u8005\u7684\u7B80\u77ED\u4E0B\u4E00\u6B65" },
|
|
195663
196579
|
{ name: "outputs", desc: "\u53D8\u5316\u8F93\u51FA\u5F15\u7528 JSON array" },
|
|
195664
196580
|
{ name: "unresolved", desc: "\u672A\u89E3\u51B3\u95EE\u9898 JSON string array" },
|
|
195665
196581
|
{ name: "risks", desc: "\u98CE\u9669 JSON string array" },
|
|
195666
196582
|
{ name: "next", desc: "\u4E0B\u4E00\u6B65 JSON string array" },
|
|
195667
196583
|
{ name: "verification-gaps", desc: "\u9A8C\u8BC1\u7F3A\u53E3 JSON string array" }
|
|
195668
196584
|
],
|
|
195669
|
-
examples: [`oasis continuity-handoff --outcome succeeded --summary "implemented and tested" --next '[]'`]
|
|
196585
|
+
examples: [`oasis continuity-handoff --outcome succeeded --summary "implemented and tested" --user-summary "\u5F00\u53D1\u5DF2\u5B8C\u6210\u5E76\u901A\u8FC7\u6D4B\u8BD5\u3002" --user-deliverables '[{"kind":"artifact","id":"artifact:dev:x","label":"\u5F00\u53D1\u7ED3\u679C"}]' --user-next-step "\u4E0B\u6E38\u53EF\u5F00\u59CB\u9A8C\u6536\u3002" --next '[]'`]
|
|
195670
196586
|
},
|
|
195671
196587
|
"continuity-view": {
|
|
195672
196588
|
usage: "oasis continuity-view <workOrderId>",
|
|
@@ -196443,13 +197359,13 @@ function ownFlagsFromDecl(command) {
|
|
|
196443
197359
|
}
|
|
196444
197360
|
|
|
196445
197361
|
// ../cli/src/connector-effect-runner.ts
|
|
196446
|
-
var
|
|
197362
|
+
var import_node_crypto44 = require("node:crypto");
|
|
196447
197363
|
var import_node_child_process15 = require("node:child_process");
|
|
196448
197364
|
init_src6();
|
|
196449
197365
|
function stableKey(effect) {
|
|
196450
197366
|
const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
|
|
196451
197367
|
const canonical = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
|
|
196452
|
-
return `sha256:${(0,
|
|
197368
|
+
return `sha256:${(0, import_node_crypto44.createHash)("sha256").update(canonical).digest("hex")}`;
|
|
196453
197369
|
}
|
|
196454
197370
|
async function runConnectorEffect(connector, toolArgs, deps) {
|
|
196455
197371
|
const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
|
|
@@ -196579,7 +197495,7 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
|
|
|
196579
197495
|
record-action <artifactId> --name <\u52A8\u4F5C\u540D> [--pass true] [--result <json>] [--params <json>] # \xA79.2 grounding \u7559\u75D5\uFF1A\u4E3A action_vs_oplog \u8BB0\u4E00\u6761\u901A\u8FC7\u7684 tool_call\uFF08\u771F\u8DD1\u8FC7\u5065\u5EB7\u63A2\u9488\u540E record-action --name health_probe --pass true\uFF09\uFF1B\u58F0\u660E\u4E86 requiredActions \u7684\u7C7B\u578B\u6CA1\u6709\u5B83 conclude \u4F1A\u88AB\u673A\u68B0\u62E6
|
|
196580
197496
|
continuity-checkpoint --pending <json-string-array> --recoverability full|partial|none [--trigger plan_completed|stage_completed] [--refs <json-array>] [--completed <json-string-array>]
|
|
196581
197497
|
continuity-effect --kind <kind> --target <target> --key <stable-key>
|
|
196582
|
-
continuity-handoff --outcome <status> --summary <text> [--outputs <json-array>] [--unresolved <json-string-array>] [--risks <json-string-array>] [--next <json-string-array>] [--verification-gaps <json-string-array>]
|
|
197498
|
+
continuity-handoff --outcome <status> --summary <text> --user-summary <text> [--user-deliverables <json-array>] [--user-next-step <text>] [--outputs <json-array>] [--unresolved <json-string-array>] [--risks <json-string-array>] [--next <json-string-array>] [--verification-gaps <json-string-array>]
|
|
196583
197499
|
continuity-view <workOrderId> # \u534F\u8C03\u89C6\u56FE\uFF1A\u516B\u6001\u8FDB\u5EA6\u3001\u4F9D\u8D56\u963B\u585E\u3001\u6309\u4F18\u5148\u7EA7 todo
|
|
196584
197500
|
continuity-resume <workOrderId> <nodeId> # \u8C03\u8BD5\u5F53\u524D\u8282\u70B9\u4F1A\u6536\u5230\u7684\u7ED3\u6784\u5316 ResumePack
|
|
196585
197501
|
resolve <annotationId> [--as resolved|wontfix|acknowledged]
|
|
@@ -197159,7 +198075,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
|
|
|
197159
198075
|
const store = createNodeTokenStore(path23.join(dir, "node-tokens.json"));
|
|
197160
198076
|
const sub = positional[0];
|
|
197161
198077
|
if (sub === "issue") {
|
|
197162
|
-
const id = flags.get("id") ?? `node-${(0,
|
|
198078
|
+
const id = flags.get("id") ?? `node-${(0, import_node_crypto45.randomUUID)()}`;
|
|
197163
198079
|
const token2 = store.issue(id);
|
|
197164
198080
|
println(token2);
|
|
197165
198081
|
process.stderr.write(
|
|
@@ -197596,14 +198512,26 @@ Trace: ${run.traceRunId}`);
|
|
|
197596
198512
|
break;
|
|
197597
198513
|
}
|
|
197598
198514
|
case "continuity-handoff": {
|
|
198515
|
+
const outcome = need(flags, "outcome");
|
|
198516
|
+
const changedOutputs = flags.has("outputs") ? parseArrayFlag(flags.get("outputs"), "outputs") : [];
|
|
198517
|
+
const userSummary = need(flags, "user-summary").trim();
|
|
198518
|
+
if (!userSummary) throw new Error("--user-summary \u4E0D\u80FD\u4E3A\u7A7A");
|
|
198519
|
+
const userDeliverables = flags.has("user-deliverables") ? parseArrayFlag(flags.get("user-deliverables"), "user-deliverables") : [];
|
|
198520
|
+
const userNextStep = flags.get("user-next-step")?.trim() ?? "";
|
|
197599
198521
|
const result = await api.request("PUT", "/api/execution-continuity/attempts/current/handoff", {
|
|
197600
|
-
outcome
|
|
198522
|
+
outcome,
|
|
197601
198523
|
summary: need(flags, "summary"),
|
|
197602
|
-
changedOutputs
|
|
198524
|
+
changedOutputs,
|
|
197603
198525
|
unresolvedIssues: flags.has("unresolved") ? parseStringArrayFlag(flags.get("unresolved"), "unresolved") : [],
|
|
197604
198526
|
risks: flags.has("risks") ? parseStringArrayFlag(flags.get("risks"), "risks") : [],
|
|
197605
198527
|
nextActions: flags.has("next") ? parseStringArrayFlag(flags.get("next"), "next") : [],
|
|
197606
|
-
verificationGaps: flags.has("verification-gaps") ? parseStringArrayFlag(flags.get("verification-gaps"), "verification-gaps") : []
|
|
198528
|
+
verificationGaps: flags.has("verification-gaps") ? parseStringArrayFlag(flags.get("verification-gaps"), "verification-gaps") : [],
|
|
198529
|
+
userHandoff: {
|
|
198530
|
+
status: outcome === "succeeded" ? "delivered" : "not_delivered",
|
|
198531
|
+
summary: userSummary,
|
|
198532
|
+
deliverables: outcome === "succeeded" ? userDeliverables : [],
|
|
198533
|
+
nextStep: userNextStep
|
|
198534
|
+
}
|
|
197607
198535
|
});
|
|
197608
198536
|
println(JSON.stringify(result, null, 2));
|
|
197609
198537
|
break;
|
|
@@ -199338,7 +200266,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
|
|
|
199338
200266
|
}
|
|
199339
200267
|
|
|
199340
200268
|
// src/index.ts
|
|
199341
|
-
var PKG_VERSION = true ? "0.1.
|
|
200269
|
+
var PKG_VERSION = true ? "0.1.101" : "dev";
|
|
199342
200270
|
var OASIS_DIR = path25.join(os10.homedir(), ".oasis");
|
|
199343
200271
|
var CONFIG_FILE = path25.join(OASIS_DIR, "node-config.json");
|
|
199344
200272
|
var PID_FILE = path25.join(OASIS_DIR, "node.pid");
|
|
@@ -199507,7 +200435,7 @@ function selfUpdate() {
|
|
|
199507
200435
|
const inner = `{ echo "[oasis] self-update \u2192 ${PKG_NAME}@latest (registry ${OFFICIAL}, \u5931\u8D25\u56DE\u843D\u9ED8\u8BA4\u6E90)"; sleep 2; { ${installOfficial} || ${installFallback}; } && ${printVersion} && ${startCmd}; } >> '${LOG_FILE}' 2>&1`;
|
|
199508
200436
|
const hasSystemd = process.platform === "linux" && (() => {
|
|
199509
200437
|
try {
|
|
199510
|
-
(0, import_node_child_process16.execSync)("
|
|
200438
|
+
(0, import_node_child_process16.execSync)("systemd-run --user --scope --quiet -- true", { stdio: "ignore" });
|
|
199511
200439
|
return true;
|
|
199512
200440
|
} catch {
|
|
199513
200441
|
return false;
|