@tea-agent/loop-agent 0.39.0-beta.2 → 0.39.0-beta.4
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/AGENTS.md +4 -1
- package/CHANGELOG.md +187 -92
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +429 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/executors/dag-pi-executor.js +1691 -29
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +51 -12
- package/dist/executors/pi-sdk-executor.js +180 -33
- package/dist/executors/shell-executor.js +1152 -171
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +32 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +19 -5
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +60 -17
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +36 -0
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operation-store.js +226 -6
- package/dist/worker/console/operator-actions.js +263 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +151 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-zsNyvGaH.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BDjZ5kE1.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-BkgaujnN.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-CqxOJi0j.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-C2JHLdRi.js} +1 -1
- package/dist/worker/console/static/assets/channel-BuqaGKAT.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-DF_YSvOS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-D3Ort5Vo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-XfAfZeUC.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-SLBibhf5.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-Dz29Ye_f.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DB2G4H8V.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-CIoGkxdu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-CG78FGkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-ZDHAvXPd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BKkkC4VA.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-SposUZsO.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-BSCtQpD7.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-DgFHrw9l.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-BVBasYbj.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-6r_4rN_c.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-JmuaB41P.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-xXvAY3sk.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-D2B3lY2o.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-tK54tMEX.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CNtELzV-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-C1xKMKcW.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CZ7dlPMz.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BqGb42Xe.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-D_BlXEtt.js} +1 -1
- package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-D5zGX6fG.js} +68 -68
- package/dist/worker/console/static/assets/index-DBbhESQ_.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Vce2vmIe.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DDwVBAvI.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-y_Ew-wv4.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-CjXJFp1K.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-1itMst1Q.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-C-D3ZRUa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BiYywmZc.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-l2fydNWA.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-Dz0hNbYW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-BujI_mLZ.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-pWe84beb.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-ByYu2frg.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-CoPnilFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-1Md2LnS0.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DqqqAoSH.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C4sGkf--.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-ByxJrUzW.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-D8MLgM0O.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DnBis-wZ.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-uuyYT7UO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-BjgabhwK.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/failure-category.js +12 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +366 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +378 -160
- package/dist/workflows/dag/frontend-prewrite-gate.js +343 -391
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +201 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +7 -424
- package/dist/workflows/dag/frontend-review-context.js +195 -8
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +665 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +425 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +439 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +97 -18
- package/dist/workflows/dag/frontend-writer-admission.js +260 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +572 -500
- package/dist/workflows/dag/node-execution.js +307 -70
- package/dist/workflows/dag/prompt.js +10 -18
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +228 -4
- package/dist/workflows/dag/runner.js +314 -119
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +235 -23
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +60 -10
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +3 -3
- package/package.json +5 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-implementation/SKILL.md +4 -4
- package/skills/frontend-implementation/references/design-spec.md +11 -7
- package/skills/frontend-implementation/references/node-contracts.md +20 -11
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/skills/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { assertFrontendSplitProjectionSuccessor, frontendSplitProjectionSchema, } from "../../workflows/dag/frontend-split-application-service.js";
|
|
4
|
+
import { buildFrontendHumanDecisionProjection, consumeFrontendHumanDecision, materializeFrontendHumanDecision, parseFrontendHumanDecisionProjection, parseFrontendHumanDecisionReceipt, } from "../../workflows/dag/frontend-human-decision.js";
|
|
5
|
+
import { buildFrontendSplitAggregateCloseout, computeFrontendSplitDigest, finalizeFrontendSplitAggregate, frontendCommittedFactRefSchema, frontendSplitChildCloseoutFactsSchema, initializeFrontendSplitParent, } from "../../workflows/dag/frontend-split-orchestrator.js";
|
|
6
|
+
import { frontendCloseoutFactsSchema, frontendTestHandoffSchema, } from "../../workflows/dag/frontend-closeout.js";
|
|
3
7
|
import { listJsonFiles, readJsonIfExists, sha256Json, writeSecureJson, } from "./app-data.js";
|
|
4
8
|
export const TERMINAL_OPERATION_STATES = new Set(["succeeded", "failed", "timed-out"]);
|
|
5
9
|
function operationFile(appData, operationId) {
|
|
@@ -15,16 +19,21 @@ export function computeActionPayloadHash(action, actionParams) {
|
|
|
15
19
|
export function newOperationId() {
|
|
16
20
|
return `op_${randomBytes(12).toString("hex")}`;
|
|
17
21
|
}
|
|
22
|
+
const sharedWriteChains = new Map();
|
|
18
23
|
export class OperationStore {
|
|
19
24
|
appData;
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
* (Windows EPERM) or last-write-wins lost patches.
|
|
24
|
-
*/
|
|
25
|
-
writeChains = new Map();
|
|
25
|
+
/** Shared per-store-root, per-key write lock. Multiple OperationStore instances
|
|
26
|
+
* in one worker process must observe the same CAS critical section. */
|
|
27
|
+
writeChains;
|
|
26
28
|
constructor(appData) {
|
|
27
29
|
this.appData = appData;
|
|
30
|
+
const root = path.resolve(appData.operations);
|
|
31
|
+
let chains = sharedWriteChains.get(root);
|
|
32
|
+
if (!chains) {
|
|
33
|
+
chains = new Map();
|
|
34
|
+
sharedWriteChains.set(root, chains);
|
|
35
|
+
}
|
|
36
|
+
this.writeChains = chains;
|
|
28
37
|
}
|
|
29
38
|
serialize(key, op) {
|
|
30
39
|
const prev = this.writeChains.get(key) ?? Promise.resolve();
|
|
@@ -66,6 +75,7 @@ export class OperationStore {
|
|
|
66
75
|
payloadHash,
|
|
67
76
|
state: "queued",
|
|
68
77
|
createdAt: now,
|
|
78
|
+
revision: 1,
|
|
69
79
|
taskId: input.taskId,
|
|
70
80
|
cliArgs: input.cliArgs,
|
|
71
81
|
...(input.externalCommand
|
|
@@ -77,11 +87,61 @@ export class OperationStore {
|
|
|
77
87
|
return { kind: "created", operation };
|
|
78
88
|
});
|
|
79
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Idempotently materialize a split child/integration operation with a
|
|
92
|
+
* caller-specified deterministic operationId (from the split manifest).
|
|
93
|
+
* Unlike create() (random op_<hex> id) and save/update (no create), this is
|
|
94
|
+
* the durable cross-restart idempotency boundary for the production
|
|
95
|
+
* FrontendSplitTaskMaterializer backend: an existing operation file with the
|
|
96
|
+
* same taskId + payloadHash is returned as {created:false}; any drift fails
|
|
97
|
+
* closed instead of overwriting or silently diverging.
|
|
98
|
+
*/
|
|
99
|
+
async ensureSplitMaterializedOperation(input) {
|
|
100
|
+
return this.serialize(input.operationId, async () => {
|
|
101
|
+
const existing = await this.get(input.operationId);
|
|
102
|
+
if (existing) {
|
|
103
|
+
if (existing.taskId !== input.taskId ||
|
|
104
|
+
existing.payloadHash !== input.payloadHash) {
|
|
105
|
+
throw new Error("frontend split operation materialization payload drift");
|
|
106
|
+
}
|
|
107
|
+
return { operation: existing, created: false };
|
|
108
|
+
}
|
|
109
|
+
const now = new Date().toISOString();
|
|
110
|
+
const operation = {
|
|
111
|
+
schemaVersion: 1,
|
|
112
|
+
operationId: input.operationId,
|
|
113
|
+
clientRequestId: `split:${input.operationId}`,
|
|
114
|
+
action: input.action,
|
|
115
|
+
actionParams: input.actionParams,
|
|
116
|
+
payloadHash: input.payloadHash,
|
|
117
|
+
state: "queued",
|
|
118
|
+
createdAt: now,
|
|
119
|
+
revision: 1,
|
|
120
|
+
taskId: input.taskId,
|
|
121
|
+
...(input.cliArgs ? { cliArgs: input.cliArgs } : {}),
|
|
122
|
+
};
|
|
123
|
+
await writeSecureJson(operationFile(this.appData, input.operationId), operation);
|
|
124
|
+
return { operation, created: true };
|
|
125
|
+
});
|
|
126
|
+
}
|
|
80
127
|
async get(operationId) {
|
|
81
128
|
return readJsonIfExists(operationFile(this.appData, operationId));
|
|
82
129
|
}
|
|
83
130
|
async save(operation) {
|
|
84
131
|
return this.serialize(operation.operationId, async () => {
|
|
132
|
+
const current = await this.get(operation.operationId);
|
|
133
|
+
if (current?.revision !== undefined && operation.revision !== current.revision)
|
|
134
|
+
throw new Error("operation revision may not be changed by save");
|
|
135
|
+
if (operation.frontendHumanDecision !== undefined || current?.frontendHumanDecision !== undefined) {
|
|
136
|
+
if (!current || JSON.stringify(operation) !== JSON.stringify(current))
|
|
137
|
+
throw new Error("human-decision projection may only be mutated by canonical CAS");
|
|
138
|
+
return current;
|
|
139
|
+
}
|
|
140
|
+
if (operation.frontendSplit !== undefined || current?.frontendSplit !== undefined) {
|
|
141
|
+
if (!current || JSON.stringify(operation) !== JSON.stringify(current))
|
|
142
|
+
throw new Error("split projection may only be mutated by compareAndSwapFrontendSplit");
|
|
143
|
+
return current;
|
|
144
|
+
}
|
|
85
145
|
await writeSecureJson(operationFile(this.appData, operation.operationId), operation);
|
|
86
146
|
return operation;
|
|
87
147
|
});
|
|
@@ -92,6 +152,10 @@ export class OperationStore {
|
|
|
92
152
|
if (!current) {
|
|
93
153
|
throw new Error(`operation not found: ${operationId}`);
|
|
94
154
|
}
|
|
155
|
+
if ("frontendHumanDecision" in patch || (current.frontendHumanDecision !== undefined && ("state" in patch || "frontendExecution" in patch)))
|
|
156
|
+
throw new Error("human-decision projection may only be mutated by canonical CAS");
|
|
157
|
+
if ("frontendSplit" in patch || "revision" in patch || (current.frontendSplit !== undefined && ("state" in patch || "frontendExecution" in patch)))
|
|
158
|
+
throw new Error("split projection may only be mutated by compareAndSwapFrontendSplit");
|
|
95
159
|
const next = {
|
|
96
160
|
...current,
|
|
97
161
|
...patch,
|
|
@@ -106,6 +170,162 @@ export class OperationStore {
|
|
|
106
170
|
return next;
|
|
107
171
|
});
|
|
108
172
|
}
|
|
173
|
+
async materializeFrontendHumanDecision(input) {
|
|
174
|
+
return this.serialize(input.operationId, async () => {
|
|
175
|
+
const current = await this.get(input.operationId);
|
|
176
|
+
if (!current || current.revision === undefined || current.revision !== input.expectedRecordRevision) {
|
|
177
|
+
const fallback = current?.frontendHumanDecision ?? buildFrontendHumanDecisionProjection({ status: "active", operationId: input.operationId, runId: input.envelope.runId, revision: Math.max(1, input.expectedRecordRevision) });
|
|
178
|
+
return { outcome: "rejected", reason: "record revision conflict or legacy record", projection: fallback };
|
|
179
|
+
}
|
|
180
|
+
const projection = current.frontendHumanDecision ?? buildFrontendHumanDecisionProjection({ status: "active", operationId: current.operationId, runId: input.envelope.runId, revision: current.revision });
|
|
181
|
+
const result = materializeFrontendHumanDecision({ projection, envelope: input.envelope, expectedRecordRevision: input.expectedRecordRevision, expectedProjectionDigest: input.expectedProjectionDigest });
|
|
182
|
+
if (result.outcome !== "applied")
|
|
183
|
+
return result;
|
|
184
|
+
const record = { ...current, revision: result.projection.revision, frontendHumanDecision: result.projection, state: "running" };
|
|
185
|
+
await writeSecureJson(operationFile(this.appData, current.operationId), record);
|
|
186
|
+
return result;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async consumeFrontendHumanDecision(input) {
|
|
190
|
+
return this.serialize(input.operationId, async () => {
|
|
191
|
+
const current = await this.get(input.operationId);
|
|
192
|
+
if (!current || current.revision === undefined || !current.frontendHumanDecision) {
|
|
193
|
+
const fallback = buildFrontendHumanDecisionProjection({ status: "active", operationId: input.operationId, runId: input.receipt.runId, revision: Math.max(1, input.expectedRecordRevision) });
|
|
194
|
+
return { outcome: "rejected", reason: "record has no mutable human-decision projection", projection: fallback };
|
|
195
|
+
}
|
|
196
|
+
const projection = parseFrontendHumanDecisionProjection(current.frontendHumanDecision);
|
|
197
|
+
if (current.revision !== input.expectedRecordRevision)
|
|
198
|
+
return { outcome: "rejected", reason: "record revision conflict", projection };
|
|
199
|
+
const result = consumeFrontendHumanDecision({ projection, receipt: input.receipt, expectedRecordRevision: input.expectedRecordRevision, expectedProjectionDigest: input.expectedProjectionDigest, currentTargetDigest: input.currentTargetDigest, now: input.now });
|
|
200
|
+
if (result.outcome !== "applied")
|
|
201
|
+
return result;
|
|
202
|
+
const record = { ...current, revision: result.projection.revision, frontendHumanDecision: result.projection };
|
|
203
|
+
await writeSecureJson(operationFile(this.appData, current.operationId), record);
|
|
204
|
+
return result;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async initializeFrontendHumanApprovedSplit(input) {
|
|
208
|
+
return this.serialize(input.operationId, async () => {
|
|
209
|
+
const current = await this.get(input.operationId);
|
|
210
|
+
const fallback = current?.frontendHumanDecision ?? buildFrontendHumanDecisionProjection({ status: "active", operationId: input.operationId, runId: input.receipt.runId, revision: Math.max(1, input.expectedRecordRevision) });
|
|
211
|
+
if (!current || current.revision === undefined || current.revision !== input.expectedRecordRevision || !current.frontendHumanDecision || !current.frontendExecution || current.frontendSplit)
|
|
212
|
+
return { outcome: "rejected", reason: "record is not an authoritative pending single-task split", projection: fallback };
|
|
213
|
+
const humanProjection = parseFrontendHumanDecisionProjection(current.frontendHumanDecision);
|
|
214
|
+
if (humanProjection.status !== "blocked-human" || humanProjection.envelope.kind !== "split-materialization-approval" || humanProjection.digest !== input.expectedProjectionDigest)
|
|
215
|
+
return { outcome: "rejected", reason: "pending split envelope conflict", projection: humanProjection };
|
|
216
|
+
const receipt = parseFrontendHumanDecisionReceipt(input.receipt);
|
|
217
|
+
if (receipt.kind !== "split-materialization-approval")
|
|
218
|
+
return { outcome: "rejected", reason: "receipt is not a split approval", projection: humanProjection };
|
|
219
|
+
if (JSON.stringify(current.frontendExecution) !== JSON.stringify(input.execution) || input.execution.kind !== "single-task")
|
|
220
|
+
return { outcome: "rejected", reason: "single-task execution conflict", projection: humanProjection };
|
|
221
|
+
const consumed = consumeFrontendHumanDecision({ projection: humanProjection, receipt, expectedRecordRevision: input.expectedRecordRevision, expectedProjectionDigest: input.expectedProjectionDigest, currentTargetDigest: input.currentTargetDigest, now: input.now });
|
|
222
|
+
if (consumed.outcome !== "applied")
|
|
223
|
+
return consumed;
|
|
224
|
+
const initialized = initializeFrontendSplitParent({
|
|
225
|
+
execution: input.execution,
|
|
226
|
+
manifest: input.manifest,
|
|
227
|
+
expectedOperationRevision: input.execution.operationRevision,
|
|
228
|
+
successorOperationRevision: consumed.projection.revision,
|
|
229
|
+
envelopeTargetDigest: humanProjection.envelope.targetDigest,
|
|
230
|
+
activeWriterOperationId: input.activeWriterOperationId,
|
|
231
|
+
activeProviderOperationId: input.activeProviderOperationId,
|
|
232
|
+
authorization: { mode: "human", envelope: humanProjection.envelope, receipt },
|
|
233
|
+
});
|
|
234
|
+
if (initialized.outcome !== "applied")
|
|
235
|
+
return { outcome: "rejected", reason: initialized.reason, projection: humanProjection };
|
|
236
|
+
const splitProjection = frontendSplitProjectionSchema.parse({ revision: consumed.projection.revision, execution: initialized.execution, manifest: input.manifest, parent: initialized.parent });
|
|
237
|
+
const { frontendExecution: _singleTask, ...withoutSingleTask } = current;
|
|
238
|
+
const record = { ...withoutSingleTask, revision: consumed.projection.revision, frontendHumanDecision: consumed.projection, frontendSplit: splitProjection, state: "running" };
|
|
239
|
+
await writeSecureJson(operationFile(this.appData, current.operationId), record);
|
|
240
|
+
return { outcome: "applied", projection: splitProjection, humanDecision: consumed.projection };
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async compareAndSwapFrontendSplit(input) {
|
|
244
|
+
return this.serialize(input.operationId, async () => {
|
|
245
|
+
const current = await this.get(input.operationId);
|
|
246
|
+
if (!current || current.revision === undefined || current.revision !== input.expectedRevision)
|
|
247
|
+
return { outcome: "conflict" };
|
|
248
|
+
const currentProjection = current.frontendSplit;
|
|
249
|
+
if (currentProjection) {
|
|
250
|
+
if (currentProjection.execution.digest !== input.expectedExecutionDigest || currentProjection.parent.digest !== input.expectedParentDigest)
|
|
251
|
+
return { outcome: "conflict" };
|
|
252
|
+
}
|
|
253
|
+
else if (input.expectedExecutionDigest || input.expectedParentDigest)
|
|
254
|
+
return { outcome: "conflict" };
|
|
255
|
+
const next = currentProjection
|
|
256
|
+
? assertFrontendSplitProjectionSuccessor(currentProjection, input.next)
|
|
257
|
+
: frontendSplitProjectionSchema.parse(input.next);
|
|
258
|
+
if (next.revision !== current.revision + 1 || next.parent.operationId !== input.operationId)
|
|
259
|
+
throw new Error("split CAS next revision or operation binding mismatch");
|
|
260
|
+
const state = next.parent.operationState === "success" ? "succeeded" : next.parent.operationTerminal !== undefined ? "failed" : current.state;
|
|
261
|
+
const record = { ...current, revision: next.revision, frontendSplit: next, state };
|
|
262
|
+
await writeSecureJson(operationFile(this.appData, input.operationId), record);
|
|
263
|
+
return { outcome: "applied", projection: next };
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async loadChildCloseoutFacts(input) {
|
|
267
|
+
const factsDir = input.closeoutRef.ref;
|
|
268
|
+
const facts = await readJsonIfExists(path.join(factsDir, "frontend-closeout-facts.json"));
|
|
269
|
+
if (facts === undefined)
|
|
270
|
+
return frontendSplitChildCloseoutFactsSchema.parse({ childId: input.childId, residualRisks: [], frontendTestHandoffRef: undefined });
|
|
271
|
+
const parsedFacts = frontendCloseoutFactsSchema.parse(facts);
|
|
272
|
+
if (computeFrontendSplitDigest(parsedFacts) !== input.closeoutRef.digest)
|
|
273
|
+
throw new Error(`child closeout facts digest mismatch: ${input.childId}`);
|
|
274
|
+
const handoff = await readJsonIfExists(path.join(factsDir, "frontend-test-handoff.json"));
|
|
275
|
+
const frontendTestHandoffRef = handoff === undefined ? undefined : computeFrontendSplitDigest(frontendTestHandoffSchema.parse(handoff));
|
|
276
|
+
return frontendSplitChildCloseoutFactsSchema.parse({ childId: input.childId, residualRisks: parsedFacts.residualRisks ?? [], frontendTestHandoffRef });
|
|
277
|
+
}
|
|
278
|
+
async settleFrontendSplitAggregate(input) {
|
|
279
|
+
return this.serialize(input.operationId, async () => {
|
|
280
|
+
const record = await this.get(input.operationId);
|
|
281
|
+
if (!record || record.revision === undefined || record.revision !== input.expectedRevision || !record.frontendSplit)
|
|
282
|
+
return { outcome: "conflict" };
|
|
283
|
+
const authoritative = frontendSplitProjectionSchema.parse(record.frontendSplit);
|
|
284
|
+
if (authoritative.parent.operationId !== input.operationId
|
|
285
|
+
|| authoritative.revision !== input.expectedRevision
|
|
286
|
+
|| authoritative.execution.digest !== input.expectedExecutionDigest
|
|
287
|
+
|| authoritative.parent.digest !== input.expectedParentDigest
|
|
288
|
+
|| authoritative.manifest.digest !== input.expectedManifestDigest
|
|
289
|
+
|| authoritative.candidateBinding?.digest !== input.expectedCandidateBindingDigest
|
|
290
|
+
|| authoritative.integrationBinding?.digest !== input.expectedIntegrationBindingDigest)
|
|
291
|
+
return { outcome: "conflict" };
|
|
292
|
+
if (!authoritative.candidateBinding)
|
|
293
|
+
return { outcome: "conflict" };
|
|
294
|
+
const expectedAggregate = buildFrontendSplitAggregateCloseout(authoritative.parent, authoritative.manifest, authoritative.candidateBinding, authoritative.integrationBinding, input.childCloseoutFacts);
|
|
295
|
+
if (JSON.stringify(expectedAggregate) !== JSON.stringify(input.aggregate))
|
|
296
|
+
return { outcome: "conflict" };
|
|
297
|
+
const transaction = await input.prepareAggregate({
|
|
298
|
+
operationId: authoritative.parent.operationId,
|
|
299
|
+
aggregate: expectedAggregate,
|
|
300
|
+
});
|
|
301
|
+
try {
|
|
302
|
+
const committedRef = frontendCommittedFactRefSchema.parse(transaction.committedRef);
|
|
303
|
+
const finalized = finalizeFrontendSplitAggregate(authoritative.parent, expectedAggregate, committedRef);
|
|
304
|
+
const next = assertFrontendSplitProjectionSuccessor(authoritative, { ...authoritative, revision: authoritative.revision + 1, parent: finalized.parent, execution: finalized.execution, aggregateCloseout: { value: expectedAggregate, committedRef } });
|
|
305
|
+
const nextRecord = { ...record, revision: next.revision, frontendSplit: next, state: "succeeded" };
|
|
306
|
+
await transaction.commit({
|
|
307
|
+
parentRecordPath: operationFile(this.appData, record.operationId),
|
|
308
|
+
parentRecord: nextRecord,
|
|
309
|
+
});
|
|
310
|
+
return { outcome: "applied", projection: next };
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
await transaction.rollback();
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
async initializeFrontendSplit(expectedRevision, projectionValue) {
|
|
319
|
+
const projection = frontendSplitProjectionSchema.parse(projectionValue);
|
|
320
|
+
return this.serialize(projection.parent.operationId, async () => {
|
|
321
|
+
const current = await this.get(projection.parent.operationId);
|
|
322
|
+
if (!current || current.revision === undefined || current.revision !== expectedRevision || current.frontendSplit !== undefined || projection.revision !== expectedRevision + 1)
|
|
323
|
+
return { outcome: "conflict" };
|
|
324
|
+
const record = { ...current, revision: projection.revision, frontendSplit: projection };
|
|
325
|
+
await writeSecureJson(operationFile(this.appData, current.operationId), record);
|
|
326
|
+
return { outcome: "applied", projection };
|
|
327
|
+
});
|
|
328
|
+
}
|
|
109
329
|
async list() {
|
|
110
330
|
const files = await listJsonFiles(this.appData.operations);
|
|
111
331
|
const ops = [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import { buildOperatorCapabilitiesDocument, OFFICIAL_TAIL_ACTION_IDS, } from "../../shared/operator/capabilities.js";
|
|
3
|
+
import { DAG_RERUN_PROMPT_OVERRIDE_ENV, isDagRerunPromptOverrideEnabled, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
|
|
3
4
|
import { buildOperatorResult, operatorFailed, operatorSucceeded, } from "../../shared/operator/envelope.js";
|
|
4
5
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalBusinessDate, getTaskPaths, loadTaskConfig, } from "../../task/runtime.js";
|
|
5
6
|
import { diffTaskContract, validateDraftForTask, } from "../../task/contract/index.js";
|
|
@@ -401,6 +402,85 @@ async function runReadCli(ctx, args, command) {
|
|
|
401
402
|
});
|
|
402
403
|
}
|
|
403
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Parse and bound the experimental promptOverrides action param. The env
|
|
407
|
+
* switch is checked separately so disabled deployments fail closed before any
|
|
408
|
+
* staging happens. `mode` defaults to "replace"; "append" keeps the frozen
|
|
409
|
+
* base prompt and only appends the operator text as a marked addendum.
|
|
410
|
+
*/
|
|
411
|
+
function parsePromptOverrideParams(raw) {
|
|
412
|
+
if (raw === undefined || raw === null)
|
|
413
|
+
return { kind: "ok", overrides: [] };
|
|
414
|
+
if (!Array.isArray(raw)) {
|
|
415
|
+
return {
|
|
416
|
+
kind: "error",
|
|
417
|
+
message: "promptOverrides must be an array of {nodeId, prompt, mode?}",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (raw.length === 0)
|
|
421
|
+
return { kind: "ok", overrides: [] };
|
|
422
|
+
if (raw.length > 8) {
|
|
423
|
+
return { kind: "error", message: "promptOverrides supports at most 8 nodes" };
|
|
424
|
+
}
|
|
425
|
+
const overrides = [];
|
|
426
|
+
const seen = new Set();
|
|
427
|
+
for (const item of raw) {
|
|
428
|
+
if (!item || typeof item !== "object") {
|
|
429
|
+
return {
|
|
430
|
+
kind: "error",
|
|
431
|
+
message: "promptOverrides entries must be objects {nodeId, prompt, mode?}",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const record = item;
|
|
435
|
+
const nodeId = typeof record.nodeId === "string" ? record.nodeId.trim() : "";
|
|
436
|
+
const prompt = typeof record.prompt === "string" ? record.prompt : "";
|
|
437
|
+
const mode = record.mode === undefined || record.mode === null
|
|
438
|
+
? undefined
|
|
439
|
+
: typeof record.mode === "string"
|
|
440
|
+
? record.mode
|
|
441
|
+
: "invalid";
|
|
442
|
+
if (!nodeId) {
|
|
443
|
+
return { kind: "error", message: "promptOverrides nodeId is required" };
|
|
444
|
+
}
|
|
445
|
+
if (seen.has(nodeId)) {
|
|
446
|
+
return {
|
|
447
|
+
kind: "error",
|
|
448
|
+
message: `promptOverrides contains duplicate nodeId: ${nodeId}`,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
seen.add(nodeId);
|
|
452
|
+
if (mode !== undefined && mode !== "append" && mode !== "replace") {
|
|
453
|
+
return {
|
|
454
|
+
kind: "error",
|
|
455
|
+
message: `promptOverrides mode for ${nodeId} must be append or replace`,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
if (!prompt.trim() || prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
|
|
459
|
+
return {
|
|
460
|
+
kind: "error",
|
|
461
|
+
message: `promptOverrides prompt for ${nodeId} must be 1-${MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS} characters`,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
overrides.push(mode === undefined ? { nodeId, prompt } : { nodeId, prompt, mode });
|
|
465
|
+
}
|
|
466
|
+
return { kind: "ok", overrides };
|
|
467
|
+
}
|
|
468
|
+
/** Stage override bodies under app-data and build `--prompt-override` CLI
|
|
469
|
+
* args. The flag carries the mode: `--prompt-override-append` for append. */
|
|
470
|
+
async function buildPromptOverrideCliArgs(ctx, overrides) {
|
|
471
|
+
const args = [];
|
|
472
|
+
for (const override of overrides) {
|
|
473
|
+
const staged = await stageUtf8Text(ctx.appData, override.prompt, {
|
|
474
|
+
extension: ".md",
|
|
475
|
+
label: `prompt-override-${override.nodeId}`,
|
|
476
|
+
});
|
|
477
|
+
const flag = override.mode === "append"
|
|
478
|
+
? "--prompt-override-append"
|
|
479
|
+
: "--prompt-override";
|
|
480
|
+
args.push(flag, `${override.nodeId}@${staged.path}`);
|
|
481
|
+
}
|
|
482
|
+
return args;
|
|
483
|
+
}
|
|
404
484
|
async function acceptOperation(ctx, input) {
|
|
405
485
|
const created = await ctx.operations.create({
|
|
406
486
|
clientRequestId: input.clientRequestId,
|
|
@@ -456,6 +536,84 @@ async function acceptOperation(ctx, input) {
|
|
|
456
536
|
},
|
|
457
537
|
};
|
|
458
538
|
}
|
|
539
|
+
function splitRuntimeMissing(action) {
|
|
540
|
+
return {
|
|
541
|
+
kind: "error",
|
|
542
|
+
status: 503,
|
|
543
|
+
body: operatorFailed({
|
|
544
|
+
command: action,
|
|
545
|
+
outcome: "blocked",
|
|
546
|
+
code: "INTERNAL_ERROR",
|
|
547
|
+
message: `${action} is unavailable: frontend split runtime is not wired`,
|
|
548
|
+
}),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function numOf(v) {
|
|
552
|
+
return typeof v === "number" ? v : Number(v);
|
|
553
|
+
}
|
|
554
|
+
function splitCommandBase(p) {
|
|
555
|
+
return {
|
|
556
|
+
operationId: str(p.operationId) ?? "",
|
|
557
|
+
expectedRevision: numOf(p.expectedRevision),
|
|
558
|
+
expectedParentDigest: str(p.expectedParentDigest) ?? "",
|
|
559
|
+
expectedManifestDigest: str(p.expectedManifestDigest) ?? "",
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function splitCommandDispatch(action, result) {
|
|
563
|
+
if (result.outcome === "conflict") {
|
|
564
|
+
return {
|
|
565
|
+
kind: "error",
|
|
566
|
+
status: 409,
|
|
567
|
+
body: operatorFailed({
|
|
568
|
+
command: action,
|
|
569
|
+
outcome: "conflict",
|
|
570
|
+
code: "CONFLICT",
|
|
571
|
+
message: "frontend split CAS conflict (stale revision or binding)",
|
|
572
|
+
}),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
return { kind: "sync", status: 200, body: operatorSucceeded(action, result) };
|
|
576
|
+
}
|
|
577
|
+
async function runSplitAction(action, run) {
|
|
578
|
+
try {
|
|
579
|
+
const result = await run();
|
|
580
|
+
if (result && typeof result === "object" && "outcome" in result) {
|
|
581
|
+
return splitCommandDispatch(action, result);
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
kind: "sync",
|
|
585
|
+
status: 200,
|
|
586
|
+
body: operatorSucceeded(action, result),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
return invalid(action, error instanceof Error ? error.message : String(error));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function humanDecisionDispatch(action, result) {
|
|
594
|
+
if (result.outcome === "rejected") {
|
|
595
|
+
return {
|
|
596
|
+
kind: "error",
|
|
597
|
+
status: 409,
|
|
598
|
+
body: operatorFailed({
|
|
599
|
+
command: action,
|
|
600
|
+
outcome: "conflict",
|
|
601
|
+
code: "CONFLICT",
|
|
602
|
+
message: result.reason,
|
|
603
|
+
details: { projection: result.projection },
|
|
604
|
+
}),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
return { kind: "sync", status: 200, body: operatorSucceeded(action, result) };
|
|
608
|
+
}
|
|
609
|
+
async function runHumanDecisionAction(action, run) {
|
|
610
|
+
try {
|
|
611
|
+
return humanDecisionDispatch(action, await run());
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
return invalid(action, error instanceof Error ? error.message : String(error));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
459
617
|
export async function dispatchOperatorAction(ctx, req) {
|
|
460
618
|
const action = req.action?.trim();
|
|
461
619
|
if (!action) {
|
|
@@ -2078,12 +2236,35 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2078
2236
|
}
|
|
2079
2237
|
return accepted;
|
|
2080
2238
|
}
|
|
2239
|
+
case "dagPromptOverrideConfig": {
|
|
2240
|
+
// Experimental internal switch; the browser UI hides the prompt-edit
|
|
2241
|
+
// entry unless this reports enabled=true.
|
|
2242
|
+
return {
|
|
2243
|
+
kind: "sync",
|
|
2244
|
+
status: 200,
|
|
2245
|
+
body: operatorSucceeded(action, {
|
|
2246
|
+
enabled: isDagRerunPromptOverrideEnabled(),
|
|
2247
|
+
envVar: DAG_RERUN_PROMPT_OVERRIDE_ENV,
|
|
2248
|
+
}),
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2081
2251
|
case "dagRerunPlan": {
|
|
2082
2252
|
const runId = str(p.runId);
|
|
2083
2253
|
const fromNode = str(p.fromNode);
|
|
2084
2254
|
if (!runId || !fromNode) {
|
|
2085
2255
|
return invalid(action, "runId and fromNode are required");
|
|
2086
2256
|
}
|
|
2257
|
+
const parsedOverrides = parsePromptOverrideParams(p.promptOverrides);
|
|
2258
|
+
if (parsedOverrides.kind === "error") {
|
|
2259
|
+
return invalid(action, parsedOverrides.message);
|
|
2260
|
+
}
|
|
2261
|
+
if (parsedOverrides.overrides.length > 0 &&
|
|
2262
|
+
!isDagRerunPromptOverrideEnabled()) {
|
|
2263
|
+
return invalid(action, `prompt override rerun is disabled; set ${DAG_RERUN_PROMPT_OVERRIDE_ENV}=1 to enable this experimental capability`);
|
|
2264
|
+
}
|
|
2265
|
+
const overrideArgs = parsedOverrides.overrides.length > 0
|
|
2266
|
+
? await buildPromptOverrideCliArgs(ctx, parsedOverrides.overrides)
|
|
2267
|
+
: [];
|
|
2087
2268
|
const body = await runReadCli(ctx, [
|
|
2088
2269
|
"dag",
|
|
2089
2270
|
"rerun",
|
|
@@ -2092,6 +2273,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2092
2273
|
"--from-node",
|
|
2093
2274
|
fromNode,
|
|
2094
2275
|
"--plan",
|
|
2276
|
+
...overrideArgs,
|
|
2095
2277
|
"--json",
|
|
2096
2278
|
], "dag rerun plan");
|
|
2097
2279
|
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
@@ -2105,6 +2287,17 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2105
2287
|
if (!runId || !fromNode || !planHash || !reason || !clientRequestId) {
|
|
2106
2288
|
return invalid(action, "runId, fromNode, planHash, reason, and clientRequestId are required");
|
|
2107
2289
|
}
|
|
2290
|
+
const parsedOverrides = parsePromptOverrideParams(p.promptOverrides);
|
|
2291
|
+
if (parsedOverrides.kind === "error") {
|
|
2292
|
+
return invalid(action, parsedOverrides.message);
|
|
2293
|
+
}
|
|
2294
|
+
if (parsedOverrides.overrides.length > 0 &&
|
|
2295
|
+
!isDagRerunPromptOverrideEnabled()) {
|
|
2296
|
+
return invalid(action, `prompt override rerun is disabled; set ${DAG_RERUN_PROMPT_OVERRIDE_ENV}=1 to enable this experimental capability`);
|
|
2297
|
+
}
|
|
2298
|
+
const overrideArgs = parsedOverrides.overrides.length > 0
|
|
2299
|
+
? await buildPromptOverrideCliArgs(ctx, parsedOverrides.overrides)
|
|
2300
|
+
: [];
|
|
2108
2301
|
return acceptOperation(ctx, {
|
|
2109
2302
|
action,
|
|
2110
2303
|
actionParams: p,
|
|
@@ -2122,6 +2315,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2122
2315
|
clientRequestId,
|
|
2123
2316
|
"--reason",
|
|
2124
2317
|
reason,
|
|
2318
|
+
...overrideArgs,
|
|
2125
2319
|
"--json",
|
|
2126
2320
|
],
|
|
2127
2321
|
});
|
|
@@ -2606,6 +2800,75 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2606
2800
|
}),
|
|
2607
2801
|
};
|
|
2608
2802
|
}
|
|
2803
|
+
case "frontendSplitWakeup": {
|
|
2804
|
+
if (!ctx.splitOrchestrator)
|
|
2805
|
+
return splitRuntimeMissing(action);
|
|
2806
|
+
return runSplitAction(action, () => ctx.splitOrchestrator.wakeupChildren(splitCommandBase(p)));
|
|
2807
|
+
}
|
|
2808
|
+
case "frontendSplitChildCallback": {
|
|
2809
|
+
if (!ctx.splitOrchestrator)
|
|
2810
|
+
return splitRuntimeMissing(action);
|
|
2811
|
+
return runSplitAction(action, () => ctx.splitOrchestrator.childCallback({
|
|
2812
|
+
...splitCommandBase(p),
|
|
2813
|
+
observation: p.observation,
|
|
2814
|
+
}));
|
|
2815
|
+
}
|
|
2816
|
+
case "frontendSplitWakeupIntegration": {
|
|
2817
|
+
if (!ctx.splitOrchestrator)
|
|
2818
|
+
return splitRuntimeMissing(action);
|
|
2819
|
+
return runSplitAction(action, () => ctx.splitOrchestrator.wakeupIntegration(splitCommandBase(p)));
|
|
2820
|
+
}
|
|
2821
|
+
case "frontendSplitIntegrationCallback": {
|
|
2822
|
+
if (!ctx.splitOrchestrator)
|
|
2823
|
+
return splitRuntimeMissing(action);
|
|
2824
|
+
return runSplitAction(action, () => ctx.splitOrchestrator.integrationCallback({
|
|
2825
|
+
...splitCommandBase(p),
|
|
2826
|
+
completion: p.completion,
|
|
2827
|
+
}));
|
|
2828
|
+
}
|
|
2829
|
+
case "frontendSplitFinalize": {
|
|
2830
|
+
if (!ctx.splitOrchestrator)
|
|
2831
|
+
return splitRuntimeMissing(action);
|
|
2832
|
+
return runSplitAction(action, () => ctx.splitOrchestrator.finalize(splitCommandBase(p)));
|
|
2833
|
+
}
|
|
2834
|
+
case "frontendHumanDecisionMaterialize": {
|
|
2835
|
+
if (!ctx.humanDecisionAdapter)
|
|
2836
|
+
return splitRuntimeMissing(action);
|
|
2837
|
+
return runHumanDecisionAction(action, () => ctx.humanDecisionAdapter.materialize({
|
|
2838
|
+
operationId: str(p.operationId) ?? "",
|
|
2839
|
+
expectedRecordRevision: numOf(p.expectedRecordRevision),
|
|
2840
|
+
expectedProjectionDigest: str(p.expectedProjectionDigest) ?? "",
|
|
2841
|
+
envelope: p.envelope,
|
|
2842
|
+
}));
|
|
2843
|
+
}
|
|
2844
|
+
case "frontendHumanDecisionApprove": {
|
|
2845
|
+
if (!ctx.humanDecisionAdapter)
|
|
2846
|
+
return splitRuntimeMissing(action);
|
|
2847
|
+
return runHumanDecisionAction(action, () => ctx.humanDecisionAdapter.consume({
|
|
2848
|
+
operationId: str(p.operationId) ?? "",
|
|
2849
|
+
expectedRecordRevision: numOf(p.expectedRecordRevision),
|
|
2850
|
+
expectedProjectionDigest: str(p.expectedProjectionDigest) ?? "",
|
|
2851
|
+
receipt: p.receipt,
|
|
2852
|
+
currentTargetDigest: str(p.currentTargetDigest) ?? "",
|
|
2853
|
+
now: str(p.now) ?? new Date().toISOString(),
|
|
2854
|
+
}));
|
|
2855
|
+
}
|
|
2856
|
+
case "frontendSplitInitialize": {
|
|
2857
|
+
if (!ctx.humanDecisionAdapter)
|
|
2858
|
+
return splitRuntimeMissing(action);
|
|
2859
|
+
return runHumanDecisionAction(action, () => ctx.humanDecisionAdapter.initializeSplit({
|
|
2860
|
+
operationId: str(p.operationId) ?? "",
|
|
2861
|
+
expectedRecordRevision: numOf(p.expectedRecordRevision),
|
|
2862
|
+
expectedProjectionDigest: str(p.expectedProjectionDigest) ?? "",
|
|
2863
|
+
execution: p.execution,
|
|
2864
|
+
manifest: p.manifest,
|
|
2865
|
+
receipt: p.receipt,
|
|
2866
|
+
currentTargetDigest: str(p.currentTargetDigest) ?? "",
|
|
2867
|
+
now: str(p.now) ?? new Date().toISOString(),
|
|
2868
|
+
activeWriterOperationId: str(p.activeWriterOperationId) ?? null,
|
|
2869
|
+
activeProviderOperationId: str(p.activeProviderOperationId) ?? null,
|
|
2870
|
+
}));
|
|
2871
|
+
}
|
|
2609
2872
|
default: {
|
|
2610
2873
|
// M5 official-command coverage uses one generic tail for newly registered
|
|
2611
2874
|
// CLI actions. It remains inside dispatchOperatorAction (the sole dispatch
|
|
@@ -3,7 +3,7 @@ import { rewriteRecoveryOperatorError } from "./recovery-error-copy.js";
|
|
|
3
3
|
const MUTATION_GATE_COPY = [
|
|
4
4
|
{
|
|
5
5
|
pattern: /invalid (boot capability|confirmation header) token/i,
|
|
6
|
-
copy: "
|
|
6
|
+
copy: "页面凭证已失效(控制台可能已重启),请刷新页面(F5)后重试。",
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
pattern: /mutation requires boot capability cookie|mutation requires confirmation header token/i,
|
|
@@ -23,7 +23,7 @@ export function formatOperatorUserError(message) {
|
|
|
23
23
|
if (recovery)
|
|
24
24
|
return recovery;
|
|
25
25
|
if (/controller identity changed|package fingerprint changed|entry binary sha256 changed/i.test(raw)) {
|
|
26
|
-
return "控制器版本已变更(Console
|
|
26
|
+
return "控制器版本已变更(Console 启动后代码被重新编译,或提交了 package.json / bin / dist / skills 等指纹范围文件)。请重启 Console 进程使其重新加载当前构建后再重试本操作;一次会话中途不要重建控制器。";
|
|
27
27
|
}
|
|
28
28
|
if (/Pi session file missing or not openable|cannot reopen chat session .* no persisted Pi session file/i.test(raw)) {
|
|
29
29
|
return "该对话的 Pi 会话文件已丢失,无法恢复上下文。请新建对话继续(历史记录无法重开)。";
|