@tea-agent/loop-agent 0.26.5-beta.2 → 0.27.0
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 +2 -2
- package/CHANGELOG.md +29 -0
- package/README.md +13 -23
- package/dist/application/dag/args.js +7 -7
- package/dist/application/dag/generate-task-dag.js +2 -2
- package/dist/application/dag/run-dag.js +3 -4
- package/dist/application/dag/validate-dag.js +1 -1
- package/dist/application/task-lifecycle/advance.js +845 -0
- package/dist/application/task-lifecycle/gates.js +80 -0
- package/dist/application/task-lifecycle/index.js +7 -0
- package/dist/application/task-lifecycle/observe.js +395 -0
- package/dist/application/task-lifecycle/plan-transitions.js +224 -0
- package/dist/application/task-lifecycle/recommendations.js +180 -0
- package/dist/application/task-lifecycle/record.js +73 -0
- package/dist/application/task-lifecycle/types.js +1 -0
- package/dist/cli/command-definitions.js +14 -111
- package/dist/cli/help.js +6 -7
- package/dist/cli/program.js +26 -90
- package/dist/cli/update/policy.js +0 -1
- package/dist/commands/dag-final-verification.js +1 -1
- package/dist/commands/dag-init-hybrid.js +1 -1
- package/dist/commands/delegate.js +2 -2
- package/dist/commands/init.js +21 -19
- package/dist/commands/run-dag-progress.js +1 -1
- package/dist/commands/status.js +18 -17
- package/dist/commands/study-init.js +2 -2
- package/dist/commands/task-advance.js +335 -0
- package/dist/commands/task-contract.js +3 -5
- package/dist/commands/task-source-prepare.js +9 -4
- package/dist/commands/task-status.js +133 -0
- package/dist/governance/manifest-types.js +2 -2
- package/dist/shared/operator/capabilities.js +1358 -243
- package/dist/task/contract/adopt.js +1 -1
- package/dist/task/contract/apply.js +1 -1
- package/dist/task/contract/import-revision.js +1 -1
- package/dist/task/contract/recover.js +2 -2
- package/dist/task/read-model.js +18 -23
- package/dist/task/runtime.js +1 -1
- package/dist/task/source-prepare/completeness.js +1 -1
- package/dist/task/source-prepare/parse-intent.js +6 -1
- package/dist/task/source-prepare/prepare.js +23 -20
- package/dist/worker/console/operator-actions.js +288 -95
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/static/assets/{index-CSRIhuzh.js → index-CNO7n6qB.js} +1 -1
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/materialize/harness-task-materializer.js +6 -5
- package/dist/worker/run-task/run-task.js +204 -112
- package/dist/worker/runner/run-ready.js +1 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +6 -72
- package/dist/workflows/dag/frontend-prewrite-gate.js +4 -17
- package/dist/workflows/dag/init-hybrid.js +11 -66
- package/docs/templates/evaluation/agents-map-slim-v1.md +1 -1
- package/docs/templates/evaluation/agents-map-verbose-v0.md +3 -3
- package/docs/templates/harness.schema.json +2 -2
- package/docs/templates/init-managed-agents.md +13 -16
- package/docs/templates/production-readiness-checklist.md +3 -3
- package/harness.json +4 -4
- package/package.json +1 -1
- package/scripts/kb-bootstrap-init-skeleton.sh +2 -1
- package/scripts/kb-graph-incremental-prepare.mjs +2 -2
- package/skills/loop-agent/SKILL.md +18 -13
- package/skills/loop-agent/references/README.md +1 -1
- package/skills/loop-agent/references/command-reference.md +55 -84
- package/skills/loop-agent/references/harness-policy.md +19 -23
- package/skills/loop-agent/references/hybrid-dag.md +31 -33
- package/skills/loop-agent/references/long-running-loop.md +2 -2
- package/skills/loop-agent/references/one-shot-runs.md +4 -5
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/post-implementation-and-patterns.md +4 -4
- package/skills/loop-agent/references/source-and-plan-practice.md +45 -51
- package/skills/loop-agent/references/task-workflow.md +14 -15
|
@@ -2,6 +2,7 @@ import { access, readFile } from "node:fs/promises";
|
|
|
2
2
|
import { buildOperatorCapabilitiesDocument } from "../../shared/operator/capabilities.js";
|
|
3
3
|
import { buildOperatorResult, operatorFailed, operatorSucceeded, } from "../../shared/operator/envelope.js";
|
|
4
4
|
import { getTaskPaths } from "../../task/runtime.js";
|
|
5
|
+
import { diffTaskContract, validateDraftForTask, } from "../../task/contract/index.js";
|
|
5
6
|
import { stageUtf8Text } from "./app-data.js";
|
|
6
7
|
import { ALL_CONFIRMATION_CHALLENGES, hashDagBytes, } from "./dag-confirmation.js";
|
|
7
8
|
import { capabilityForObserveTarget, DEFAULT_OBSERVE_BASE_URL, OBSERVE_START_COMMAND, probeAndClassifyObserve, } from "./observe-health-match.js";
|
|
@@ -41,6 +42,31 @@ function paramsOf(req) {
|
|
|
41
42
|
function str(v) {
|
|
42
43
|
return typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
43
44
|
}
|
|
45
|
+
/** Extract write-set gate token from task advance operator JSON. */
|
|
46
|
+
function extractWriteSetGateTokenFromCliJson(json, taskId) {
|
|
47
|
+
if (!json || typeof json !== "object")
|
|
48
|
+
return undefined;
|
|
49
|
+
const envelope = json;
|
|
50
|
+
const payload = envelope.result && typeof envelope.result === "object"
|
|
51
|
+
? envelope.result
|
|
52
|
+
: envelope;
|
|
53
|
+
const gate = payload.gate && typeof payload.gate === "object"
|
|
54
|
+
? payload.gate
|
|
55
|
+
: undefined;
|
|
56
|
+
const id = typeof gate?.id === "string" ? gate.id : undefined;
|
|
57
|
+
const digest = typeof gate?.digest === "string" ? gate.digest : undefined;
|
|
58
|
+
if (!id || !digest)
|
|
59
|
+
return undefined;
|
|
60
|
+
// taskId kept for future binding checks / logging symmetry with worker.
|
|
61
|
+
void taskId;
|
|
62
|
+
return `${id}:${digest}`;
|
|
63
|
+
}
|
|
64
|
+
function readWriteSetGateToken(reviewPacket) {
|
|
65
|
+
if (!reviewPacket)
|
|
66
|
+
return undefined;
|
|
67
|
+
const token = reviewPacket.writeSetGateToken;
|
|
68
|
+
return typeof token === "string" && token.includes(":") ? token : undefined;
|
|
69
|
+
}
|
|
44
70
|
async function runReadCli(ctx, args, command) {
|
|
45
71
|
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
46
72
|
try {
|
|
@@ -55,7 +81,9 @@ async function runReadCli(ctx, args, command) {
|
|
|
55
81
|
if (env.schemaVersion === 1 && typeof env.ok === "boolean") {
|
|
56
82
|
return env;
|
|
57
83
|
}
|
|
58
|
-
if (command === "dag rerun plan" &&
|
|
84
|
+
if (command === "dag rerun plan" &&
|
|
85
|
+
raw.plan &&
|
|
86
|
+
typeof raw.plan === "object") {
|
|
59
87
|
const plan = raw.plan;
|
|
60
88
|
if (raw.ok === true) {
|
|
61
89
|
return operatorSucceeded(command, plan);
|
|
@@ -114,7 +142,9 @@ async function acceptOperation(ctx, input) {
|
|
|
114
142
|
actionParams: input.actionParams,
|
|
115
143
|
taskId: input.taskId,
|
|
116
144
|
cliArgs: input.cliArgs,
|
|
117
|
-
...(input.externalCommand
|
|
145
|
+
...(input.externalCommand
|
|
146
|
+
? { externalCommand: input.externalCommand }
|
|
147
|
+
: {}),
|
|
118
148
|
});
|
|
119
149
|
if (created.kind === "conflict") {
|
|
120
150
|
return {
|
|
@@ -297,7 +327,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
297
327
|
if (!taskId) {
|
|
298
328
|
return invalid(action, "taskId is required");
|
|
299
329
|
}
|
|
300
|
-
const body = await runReadCli(ctx, ["status", taskId, "--json"], "status");
|
|
330
|
+
const body = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
301
331
|
return {
|
|
302
332
|
kind: "sync",
|
|
303
333
|
status: body.ok ? 200 : body.outcome === "not-found" ? 404 : 400,
|
|
@@ -322,11 +352,38 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
322
352
|
const taskId = str(p.taskId);
|
|
323
353
|
if (!taskId)
|
|
324
354
|
return invalid(action, "taskId is required");
|
|
325
|
-
const body = await runReadCli(ctx, ["task", "
|
|
355
|
+
const body = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
356
|
+
const snap = (body.result ?? {});
|
|
357
|
+
const c = snap.contract;
|
|
358
|
+
const reshaped = {
|
|
359
|
+
...body,
|
|
360
|
+
command: "contractShow",
|
|
361
|
+
result: {
|
|
362
|
+
taskId,
|
|
363
|
+
state: {
|
|
364
|
+
effectiveStatus: c?.effectiveStatus ?? "legacy-unversioned",
|
|
365
|
+
observedCanonicalHash: c?.observedCanonicalHash ?? undefined,
|
|
366
|
+
ref: c?.canonicalHash && typeof c.revision === "number"
|
|
367
|
+
? {
|
|
368
|
+
schemaVersion: 1,
|
|
369
|
+
taskId,
|
|
370
|
+
revision: c.revision,
|
|
371
|
+
canonicalHash: c.canonicalHash,
|
|
372
|
+
taskConfigSha256: c.taskConfigSha256 ?? undefined,
|
|
373
|
+
projectionVersion: c.projectionVersion ?? undefined,
|
|
374
|
+
canonicalizerVersion: c.canonicalizerVersion ?? undefined,
|
|
375
|
+
taskConfigSchemaVersion: c.taskConfigSchemaVersion ?? undefined,
|
|
376
|
+
}
|
|
377
|
+
: undefined,
|
|
378
|
+
},
|
|
379
|
+
snapshot: snap,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
const notFound = body.ok === true && snap.exists === false;
|
|
326
383
|
return {
|
|
327
384
|
kind: "sync",
|
|
328
|
-
status: body.ok ? 200 : body.outcome === "not-found" ? 404 : 400,
|
|
329
|
-
body,
|
|
385
|
+
status: notFound ? 404 : body.ok ? 200 : body.outcome === "not-found" ? 404 : 400,
|
|
386
|
+
body: reshaped,
|
|
330
387
|
};
|
|
331
388
|
}
|
|
332
389
|
case "contractValidate":
|
|
@@ -565,11 +622,15 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
565
622
|
const taskId = str(p.taskId);
|
|
566
623
|
const operatorAction = str(p.operatorAction);
|
|
567
624
|
const operatorActionArgs = p.operatorActionArgs;
|
|
568
|
-
if (operatorActionArgs !== undefined &&
|
|
625
|
+
if (operatorActionArgs !== undefined &&
|
|
626
|
+
(!Array.isArray(operatorActionArgs) ||
|
|
627
|
+
operatorActionArgs.some((value) => typeof value !== "string"))) {
|
|
569
628
|
return invalid(action, "operatorActionArgs must be an array of strings");
|
|
570
629
|
}
|
|
571
630
|
if (operatorAction) {
|
|
572
|
-
const target = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === operatorAction &&
|
|
631
|
+
const target = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === operatorAction &&
|
|
632
|
+
entry.description.startsWith("Official CLI action coverage") &&
|
|
633
|
+
entry.humanConfirmation === "required");
|
|
573
634
|
if (!target)
|
|
574
635
|
return invalid(action, "operatorAction must name a required official action");
|
|
575
636
|
}
|
|
@@ -577,22 +638,25 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
577
638
|
return invalid(action, "taskId is required");
|
|
578
639
|
let dagText = str(p.dagText) ?? str(p.dagJson);
|
|
579
640
|
let staged;
|
|
641
|
+
// When generated via lifecycle, store writeSet gate token for runDag approve path.
|
|
642
|
+
let writeSetGateToken;
|
|
580
643
|
if (!dagText) {
|
|
581
|
-
// Happy Path: generate
|
|
644
|
+
// Happy Path: generate + strict-validate via task advance; never confirm empty stub.
|
|
582
645
|
staged = await stageUtf8Text(ctx.appData, "", { extension: ".json" });
|
|
583
646
|
const profile = str(p.profile) ?? "auto";
|
|
584
647
|
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
585
648
|
const gen = await run([
|
|
586
|
-
"
|
|
587
|
-
"
|
|
649
|
+
"task",
|
|
650
|
+
"advance",
|
|
588
651
|
taskId,
|
|
589
652
|
"--profile",
|
|
590
653
|
profile,
|
|
591
|
-
"-
|
|
654
|
+
"--dag-output",
|
|
592
655
|
staged.path,
|
|
656
|
+
"--json",
|
|
593
657
|
], {
|
|
594
658
|
cwd: ctx.repoRoot,
|
|
595
|
-
artifactName: "prepare-dag-
|
|
659
|
+
artifactName: "prepare-dag-task-advance",
|
|
596
660
|
expectJson: true,
|
|
597
661
|
timeoutMs: 180_000,
|
|
598
662
|
});
|
|
@@ -605,7 +669,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
605
669
|
outcome: "invalid",
|
|
606
670
|
code: "INVALID_INPUT",
|
|
607
671
|
message: gen.stderr?.trim() ||
|
|
608
|
-
`
|
|
672
|
+
`task advance (prepare DAG) failed with exit ${gen.exitCode}`,
|
|
609
673
|
details: {
|
|
610
674
|
exitCode: gen.exitCode,
|
|
611
675
|
stdoutPreview: (gen.stdout ?? "").slice(0, 1200),
|
|
@@ -622,10 +686,11 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
622
686
|
command: action,
|
|
623
687
|
outcome: "invalid",
|
|
624
688
|
code: "INVALID_INPUT",
|
|
625
|
-
message: "
|
|
689
|
+
message: "task advance produced empty DAG output",
|
|
626
690
|
}),
|
|
627
691
|
};
|
|
628
692
|
}
|
|
693
|
+
writeSetGateToken = extractWriteSetGateTokenFromCliJson(gen.json, taskId);
|
|
629
694
|
staged = await stageUtf8Text(ctx.appData, dagText, {
|
|
630
695
|
extension: ".json",
|
|
631
696
|
});
|
|
@@ -667,9 +732,25 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
667
732
|
result: validateBody.result ?? null,
|
|
668
733
|
}));
|
|
669
734
|
// Prefer live managed contract observation over client-supplied binding fields.
|
|
670
|
-
const showBody = await runReadCli(ctx, ["task", "
|
|
671
|
-
const
|
|
672
|
-
const
|
|
735
|
+
const showBody = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
736
|
+
const statusResult = (showBody.result ?? {});
|
|
737
|
+
const c = showBody.ok ? statusResult.contract : undefined;
|
|
738
|
+
const ref = c?.canonicalHash && typeof c.revision === "number"
|
|
739
|
+
? {
|
|
740
|
+
revision: c.revision,
|
|
741
|
+
canonicalHash: c.canonicalHash,
|
|
742
|
+
taskConfigSha256: c.taskConfigSha256 ?? undefined,
|
|
743
|
+
projectionVersion: c.projectionVersion ?? undefined,
|
|
744
|
+
canonicalizerVersion: c.canonicalizerVersion ?? undefined,
|
|
745
|
+
taskConfigSchemaVersion: c.taskConfigSchemaVersion ?? undefined,
|
|
746
|
+
}
|
|
747
|
+
: undefined;
|
|
748
|
+
const showResult = {
|
|
749
|
+
state: {
|
|
750
|
+
effectiveStatus: c?.effectiveStatus,
|
|
751
|
+
ref,
|
|
752
|
+
},
|
|
753
|
+
};
|
|
673
754
|
if (!ref?.canonicalHash || typeof ref.revision !== "number") {
|
|
674
755
|
return {
|
|
675
756
|
kind: "error",
|
|
@@ -710,7 +791,13 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
710
791
|
taskId,
|
|
711
792
|
dagHandle,
|
|
712
793
|
validationOk: true,
|
|
713
|
-
...(
|
|
794
|
+
...(writeSetGateToken ? { writeSetGateToken } : {}),
|
|
795
|
+
...(operatorAction
|
|
796
|
+
? {
|
|
797
|
+
operatorAction,
|
|
798
|
+
operatorActionArgs: operatorActionArgs ?? [],
|
|
799
|
+
}
|
|
800
|
+
: {}),
|
|
714
801
|
},
|
|
715
802
|
});
|
|
716
803
|
return {
|
|
@@ -803,7 +890,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
803
890
|
actionParams: p,
|
|
804
891
|
clientRequestId,
|
|
805
892
|
taskId,
|
|
806
|
-
cliArgs: ["
|
|
893
|
+
cliArgs: ["task", "advance", taskId, title, "--json"],
|
|
807
894
|
});
|
|
808
895
|
}
|
|
809
896
|
case "bootstrapFromPrd": {
|
|
@@ -824,7 +911,8 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
824
911
|
actionParams: { taskId, stagedSha256: staged.sha256 },
|
|
825
912
|
clientRequestId,
|
|
826
913
|
taskId,
|
|
827
|
-
|
|
914
|
+
// PRD archive via task advance; create if missing.
|
|
915
|
+
cliArgs: ["task", "advance", taskId, "--prd", staged.path, "--json"],
|
|
828
916
|
});
|
|
829
917
|
}
|
|
830
918
|
case "contractApply": {
|
|
@@ -888,15 +976,10 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
888
976
|
};
|
|
889
977
|
}
|
|
890
978
|
const staged = await stageUtf8Text(ctx.appData, JSON.stringify(toCanonicalDraftForCli(draftRec.draft), null, 2), { extension: ".json" });
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
const
|
|
894
|
-
const
|
|
895
|
-
const expectedRevision = String(p.expectedRevision ?? showState?.ref?.revision ?? 0);
|
|
896
|
-
const expectedHash = str(p.expectedObservedHash) ??
|
|
897
|
-
showState?.observedCanonicalHash ??
|
|
898
|
-
emptyObservedHash;
|
|
899
|
-
// CLI validates --request-payload-sha256 against input file bytes, not draft projection hash.
|
|
979
|
+
// Lifecycle apply via task advance --from-draft (managed contract projection).
|
|
980
|
+
// Optimistic concurrency remains inside applyTaskContract via prepareTaskSource.
|
|
981
|
+
const statusBody = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
982
|
+
const statusContract = (statusBody.result ?? {}).contract;
|
|
900
983
|
const payloadSha = staged.sha256;
|
|
901
984
|
return acceptOperation(ctx, {
|
|
902
985
|
action,
|
|
@@ -905,28 +988,20 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
905
988
|
assessmentId,
|
|
906
989
|
draftSha256: draftRec.draftSha256,
|
|
907
990
|
requestPayloadSha256: payloadSha,
|
|
908
|
-
expectedRevision: Number(expectedRevision),
|
|
909
|
-
expectedObservedHash:
|
|
910
|
-
|
|
991
|
+
expectedRevision: Number(p.expectedRevision ?? statusContract?.revision ?? 0),
|
|
992
|
+
expectedObservedHash: str(p.expectedObservedHash) ??
|
|
993
|
+
statusContract?.observedCanonicalHash ??
|
|
994
|
+
null,
|
|
995
|
+
effectiveStatus: statusContract?.effectiveStatus,
|
|
911
996
|
},
|
|
912
997
|
clientRequestId,
|
|
913
998
|
taskId,
|
|
914
999
|
cliArgs: [
|
|
915
1000
|
"task",
|
|
916
|
-
"
|
|
917
|
-
"apply",
|
|
918
|
-
"--task",
|
|
1001
|
+
"advance",
|
|
919
1002
|
taskId,
|
|
920
|
-
"--
|
|
1003
|
+
"--from-draft",
|
|
921
1004
|
staged.path,
|
|
922
|
-
"--expected-revision",
|
|
923
|
-
expectedRevision,
|
|
924
|
-
"--expected-observed-hash",
|
|
925
|
-
expectedHash,
|
|
926
|
-
"--request-id",
|
|
927
|
-
clientRequestId,
|
|
928
|
-
"--request-payload-sha256",
|
|
929
|
-
payloadSha,
|
|
930
1005
|
"--json",
|
|
931
1006
|
],
|
|
932
1007
|
});
|
|
@@ -943,14 +1018,16 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
943
1018
|
actionParams: p,
|
|
944
1019
|
clientRequestId,
|
|
945
1020
|
taskId,
|
|
1021
|
+
// Lifecycle generate + strict validate; stops at writeSet gate.
|
|
946
1022
|
cliArgs: [
|
|
947
|
-
"
|
|
948
|
-
"
|
|
1023
|
+
"task",
|
|
1024
|
+
"advance",
|
|
949
1025
|
taskId,
|
|
950
1026
|
"--profile",
|
|
951
1027
|
str(p.profile) ?? "auto",
|
|
952
|
-
"-
|
|
1028
|
+
"--dag-output",
|
|
953
1029
|
out.path,
|
|
1030
|
+
"--json",
|
|
954
1031
|
],
|
|
955
1032
|
});
|
|
956
1033
|
}
|
|
@@ -1017,13 +1094,34 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1017
1094
|
}),
|
|
1018
1095
|
};
|
|
1019
1096
|
}
|
|
1097
|
+
const taskId = conf.taskContractBinding.taskId;
|
|
1098
|
+
const gateToken = readWriteSetGateToken(conf.reviewPacket);
|
|
1099
|
+
if (gateToken) {
|
|
1100
|
+
// Standard lifecycle path: human confirmation maps to writeSet gate approve.
|
|
1101
|
+
return acceptOperation(ctx, {
|
|
1102
|
+
action,
|
|
1103
|
+
actionParams: { confirmationId },
|
|
1104
|
+
clientRequestId,
|
|
1105
|
+
taskId,
|
|
1106
|
+
cliArgs: [
|
|
1107
|
+
"task",
|
|
1108
|
+
"advance",
|
|
1109
|
+
taskId,
|
|
1110
|
+
"--approve-gate",
|
|
1111
|
+
gateToken,
|
|
1112
|
+
"--json",
|
|
1113
|
+
],
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
// Advanced: arbitrary staged DagSpec (client-supplied dagText) until dag execute rename.
|
|
1020
1117
|
return acceptOperation(ctx, {
|
|
1021
1118
|
action,
|
|
1022
1119
|
actionParams: { confirmationId },
|
|
1023
1120
|
clientRequestId,
|
|
1024
|
-
taskId
|
|
1121
|
+
taskId,
|
|
1025
1122
|
cliArgs: [
|
|
1026
|
-
"
|
|
1123
|
+
"dag",
|
|
1124
|
+
"execute",
|
|
1027
1125
|
"--dag",
|
|
1028
1126
|
conf.dagBytesPath,
|
|
1029
1127
|
"--cwd",
|
|
@@ -1156,7 +1254,9 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1156
1254
|
}
|
|
1157
1255
|
case "prepareMutationGate": {
|
|
1158
1256
|
const targetAction = str(p.action);
|
|
1159
|
-
const actionParams = p.actionParams &&
|
|
1257
|
+
const actionParams = p.actionParams &&
|
|
1258
|
+
typeof p.actionParams === "object" &&
|
|
1259
|
+
!Array.isArray(p.actionParams)
|
|
1160
1260
|
? p.actionParams
|
|
1161
1261
|
: undefined;
|
|
1162
1262
|
if (!targetAction || !actionParams) {
|
|
@@ -1213,10 +1313,13 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1213
1313
|
// M5 official-command coverage uses one generic tail for newly registered
|
|
1214
1314
|
// CLI actions. It remains inside dispatchOperatorAction (the sole dispatch
|
|
1215
1315
|
// surface) and only accepts argv arrays declared by the capability schema.
|
|
1216
|
-
const capability = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === action &&
|
|
1316
|
+
const capability = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === action &&
|
|
1317
|
+
entry.description.startsWith("Official CLI action coverage"));
|
|
1217
1318
|
if (capability) {
|
|
1218
1319
|
const rawArgs = p.args;
|
|
1219
|
-
if (rawArgs !== undefined &&
|
|
1320
|
+
if (rawArgs !== undefined &&
|
|
1321
|
+
(!Array.isArray(rawArgs) ||
|
|
1322
|
+
rawArgs.some((value) => typeof value !== "string"))) {
|
|
1220
1323
|
return invalid(action, "args must be an array of strings");
|
|
1221
1324
|
}
|
|
1222
1325
|
if (capability.humanConfirmation === "required") {
|
|
@@ -1228,7 +1331,12 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1228
1331
|
return {
|
|
1229
1332
|
kind: "error",
|
|
1230
1333
|
status: 404,
|
|
1231
|
-
body: operatorFailed({
|
|
1334
|
+
body: operatorFailed({
|
|
1335
|
+
command: action,
|
|
1336
|
+
outcome: "not-found",
|
|
1337
|
+
code: "NOT_FOUND",
|
|
1338
|
+
message: `confirmation not found: ${confirmationId}`,
|
|
1339
|
+
}),
|
|
1232
1340
|
};
|
|
1233
1341
|
}
|
|
1234
1342
|
const confirmedArgs = conf.reviewPacket?.operatorActionArgs;
|
|
@@ -1240,7 +1348,12 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1240
1348
|
return {
|
|
1241
1349
|
kind: "error",
|
|
1242
1350
|
status: 403,
|
|
1243
|
-
body: operatorFailed({
|
|
1351
|
+
body: operatorFailed({
|
|
1352
|
+
command: action,
|
|
1353
|
+
outcome: "blocked",
|
|
1354
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
1355
|
+
message: "confirmation receipt is not bound to this action, arguments, and operator session",
|
|
1356
|
+
}),
|
|
1244
1357
|
};
|
|
1245
1358
|
}
|
|
1246
1359
|
let receiptBytes;
|
|
@@ -1251,7 +1364,12 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1251
1364
|
return {
|
|
1252
1365
|
kind: "error",
|
|
1253
1366
|
status: 409,
|
|
1254
|
-
body: operatorFailed({
|
|
1367
|
+
body: operatorFailed({
|
|
1368
|
+
command: action,
|
|
1369
|
+
outcome: "conflict",
|
|
1370
|
+
code: "CONFIRMATION_STALE",
|
|
1371
|
+
message: `cannot re-read confirmation receipt bytes: ${error instanceof Error ? error.message : String(error)}`,
|
|
1372
|
+
}),
|
|
1255
1373
|
};
|
|
1256
1374
|
}
|
|
1257
1375
|
const consumed = await ctx.confirmations.consume({
|
|
@@ -1261,7 +1379,8 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1261
1379
|
dagSha256: hashDagBytes(receiptBytes),
|
|
1262
1380
|
sourceBindingSha256: conf.sourceBindingSha256,
|
|
1263
1381
|
taskContractBinding: conf.taskContractBinding,
|
|
1264
|
-
controllerFingerprint: ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
1382
|
+
controllerFingerprint: ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
1383
|
+
conf.controllerFingerprint,
|
|
1265
1384
|
validationResultSha256: conf.validationResultSha256,
|
|
1266
1385
|
},
|
|
1267
1386
|
});
|
|
@@ -1269,11 +1388,18 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1269
1388
|
return {
|
|
1270
1389
|
kind: "error",
|
|
1271
1390
|
status: confirmationErrorStatus(consumed.code),
|
|
1272
|
-
body: operatorFailed({
|
|
1391
|
+
body: operatorFailed({
|
|
1392
|
+
command: action,
|
|
1393
|
+
outcome: "rejected",
|
|
1394
|
+
code: consumed.code,
|
|
1395
|
+
message: consumed.message,
|
|
1396
|
+
}),
|
|
1273
1397
|
};
|
|
1274
1398
|
}
|
|
1275
1399
|
}
|
|
1276
|
-
const prefix = capability.cli
|
|
1400
|
+
const prefix = capability.cli
|
|
1401
|
+
.replace(/^(loop-agent|agent-worker)\s+/, "")
|
|
1402
|
+
.split(/\s+/);
|
|
1277
1403
|
const args = [...prefix, ...(rawArgs ?? [])];
|
|
1278
1404
|
if (capability.kind === "read") {
|
|
1279
1405
|
if (!args.includes("--json"))
|
|
@@ -1418,7 +1544,11 @@ async function consumeMutationGateReceipt(ctx, action, p) {
|
|
|
1418
1544
|
if (!reserved.ok) {
|
|
1419
1545
|
return {
|
|
1420
1546
|
kind: "error",
|
|
1421
|
-
status: reserved.code === "NOT_FOUND"
|
|
1547
|
+
status: reserved.code === "NOT_FOUND"
|
|
1548
|
+
? 404
|
|
1549
|
+
: reserved.code === "EXPIRED"
|
|
1550
|
+
? 410
|
|
1551
|
+
: 409,
|
|
1422
1552
|
body: operatorFailed({
|
|
1423
1553
|
command: action,
|
|
1424
1554
|
outcome: "rejected",
|
|
@@ -1518,13 +1648,16 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
1518
1648
|
}
|
|
1519
1649
|
const title = identity.title;
|
|
1520
1650
|
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
1521
|
-
//
|
|
1522
|
-
const
|
|
1651
|
+
// Standard lifecycle entry: task advance create + PRD import(s).
|
|
1652
|
+
const advanceCreate = await run(["task", "advance", taskId, title, "--json"], {
|
|
1523
1653
|
cwd: ctx.repoRoot,
|
|
1524
|
-
artifactName: "bootstrap-
|
|
1654
|
+
artifactName: "bootstrap-task-advance-create",
|
|
1655
|
+
expectJson: true,
|
|
1525
1656
|
timeoutMs: 60_000,
|
|
1526
1657
|
});
|
|
1527
|
-
if (
|
|
1658
|
+
if (!advanceCreate.ok ||
|
|
1659
|
+
advanceCreate.exitCode !== 0 ||
|
|
1660
|
+
advanceCreate.timedOut) {
|
|
1528
1661
|
return {
|
|
1529
1662
|
kind: "error",
|
|
1530
1663
|
status: 400,
|
|
@@ -1532,37 +1665,36 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
1532
1665
|
command: "bootstrapFromPrd",
|
|
1533
1666
|
outcome: "rejected",
|
|
1534
1667
|
code: "INTERNAL_ERROR",
|
|
1535
|
-
message:
|
|
1536
|
-
`
|
|
1668
|
+
message: advanceCreate.stderr?.trim() ||
|
|
1669
|
+
`task advance (create) failed with exit ${advanceCreate.exitCode}`,
|
|
1537
1670
|
details: {
|
|
1538
|
-
exitCode:
|
|
1539
|
-
timedOut:
|
|
1671
|
+
exitCode: advanceCreate.exitCode,
|
|
1672
|
+
timedOut: advanceCreate.timedOut,
|
|
1540
1673
|
taskId,
|
|
1541
1674
|
title,
|
|
1542
|
-
stdoutPreview: (
|
|
1675
|
+
stdoutPreview: (advanceCreate.stdout ?? "").slice(0, 400),
|
|
1543
1676
|
},
|
|
1544
1677
|
}),
|
|
1545
1678
|
};
|
|
1546
1679
|
}
|
|
1547
1680
|
const succeededImports = [];
|
|
1548
1681
|
const importResults = [];
|
|
1549
|
-
const
|
|
1682
|
+
const runImportViaAdvance = async (label, text, name) => {
|
|
1550
1683
|
const staged = await stageUtf8Text(ctx.appData, text, {
|
|
1551
1684
|
extension: ".md",
|
|
1552
1685
|
});
|
|
1553
1686
|
const imported = await run([
|
|
1554
|
-
"
|
|
1687
|
+
"task",
|
|
1688
|
+
"advance",
|
|
1555
1689
|
taskId,
|
|
1556
|
-
"--
|
|
1690
|
+
"--prd",
|
|
1557
1691
|
staged.path,
|
|
1558
|
-
"--name",
|
|
1559
|
-
name,
|
|
1560
1692
|
"--role",
|
|
1561
1693
|
"requirement",
|
|
1562
1694
|
"--json",
|
|
1563
1695
|
], {
|
|
1564
1696
|
cwd: ctx.repoRoot,
|
|
1565
|
-
artifactName: `bootstrap-
|
|
1697
|
+
artifactName: `bootstrap-task-advance-prd-${label}`,
|
|
1566
1698
|
expectJson: true,
|
|
1567
1699
|
timeoutMs: 60_000,
|
|
1568
1700
|
});
|
|
@@ -1575,7 +1707,7 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
1575
1707
|
outcome: "rejected",
|
|
1576
1708
|
code: "INTERNAL_ERROR",
|
|
1577
1709
|
message: imported.stderr?.trim() ||
|
|
1578
|
-
`
|
|
1710
|
+
`task advance --prd failed for "${name}" with exit ${imported.exitCode}. 任务已创建但不完整,请用 CLI 补 PRD 或换 taskId 重试。`,
|
|
1579
1711
|
details: {
|
|
1580
1712
|
exitCode: imported.exitCode,
|
|
1581
1713
|
taskId,
|
|
@@ -1591,11 +1723,11 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
1591
1723
|
};
|
|
1592
1724
|
if (documents && documents.length > 0) {
|
|
1593
1725
|
for (const doc of documents) {
|
|
1594
|
-
const failure = await
|
|
1726
|
+
const failure = await runImportViaAdvance(doc.name, doc.content, doc.name);
|
|
1595
1727
|
if (failure)
|
|
1596
1728
|
return failure;
|
|
1597
1729
|
}
|
|
1598
|
-
const composedFailure = await
|
|
1730
|
+
const composedFailure = await runImportViaAdvance("composed", content, "composed");
|
|
1599
1731
|
if (composedFailure)
|
|
1600
1732
|
return composedFailure;
|
|
1601
1733
|
}
|
|
@@ -1603,9 +1735,9 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
1603
1735
|
const staged = await stageUtf8Text(ctx.appData, content, {
|
|
1604
1736
|
extension: ".md",
|
|
1605
1737
|
});
|
|
1606
|
-
const imported = await run(["
|
|
1738
|
+
const imported = await run(["task", "advance", taskId, "--prd", staged.path, "--json"], {
|
|
1607
1739
|
cwd: ctx.repoRoot,
|
|
1608
|
-
artifactName: "bootstrap-
|
|
1740
|
+
artifactName: "bootstrap-task-advance-prd",
|
|
1609
1741
|
expectJson: true,
|
|
1610
1742
|
timeoutMs: 60_000,
|
|
1611
1743
|
});
|
|
@@ -1618,7 +1750,7 @@ async function dispatchBootstrapFromPrd(ctx, p) {
|
|
|
1618
1750
|
outcome: "rejected",
|
|
1619
1751
|
code: "INTERNAL_ERROR",
|
|
1620
1752
|
message: imported.stderr?.trim() ||
|
|
1621
|
-
`
|
|
1753
|
+
`task advance --prd failed with exit ${imported.exitCode}`,
|
|
1622
1754
|
details: { exitCode: imported.exitCode, taskId },
|
|
1623
1755
|
}),
|
|
1624
1756
|
};
|
|
@@ -2037,24 +2169,85 @@ async function dispatchContractValidateOrDiff(ctx, action, p) {
|
|
|
2037
2169
|
const draftText = str(p.draftJson) ?? str(p.inputText);
|
|
2038
2170
|
if (!taskId)
|
|
2039
2171
|
return invalid(action, "taskId is required");
|
|
2040
|
-
let
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2172
|
+
let draft;
|
|
2173
|
+
try {
|
|
2174
|
+
if (!draftText) {
|
|
2175
|
+
const stored = await ctx.drafts.get(taskId);
|
|
2176
|
+
if (!stored) {
|
|
2177
|
+
return invalid(action, "draftJson or stored draft required");
|
|
2178
|
+
}
|
|
2179
|
+
draft = toCanonicalDraftForCli(stored.draft);
|
|
2180
|
+
}
|
|
2181
|
+
else {
|
|
2182
|
+
draft = JSON.parse(draftText);
|
|
2045
2183
|
}
|
|
2046
|
-
const staged = await stageUtf8Text(ctx.appData, JSON.stringify(toCanonicalDraftForCli(stored.draft), null, 2), { extension: ".json" });
|
|
2047
|
-
inputPath = staged.path;
|
|
2048
2184
|
}
|
|
2049
|
-
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2185
|
+
catch (error) {
|
|
2186
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2187
|
+
return {
|
|
2188
|
+
kind: "error",
|
|
2189
|
+
status: 400,
|
|
2190
|
+
body: operatorFailed({
|
|
2191
|
+
command: action,
|
|
2192
|
+
outcome: "invalid",
|
|
2193
|
+
code: "INVALID_INPUT",
|
|
2194
|
+
message: `invalid draft JSON: ${message}`,
|
|
2195
|
+
}),
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
try {
|
|
2199
|
+
if (action === "contractValidate") {
|
|
2200
|
+
const validation = await validateDraftForTask({
|
|
2201
|
+
repoRoot: ctx.repoRoot,
|
|
2202
|
+
taskId,
|
|
2203
|
+
draft: draft,
|
|
2204
|
+
});
|
|
2205
|
+
if (!validation.ok) {
|
|
2206
|
+
return {
|
|
2207
|
+
kind: "sync",
|
|
2208
|
+
status: 400,
|
|
2209
|
+
body: operatorFailed({
|
|
2210
|
+
command: action,
|
|
2211
|
+
outcome: "invalid",
|
|
2212
|
+
code: "INVALID_INPUT",
|
|
2213
|
+
message: validation.errors.join("; "),
|
|
2214
|
+
details: { errors: validation.errors },
|
|
2215
|
+
}),
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
return {
|
|
2219
|
+
kind: "sync",
|
|
2220
|
+
status: 200,
|
|
2221
|
+
body: operatorSucceeded(action, {
|
|
2222
|
+
taskId,
|
|
2223
|
+
warnings: validation.warnings,
|
|
2224
|
+
}),
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
const diff = await diffTaskContract({
|
|
2228
|
+
repoRoot: ctx.repoRoot,
|
|
2229
|
+
taskId,
|
|
2230
|
+
draft: draft,
|
|
2052
2231
|
});
|
|
2053
|
-
|
|
2232
|
+
return {
|
|
2233
|
+
kind: "sync",
|
|
2234
|
+
status: 200,
|
|
2235
|
+
body: operatorSucceeded(action, { taskId, diff }),
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
catch (error) {
|
|
2239
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2240
|
+
return {
|
|
2241
|
+
kind: "error",
|
|
2242
|
+
status: 400,
|
|
2243
|
+
body: operatorFailed({
|
|
2244
|
+
command: action,
|
|
2245
|
+
outcome: "blocked",
|
|
2246
|
+
code: "INTERNAL_ERROR",
|
|
2247
|
+
message,
|
|
2248
|
+
}),
|
|
2249
|
+
};
|
|
2054
2250
|
}
|
|
2055
|
-
const sub = action === "contractValidate" ? "validate" : "diff";
|
|
2056
|
-
const body = await runReadCli(ctx, ["task", "contract", sub, "--task", taskId, "--input", inputPath, "--json"], `task contract ${sub}`);
|
|
2057
|
-
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
2058
2251
|
}
|
|
2059
2252
|
export function isMutationAction(action) {
|
|
2060
2253
|
return MUTATION_OR_LONG.has(action);
|