@abelxiaoxing/cadence 1.2.2 → 1.2.3
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/README.md +7 -1
- package/agents/diagnosis-worker.md +6 -2
- package/config/routes.example.json +0 -1
- package/package.json +1 -1
- package/prompts/abel-design.md +15 -4
- package/skills/abel-workflow/SKILL.md +5 -3
- package/src/apply-transaction.ts +1 -5
- package/src/child-session.ts +37 -46
- package/src/contracts.ts +278 -32
- package/src/control-contracts.ts +62 -0
- package/src/delivery-compiler.ts +239 -124
- package/src/design-control.ts +72 -27
- package/src/design-journal.ts +2 -2
- package/src/file-snapshot.ts +1 -1
- package/src/index.ts +238 -49
- package/src/packet-runtime.ts +79 -21
- package/src/parent-provider.ts +63 -30
- package/src/route-policy.ts +58 -14
- package/src/run-store.ts +98 -23
- package/src/subagent-activity.ts +82 -6
- package/src/submit-tool.ts +182 -10
- package/src/task-ledger.ts +1 -2
- package/src/worker-broker.ts +72 -13
- package/src/workflow-engine.ts +219 -31
- package/src/workspace-store.ts +2 -10
package/src/design-control.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
compileImplementPlan,
|
|
16
16
|
compileReadyReceipt,
|
|
17
17
|
type DeliveryArtifactBinding,
|
|
18
|
+
DesignPlanValidationError,
|
|
18
19
|
type GateApprovalProof,
|
|
19
20
|
parseGateAReceipt,
|
|
20
21
|
parseImplementPlan,
|
|
@@ -88,6 +89,10 @@ export type DesignControlRequest =
|
|
|
88
89
|
operationId: string;
|
|
89
90
|
gate: "gate-b";
|
|
90
91
|
}
|
|
92
|
+
| {
|
|
93
|
+
operation: "validate-plan-draft";
|
|
94
|
+
runId: string;
|
|
95
|
+
}
|
|
91
96
|
| {
|
|
92
97
|
operation: "compile-plan";
|
|
93
98
|
runId: string;
|
|
@@ -198,7 +203,10 @@ export function validateDesignControlRequest(
|
|
|
198
203
|
? { ok: true, value: structuredClone(value) as DesignControlRequest }
|
|
199
204
|
: { ok: false, code: "invalid-design-control-request" };
|
|
200
205
|
}
|
|
201
|
-
if (
|
|
206
|
+
if (
|
|
207
|
+
value.operation === "status" ||
|
|
208
|
+
value.operation === "validate-plan-draft"
|
|
209
|
+
) {
|
|
202
210
|
return exactKeys(value, ["operation", "runId"]) &&
|
|
203
211
|
typeof value.runId === "string" &&
|
|
204
212
|
IDENTIFIER.test(value.runId)
|
|
@@ -508,6 +516,8 @@ export class DesignController {
|
|
|
508
516
|
return this.#journal.recordDecision(request);
|
|
509
517
|
case "approve-gate":
|
|
510
518
|
return this.#journal.approveGate(request);
|
|
519
|
+
case "validate-plan-draft":
|
|
520
|
+
return this.#validatePlanDraft(request);
|
|
511
521
|
case "compile-plan":
|
|
512
522
|
return this.#compilePlan(request);
|
|
513
523
|
case "finalize-delivery":
|
|
@@ -539,7 +549,7 @@ export class DesignController {
|
|
|
539
549
|
const canApproveGate =
|
|
540
550
|
!design.gates.gateA.current ||
|
|
541
551
|
(planReadyForGateB && !design.gates.gateB.current);
|
|
542
|
-
const
|
|
552
|
+
const legalOperations = terminal
|
|
543
553
|
? ["status"]
|
|
544
554
|
: [
|
|
545
555
|
"status",
|
|
@@ -549,7 +559,12 @@ export class DesignController {
|
|
|
549
559
|
? ["bind-change"]
|
|
550
560
|
: []),
|
|
551
561
|
...(design.gates.gateA.current && design.change
|
|
552
|
-
? [
|
|
562
|
+
? [
|
|
563
|
+
"write-artifact",
|
|
564
|
+
"delete-artifact",
|
|
565
|
+
"validate-plan-draft",
|
|
566
|
+
"compile-plan",
|
|
567
|
+
]
|
|
553
568
|
: []),
|
|
554
569
|
...(design.change &&
|
|
555
570
|
design.plan &&
|
|
@@ -557,7 +572,6 @@ export class DesignController {
|
|
|
557
572
|
design.gates.gateB.current
|
|
558
573
|
? ["finalize-delivery"]
|
|
559
574
|
: []),
|
|
560
|
-
"finish",
|
|
561
575
|
];
|
|
562
576
|
return {
|
|
563
577
|
runId: run.runId,
|
|
@@ -566,7 +580,8 @@ export class DesignController {
|
|
|
566
580
|
state: run.state,
|
|
567
581
|
completed: run.state === "completed",
|
|
568
582
|
...(run.pauseCode ? { pause: { code: run.pauseCode } } : {}),
|
|
569
|
-
|
|
583
|
+
legalOperations,
|
|
584
|
+
packetActions: terminal ? [] : ["finish"],
|
|
570
585
|
design,
|
|
571
586
|
};
|
|
572
587
|
}
|
|
@@ -578,7 +593,7 @@ export class DesignController {
|
|
|
578
593
|
...(fromRequirement
|
|
579
594
|
? {
|
|
580
595
|
provisionalKey: transientHash(
|
|
581
|
-
"abel-design-requirement
|
|
596
|
+
"abel-design-requirement",
|
|
582
597
|
request.requirement,
|
|
583
598
|
),
|
|
584
599
|
}
|
|
@@ -772,6 +787,51 @@ export class DesignController {
|
|
|
772
787
|
}
|
|
773
788
|
}
|
|
774
789
|
|
|
790
|
+
#readCompiledDraft(runId: string) {
|
|
791
|
+
const status = this.#journal.status(runId);
|
|
792
|
+
if (!status.change) throw new Error("design-change-required");
|
|
793
|
+
if (!status.gates.gateA.current) throw new Error("design-gate-a-required");
|
|
794
|
+
const draftPath = safeRelative(status.change, "plan-draft.json");
|
|
795
|
+
const draftBytes = readSafeFile(this.consumerRoot, draftPath);
|
|
796
|
+
let draft: unknown;
|
|
797
|
+
try {
|
|
798
|
+
draft = JSON.parse(draftBytes.toString("utf8"));
|
|
799
|
+
} catch {
|
|
800
|
+
throw new DesignPlanValidationError("design-plan-draft-invalid", [
|
|
801
|
+
{ code: "design-plan-draft-invalid", field: "plan-draft.json" },
|
|
802
|
+
]);
|
|
803
|
+
}
|
|
804
|
+
const compiled = compileImplementPlan(draft, {
|
|
805
|
+
consumerRoot: this.consumerRoot,
|
|
806
|
+
});
|
|
807
|
+
if (compiled.plan.changeId !== status.change) {
|
|
808
|
+
throw new DesignPlanValidationError("design-plan-change-mismatch", [
|
|
809
|
+
{ code: "design-plan-change-mismatch", field: "changeId" },
|
|
810
|
+
]);
|
|
811
|
+
}
|
|
812
|
+
return { status, compiled };
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
#validatePlanDraft(
|
|
816
|
+
request: Extract<
|
|
817
|
+
DesignControlRequest,
|
|
818
|
+
{ operation: "validate-plan-draft" }
|
|
819
|
+
>,
|
|
820
|
+
) {
|
|
821
|
+
const { compiled } = this.#readCompiledDraft(request.runId);
|
|
822
|
+
return {
|
|
823
|
+
operation: request.operation,
|
|
824
|
+
runId: request.runId,
|
|
825
|
+
valid: true,
|
|
826
|
+
plan: {
|
|
827
|
+
taskCount: compiled.plan.tasks.length,
|
|
828
|
+
outputCount: compiled.plan.outputs.length,
|
|
829
|
+
rawSha256: compiled.rawSha256,
|
|
830
|
+
canonicalHash: compiled.planHash,
|
|
831
|
+
},
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
775
835
|
#compilePlan(
|
|
776
836
|
request: Extract<DesignControlRequest, { operation: "compile-plan" }>,
|
|
777
837
|
) {
|
|
@@ -787,32 +847,17 @@ export class DesignController {
|
|
|
787
847
|
});
|
|
788
848
|
try {
|
|
789
849
|
this.#journal.assertNoPendingArtifactOperations(request.runId);
|
|
790
|
-
const status = this.#
|
|
791
|
-
|
|
792
|
-
if (!
|
|
793
|
-
|
|
794
|
-
const
|
|
795
|
-
const gateAPath = safeRelative(status.change, "gate-a.yaml");
|
|
850
|
+
const { status, compiled } = this.#readCompiledDraft(request.runId);
|
|
851
|
+
const change = status.change;
|
|
852
|
+
if (!change) throw new Error("design-change-required");
|
|
853
|
+
const readyPath = safeRelative(change, "ready.yaml");
|
|
854
|
+
const gateAPath = safeRelative(change, "gate-a.yaml");
|
|
796
855
|
removeSafeFile(this.consumerRoot, readyPath);
|
|
797
856
|
removeSafeFile(this.consumerRoot, gateAPath);
|
|
798
|
-
const draftPath = safeRelative(status.change, "plan-draft.json");
|
|
799
|
-
const draftBytes = readSafeFile(this.consumerRoot, draftPath);
|
|
800
|
-
let draft: unknown;
|
|
801
|
-
try {
|
|
802
|
-
draft = JSON.parse(draftBytes.toString("utf8"));
|
|
803
|
-
} catch {
|
|
804
|
-
throw new Error("design-plan-draft-invalid");
|
|
805
|
-
}
|
|
806
|
-
const compiled = compileImplementPlan(draft as never, {
|
|
807
|
-
consumerRoot: this.consumerRoot,
|
|
808
|
-
});
|
|
809
|
-
if (compiled.plan.changeId !== status.change) {
|
|
810
|
-
throw new Error("design-plan-change-mismatch");
|
|
811
|
-
}
|
|
812
857
|
this.#journal.assertFinalizationLease(lease);
|
|
813
858
|
atomicWrite(
|
|
814
859
|
this.consumerRoot,
|
|
815
|
-
safeRelative(
|
|
860
|
+
safeRelative(change, "implement-plan.json"),
|
|
816
861
|
compiled.bytes,
|
|
817
862
|
);
|
|
818
863
|
return this.#journal.recordCompiledPlan({
|
package/src/design-journal.ts
CHANGED
|
@@ -899,7 +899,7 @@ export class DesignJournal {
|
|
|
899
899
|
}): Record<string, unknown> {
|
|
900
900
|
requireIdentifier(input.decisionId, "design-decision-id");
|
|
901
901
|
const decisionContractHash = contractHash(
|
|
902
|
-
"abel-design-decision
|
|
902
|
+
"abel-design-decision",
|
|
903
903
|
input.contract,
|
|
904
904
|
);
|
|
905
905
|
if (input.category !== "behavior" && input.category !== "technical") {
|
|
@@ -1024,7 +1024,7 @@ export class DesignJournal {
|
|
|
1024
1024
|
}
|
|
1025
1025
|
const gateAContractHash =
|
|
1026
1026
|
input.gate === "gate-a"
|
|
1027
|
-
? contractHash("abel-design-gate-a
|
|
1027
|
+
? contractHash("abel-design-gate-a", input.contract)
|
|
1028
1028
|
: undefined;
|
|
1029
1029
|
return this.#transaction(() => {
|
|
1030
1030
|
this.#run(input.runId, true);
|
package/src/file-snapshot.ts
CHANGED
|
@@ -56,7 +56,7 @@ function updateManifest(
|
|
|
56
56
|
|
|
57
57
|
function recursiveDirectoryManifest(abs: string): string {
|
|
58
58
|
const hash = createHash("sha256");
|
|
59
|
-
updateManifest(hash, "cadence-directory-manifest
|
|
59
|
+
updateManifest(hash, "cadence-directory-manifest");
|
|
60
60
|
const visit = (directory: string, prefix: string): void => {
|
|
61
61
|
const names = readdirSync(directory).sort();
|
|
62
62
|
for (const name of names) {
|
package/src/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
verificationSteps,
|
|
39
39
|
} from "./contracts.ts";
|
|
40
40
|
import {
|
|
41
|
+
CONTROL_COMMAND_PARAMETERS,
|
|
41
42
|
canonicalizeControlCommandToolInput,
|
|
42
43
|
validateControlCommand,
|
|
43
44
|
} from "./control-contracts.ts";
|
|
@@ -45,14 +46,15 @@ import {
|
|
|
45
46
|
assessDeliveryTraceability,
|
|
46
47
|
compileImplementPlan,
|
|
47
48
|
DeliveryValidationError,
|
|
49
|
+
DesignPlanValidationError,
|
|
48
50
|
type GateApprovalProof,
|
|
49
|
-
IMPLEMENT_PLAN_SCHEMA_VERSION,
|
|
50
51
|
parseGateAReceipt,
|
|
51
52
|
parseImplementPlan,
|
|
52
53
|
parseReadyReceipt,
|
|
53
54
|
} from "./delivery-compiler.ts";
|
|
54
55
|
import {
|
|
55
56
|
DesignController,
|
|
57
|
+
DesignFinalizationError,
|
|
56
58
|
validateDesignControlRequest,
|
|
57
59
|
} from "./design-control.ts";
|
|
58
60
|
import { canonicalJson, hashCanonicalValue } from "./implement-graph.ts";
|
|
@@ -67,6 +69,7 @@ import {
|
|
|
67
69
|
unavailableRoutePolicy,
|
|
68
70
|
type WorkerRoutePolicy,
|
|
69
71
|
} from "./route-policy.ts";
|
|
72
|
+
import { RunStoreFormatError } from "./run-store.ts";
|
|
70
73
|
import { observeSafePath } from "./safe-path.ts";
|
|
71
74
|
import { resolveStateRoot } from "./state-root.ts";
|
|
72
75
|
import {
|
|
@@ -229,15 +232,15 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
|
|
|
229
232
|
required: ["operation", "operationId", field],
|
|
230
233
|
additionalProperties: false,
|
|
231
234
|
})),
|
|
232
|
-
{
|
|
233
|
-
type: "object",
|
|
235
|
+
...(["status", "validate-plan-draft"] as const).map((operation) => ({
|
|
236
|
+
type: "object" as const,
|
|
234
237
|
properties: {
|
|
235
|
-
operation: { type: "string", enum: [
|
|
236
|
-
runId: { type: "string" },
|
|
238
|
+
operation: { type: "string" as const, enum: [operation] },
|
|
239
|
+
runId: { type: "string" as const },
|
|
237
240
|
},
|
|
238
241
|
required: ["operation", "runId"],
|
|
239
242
|
additionalProperties: false,
|
|
240
|
-
},
|
|
243
|
+
})),
|
|
241
244
|
{
|
|
242
245
|
type: "object",
|
|
243
246
|
properties: {
|
|
@@ -421,6 +424,89 @@ function splitUsage(result: unknown): { payload: unknown; usage?: unknown } {
|
|
|
421
424
|
return { payload, usage };
|
|
422
425
|
}
|
|
423
426
|
|
|
427
|
+
const SAFE_DESIGN_ERROR_CODE = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
428
|
+
const MAX_DESIGN_ERROR_DIAGNOSTICS = 64;
|
|
429
|
+
|
|
430
|
+
type SafeDesignFailure = {
|
|
431
|
+
kind: "design-control-failure";
|
|
432
|
+
operation: string;
|
|
433
|
+
code: string;
|
|
434
|
+
diagnostics: ReadonlyArray<Record<string, string>>;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
function safeDesignFailure(
|
|
438
|
+
error: unknown,
|
|
439
|
+
operation: string,
|
|
440
|
+
): SafeDesignFailure {
|
|
441
|
+
const message = error instanceof Error ? error.message : "";
|
|
442
|
+
const code =
|
|
443
|
+
error instanceof DesignPlanValidationError
|
|
444
|
+
? "design-plan-validation-invalid"
|
|
445
|
+
: SAFE_DESIGN_ERROR_CODE.test(message)
|
|
446
|
+
? message
|
|
447
|
+
: "design-control-failed";
|
|
448
|
+
const rawDiagnostics: readonly unknown[] =
|
|
449
|
+
error instanceof DesignFinalizationError ||
|
|
450
|
+
error instanceof DesignPlanValidationError ||
|
|
451
|
+
error instanceof DeliveryValidationError
|
|
452
|
+
? error.diagnostics
|
|
453
|
+
: [];
|
|
454
|
+
const diagnostics = rawDiagnostics
|
|
455
|
+
.flatMap((candidate) => {
|
|
456
|
+
if (typeof candidate === "string") {
|
|
457
|
+
const diagnosticCode = candidate.split(":", 1)[0] ?? "";
|
|
458
|
+
return SAFE_DESIGN_ERROR_CODE.test(diagnosticCode)
|
|
459
|
+
? [{ code: diagnosticCode }]
|
|
460
|
+
: [];
|
|
461
|
+
}
|
|
462
|
+
if (
|
|
463
|
+
candidate === null ||
|
|
464
|
+
typeof candidate !== "object" ||
|
|
465
|
+
Array.isArray(candidate)
|
|
466
|
+
) {
|
|
467
|
+
return [];
|
|
468
|
+
}
|
|
469
|
+
const admitted = Object.fromEntries(
|
|
470
|
+
Object.entries(candidate as Record<string, unknown>)
|
|
471
|
+
.filter(
|
|
472
|
+
(entry): entry is [string, string] =>
|
|
473
|
+
[
|
|
474
|
+
"code",
|
|
475
|
+
"taskId",
|
|
476
|
+
"phase",
|
|
477
|
+
"field",
|
|
478
|
+
"category",
|
|
479
|
+
"owner",
|
|
480
|
+
"verificationId",
|
|
481
|
+
"outputId",
|
|
482
|
+
"dependencyTaskId",
|
|
483
|
+
"producerTaskId",
|
|
484
|
+
"producerPhase",
|
|
485
|
+
].includes(entry[0]) &&
|
|
486
|
+
typeof entry[1] === "string" &&
|
|
487
|
+
SAFE_DESIGN_ERROR_CODE.test(entry[1]),
|
|
488
|
+
)
|
|
489
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
490
|
+
);
|
|
491
|
+
return typeof admitted.code === "string" ? [admitted] : [];
|
|
492
|
+
})
|
|
493
|
+
.sort((left, right) =>
|
|
494
|
+
canonicalJson(left).localeCompare(canonicalJson(right)),
|
|
495
|
+
)
|
|
496
|
+
.filter(
|
|
497
|
+
(candidate, index, values) =>
|
|
498
|
+
index === 0 ||
|
|
499
|
+
canonicalJson(candidate) !== canonicalJson(values[index - 1]),
|
|
500
|
+
)
|
|
501
|
+
.slice(0, MAX_DESIGN_ERROR_DIAGNOSTICS);
|
|
502
|
+
return {
|
|
503
|
+
kind: "design-control-failure",
|
|
504
|
+
operation: SAFE_DESIGN_ERROR_CODE.test(operation) ? operation : "unknown",
|
|
505
|
+
code,
|
|
506
|
+
diagnostics,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
424
510
|
export interface WorkflowControlEngine {
|
|
425
511
|
execute(
|
|
426
512
|
command: unknown,
|
|
@@ -733,7 +819,10 @@ export function packageDeliverySource(
|
|
|
733
819
|
try {
|
|
734
820
|
receipt = parseReadyReceipt(receiptBytes);
|
|
735
821
|
} catch {
|
|
736
|
-
throw new DeliveryValidationError([
|
|
822
|
+
throw new DeliveryValidationError([
|
|
823
|
+
"delivery-receipt-invalid",
|
|
824
|
+
"delivery-recompile-required",
|
|
825
|
+
]);
|
|
737
826
|
}
|
|
738
827
|
const diagnostics = new Set<string>();
|
|
739
828
|
if (receipt.change !== input.change) {
|
|
@@ -899,7 +988,6 @@ export function packageDeliverySource(
|
|
|
899
988
|
}
|
|
900
989
|
if (
|
|
901
990
|
receipt.plan.path !== "implement-plan.json" ||
|
|
902
|
-
receipt.plan.schemaVersion !== IMPLEMENT_PLAN_SCHEMA_VERSION ||
|
|
903
991
|
receipt.plan.rawSha256 !== sha256(planBytes) ||
|
|
904
992
|
receipt.plan.canonicalHash !== hashCanonicalValue(plan)
|
|
905
993
|
) {
|
|
@@ -982,7 +1070,6 @@ export function packageDeliverySource(
|
|
|
982
1070
|
throw new DeliveryValidationError([...diagnostics]);
|
|
983
1071
|
}
|
|
984
1072
|
return {
|
|
985
|
-
version: 2,
|
|
986
1073
|
gate: "gate-b",
|
|
987
1074
|
revision: receipt.deliveryRevision,
|
|
988
1075
|
receiptHash,
|
|
@@ -1251,7 +1338,7 @@ function normalizedFailureIdentities(input: {
|
|
|
1251
1338
|
if (vitestFailures.length > 0) {
|
|
1252
1339
|
return [...new Set(vitestFailures)]
|
|
1253
1340
|
.sort()
|
|
1254
|
-
.map((failure) => sha256(`vitest-failure
|
|
1341
|
+
.map((failure) => sha256(`vitest-failure\0${failure}`));
|
|
1255
1342
|
}
|
|
1256
1343
|
const normalizedOutput = input.output
|
|
1257
1344
|
.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "gu"), "")
|
|
@@ -1261,7 +1348,6 @@ function normalizedFailureIdentities(input: {
|
|
|
1261
1348
|
return [
|
|
1262
1349
|
sha256(
|
|
1263
1350
|
canonicalJson({
|
|
1264
|
-
version: 1,
|
|
1265
1351
|
verificationId: input.step.id,
|
|
1266
1352
|
exitCode: input.exitCode,
|
|
1267
1353
|
output: normalizedOutput,
|
|
@@ -1514,6 +1600,8 @@ export function openPackageWorkflowControlEngine(
|
|
|
1514
1600
|
context,
|
|
1515
1601
|
parentPayloadBridge,
|
|
1516
1602
|
input.signal,
|
|
1603
|
+
process.env,
|
|
1604
|
+
{ onResponse: input.onHeaders },
|
|
1517
1605
|
);
|
|
1518
1606
|
if (!phaseRuntime.ok) {
|
|
1519
1607
|
if (phaseRuntime.failure.kind === "cancelled") {
|
|
@@ -1565,6 +1653,9 @@ export function openPackageWorkflowControlEngine(
|
|
|
1565
1653
|
...(input.artifactCorrection
|
|
1566
1654
|
? { artifactCorrection: structuredClone(input.artifactCorrection) }
|
|
1567
1655
|
: {}),
|
|
1656
|
+
...(input.contextRequest
|
|
1657
|
+
? { requestedContext: structuredClone(input.contextRequest) }
|
|
1658
|
+
: {}),
|
|
1568
1659
|
...(input.repair
|
|
1569
1660
|
? {
|
|
1570
1661
|
repair: {
|
|
@@ -1608,7 +1699,6 @@ export function openPackageWorkflowControlEngine(
|
|
|
1608
1699
|
failureOverride: phaseRuntime.failureOverride,
|
|
1609
1700
|
ledgerProjection: input.ledgerProjection,
|
|
1610
1701
|
candidateArtifact: input.candidateArtifact,
|
|
1611
|
-
onStreamStart: input.onHeaders,
|
|
1612
1702
|
onStreamProgress: input.onProgress,
|
|
1613
1703
|
});
|
|
1614
1704
|
if (!child.ok) {
|
|
@@ -1624,10 +1714,18 @@ export function openPackageWorkflowControlEngine(
|
|
|
1624
1714
|
if (child.failure.kind === "result-limit") {
|
|
1625
1715
|
return { kind: "paused", code: "needs-task-split" };
|
|
1626
1716
|
}
|
|
1717
|
+
const attemptDiagnostic = {
|
|
1718
|
+
finalCategory: child.classification.finalCategory,
|
|
1719
|
+
submitAttempts: child.classification.attempts,
|
|
1720
|
+
schema: child.classification.schema,
|
|
1721
|
+
identityMismatch: Object.entries(child.classification.identity)
|
|
1722
|
+
.filter(([, matches]) => !matches)
|
|
1723
|
+
.map(([dimension]) => dimension),
|
|
1724
|
+
};
|
|
1627
1725
|
return child.failure.kind === "environment" ||
|
|
1628
1726
|
child.failure.kind === "verification-adapter"
|
|
1629
|
-
? { kind: "paused", code: child.failure.code }
|
|
1630
|
-
: { kind: "retryable", code: child.failure.code };
|
|
1727
|
+
? { kind: "paused", code: child.failure.code, attemptDiagnostic }
|
|
1728
|
+
: { kind: "retryable", code: child.failure.code, attemptDiagnostic };
|
|
1631
1729
|
}
|
|
1632
1730
|
const result = child.result;
|
|
1633
1731
|
if (result.kind === "context-request") {
|
|
@@ -1764,6 +1862,39 @@ export function openPackageWorkflowControlEngine(
|
|
|
1764
1862
|
};
|
|
1765
1863
|
}
|
|
1766
1864
|
|
|
1865
|
+
function runStoreUnavailableStatus(
|
|
1866
|
+
error: RunStoreFormatError,
|
|
1867
|
+
command: {
|
|
1868
|
+
stage: "abel-implement";
|
|
1869
|
+
change: string;
|
|
1870
|
+
},
|
|
1871
|
+
): Record<string, unknown> {
|
|
1872
|
+
return {
|
|
1873
|
+
stage: command.stage,
|
|
1874
|
+
change: command.change,
|
|
1875
|
+
state: "paused",
|
|
1876
|
+
durable: false,
|
|
1877
|
+
completed: false,
|
|
1878
|
+
pause: { code: error.code },
|
|
1879
|
+
legalCommands: ["status", "start"],
|
|
1880
|
+
tasks: [],
|
|
1881
|
+
queue: [],
|
|
1882
|
+
controlStore: {
|
|
1883
|
+
code: error.code,
|
|
1884
|
+
databasePath: error.databasePath,
|
|
1885
|
+
recovery: structuredClone(error.recovery),
|
|
1886
|
+
},
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
function runStoreResetError(error: RunStoreFormatError): Error {
|
|
1891
|
+
const reset = new Error(
|
|
1892
|
+
`${error.code}: back up and remove the private run store at ${error.databasePath}, then retry the operation`,
|
|
1893
|
+
);
|
|
1894
|
+
reset.name = "RunStoreResetError";
|
|
1895
|
+
return reset;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1767
1898
|
function visibleRoutePolicyStatus(
|
|
1768
1899
|
resolution: RoutePolicyResolution,
|
|
1769
1900
|
brokerStatus: Record<string, unknown> | undefined,
|
|
@@ -1783,6 +1914,7 @@ export function registerWorkflowControl(
|
|
|
1783
1914
|
const parentPayloadBridge = new ParentPayloadBridge();
|
|
1784
1915
|
const activity = new ActivityController();
|
|
1785
1916
|
const engines = new Map<string, Promise<WorkflowControlEngine>>();
|
|
1917
|
+
const designFailures = new Map<string, SafeDesignFailure>();
|
|
1786
1918
|
let pendingPrompt: EligiblePrompt | undefined;
|
|
1787
1919
|
let activePrompt: EligiblePrompt | undefined;
|
|
1788
1920
|
let designToolSnapshot: string[] | undefined;
|
|
@@ -1875,30 +2007,15 @@ export function registerWorkflowControl(
|
|
|
1875
2007
|
|
|
1876
2008
|
// Pi validates tool arguments before execute(), so expose only the schema
|
|
1877
2009
|
// legal for the active stage instead of one ambiguous command/packet union.
|
|
1878
|
-
const
|
|
2010
|
+
const PACKET_PARAMETERS = {
|
|
1879
2011
|
type: "object",
|
|
1880
2012
|
properties: {
|
|
1881
|
-
|
|
1882
|
-
type: "string",
|
|
1883
|
-
enum: ["start", "status", "resume", "rebind", "cancel", "discard"],
|
|
1884
|
-
},
|
|
1885
|
-
stage: {
|
|
2013
|
+
action: {
|
|
1886
2014
|
type: "string",
|
|
1887
|
-
enum: ["
|
|
2015
|
+
enum: [...PACKET_ACTIONS, "design"],
|
|
2016
|
+
description:
|
|
2017
|
+
"Top-level dispatch action. design and run require request; finish is explicit stage exit and MUST omit request.",
|
|
1888
2018
|
},
|
|
1889
|
-
change: { type: "string" },
|
|
1890
|
-
operationId: { type: "string" },
|
|
1891
|
-
deliveryRevision: { type: "integer", minimum: 1 },
|
|
1892
|
-
receiptHash: { type: "string" },
|
|
1893
|
-
routeId: { type: "string" },
|
|
1894
|
-
},
|
|
1895
|
-
required: ["command", "stage", "change"],
|
|
1896
|
-
additionalProperties: false,
|
|
1897
|
-
} as const;
|
|
1898
|
-
const PACKET_PARAMETERS = {
|
|
1899
|
-
type: "object",
|
|
1900
|
-
properties: {
|
|
1901
|
-
action: { type: "string", enum: [...PACKET_ACTIONS, "design"] },
|
|
1902
2019
|
request: {
|
|
1903
2020
|
anyOf: [
|
|
1904
2021
|
DESIGN_REQUEST_SCHEMA,
|
|
@@ -1950,6 +2067,9 @@ export function registerWorkflowControl(
|
|
|
1950
2067
|
) {
|
|
1951
2068
|
throw new Error("control-envelope-ambiguous");
|
|
1952
2069
|
}
|
|
2070
|
+
if (record.action === "finish" && Object.hasOwn(record, "request")) {
|
|
2071
|
+
throw new Error("control-envelope-ambiguous");
|
|
2072
|
+
}
|
|
1953
2073
|
if (
|
|
1954
2074
|
activePrompt !== "abel-design" &&
|
|
1955
2075
|
activePrompt !== "abel-diagnose"
|
|
@@ -1962,22 +2082,40 @@ export function registerWorkflowControl(
|
|
|
1962
2082
|
}
|
|
1963
2083
|
const designRequest = validateDesignControlRequest(record.request);
|
|
1964
2084
|
if (!designRequest.ok) throw new Error(designRequest.code);
|
|
1965
|
-
|
|
2085
|
+
let engine: WorkflowControlEngine;
|
|
2086
|
+
try {
|
|
2087
|
+
engine = await engineFor(ctx);
|
|
2088
|
+
} catch (error) {
|
|
2089
|
+
if (error instanceof RunStoreFormatError)
|
|
2090
|
+
throw runStoreResetError(error);
|
|
2091
|
+
throw error;
|
|
2092
|
+
}
|
|
1966
2093
|
if (!engine.executeDesign)
|
|
1967
2094
|
throw new Error("design-control-unavailable");
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2095
|
+
try {
|
|
2096
|
+
const payload = await engine.executeDesign(designRequest.value);
|
|
2097
|
+
if (
|
|
2098
|
+
designRequest.value.operation === "finalize-delivery" &&
|
|
2099
|
+
payload.state === "completed"
|
|
2100
|
+
) {
|
|
2101
|
+
await deactivateStage();
|
|
2102
|
+
}
|
|
2103
|
+
return {
|
|
2104
|
+
content: [
|
|
2105
|
+
{ type: "text" as const, text: JSON.stringify(payload) },
|
|
2106
|
+
],
|
|
2107
|
+
details: payload,
|
|
2108
|
+
};
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
const failure = safeDesignFailure(
|
|
2111
|
+
error,
|
|
2112
|
+
designRequest.value.operation,
|
|
2113
|
+
);
|
|
2114
|
+
designFailures.set(toolCallId, failure);
|
|
2115
|
+
const sanitized = new Error(failure.code);
|
|
2116
|
+
sanitized.name = "DesignControlError";
|
|
2117
|
+
throw sanitized;
|
|
1974
2118
|
}
|
|
1975
|
-
return {
|
|
1976
|
-
content: [
|
|
1977
|
-
{ type: "text" as const, text: JSON.stringify(payload) },
|
|
1978
|
-
],
|
|
1979
|
-
details: payload,
|
|
1980
|
-
};
|
|
1981
2119
|
}
|
|
1982
2120
|
let designEngine: WorkflowControlEngine | undefined;
|
|
1983
2121
|
if (record.action === "run") {
|
|
@@ -1988,7 +2126,13 @@ export function registerWorkflowControl(
|
|
|
1988
2126
|
);
|
|
1989
2127
|
}
|
|
1990
2128
|
if (packet.value.stage === "abel-design") {
|
|
1991
|
-
|
|
2129
|
+
try {
|
|
2130
|
+
designEngine = await engineFor(ctx);
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
if (error instanceof RunStoreFormatError)
|
|
2133
|
+
throw runStoreResetError(error);
|
|
2134
|
+
throw error;
|
|
2135
|
+
}
|
|
1992
2136
|
if (!designEngine.assertDesignRun || !packet.value.runId) {
|
|
1993
2137
|
throw new Error("design-control-unavailable");
|
|
1994
2138
|
}
|
|
@@ -2076,7 +2220,17 @@ export function registerWorkflowControl(
|
|
|
2076
2220
|
) {
|
|
2077
2221
|
throw new Error("stage-control-mismatch");
|
|
2078
2222
|
}
|
|
2079
|
-
|
|
2223
|
+
let engine: WorkflowControlEngine;
|
|
2224
|
+
try {
|
|
2225
|
+
engine = await engineFor(ctx);
|
|
2226
|
+
} catch (error) {
|
|
2227
|
+
if (!(error instanceof RunStoreFormatError)) throw error;
|
|
2228
|
+
const payload = runStoreUnavailableStatus(error, validation.value);
|
|
2229
|
+
return {
|
|
2230
|
+
content: [{ type: "text" as const, text: JSON.stringify(payload) }],
|
|
2231
|
+
details: payload,
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2080
2234
|
const tuiControl = ctx.mode === "tui";
|
|
2081
2235
|
if (tuiControl) {
|
|
2082
2236
|
try {
|
|
@@ -2161,6 +2315,39 @@ export function registerWorkflowControl(
|
|
|
2161
2315
|
};
|
|
2162
2316
|
registerDispatchTool("packet");
|
|
2163
2317
|
|
|
2318
|
+
pi.on("tool_result", (event) => {
|
|
2319
|
+
if (
|
|
2320
|
+
event.toolName !== DISPATCH_TOOL ||
|
|
2321
|
+
!event.isError ||
|
|
2322
|
+
event.input.action !== "design"
|
|
2323
|
+
) {
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
const errorText = event.content
|
|
2327
|
+
.flatMap((item) =>
|
|
2328
|
+
item.type === "text" && typeof item.text === "string"
|
|
2329
|
+
? [item.text]
|
|
2330
|
+
: [],
|
|
2331
|
+
)
|
|
2332
|
+
.join("\n");
|
|
2333
|
+
const failure = safeDesignFailure(
|
|
2334
|
+
new Error(errorText.trim()),
|
|
2335
|
+
typeof (event.input.request as Record<string, unknown> | undefined)
|
|
2336
|
+
?.operation === "string"
|
|
2337
|
+
? String((event.input.request as Record<string, unknown>).operation)
|
|
2338
|
+
: "unknown",
|
|
2339
|
+
);
|
|
2340
|
+
const details =
|
|
2341
|
+
event.details && typeof event.details === "object"
|
|
2342
|
+
? (event.details as Record<string, unknown>)
|
|
2343
|
+
: {};
|
|
2344
|
+
const visible = designFailures.get(event.toolCallId) ?? failure;
|
|
2345
|
+
designFailures.delete(event.toolCallId);
|
|
2346
|
+
return {
|
|
2347
|
+
content: [{ type: "text" as const, text: JSON.stringify(visible) }],
|
|
2348
|
+
details: { ...details, designFailure: visible },
|
|
2349
|
+
};
|
|
2350
|
+
});
|
|
2164
2351
|
pi.on("input", (event) => {
|
|
2165
2352
|
pendingPrompt = invokedPrompt(event.text);
|
|
2166
2353
|
return { action: "continue" };
|
|
@@ -2214,6 +2401,7 @@ export function registerWorkflowControl(
|
|
|
2214
2401
|
pi.on("session_start", async (_event, ctx) => {
|
|
2215
2402
|
pendingPrompt = undefined;
|
|
2216
2403
|
activePrompt = undefined;
|
|
2404
|
+
designFailures.clear();
|
|
2217
2405
|
activity.detach();
|
|
2218
2406
|
await packetRuntime.drain();
|
|
2219
2407
|
await closeEngines();
|
|
@@ -2229,6 +2417,7 @@ export function registerWorkflowControl(
|
|
|
2229
2417
|
});
|
|
2230
2418
|
pi.on("session_shutdown", async () => {
|
|
2231
2419
|
activePrompt = undefined;
|
|
2420
|
+
designFailures.clear();
|
|
2232
2421
|
activity.detach();
|
|
2233
2422
|
await packetRuntime.drain();
|
|
2234
2423
|
await closeEngines();
|