@tea-agent/loop-agent 0.10.0 → 0.12.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 +10 -2
- package/CHANGELOG.md +91 -24
- package/README.md +84 -12
- package/dist/application/dag/args.js +1 -12
- package/dist/application/dag/generate-task-dag.js +38 -2
- package/dist/application/dag/run-dag.js +11 -27
- package/dist/application/dag/validate-dag.js +13 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +44 -16
- package/dist/cli/program.js +40 -23
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +58 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli.js +33 -0
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +547 -95
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- package/dist/commands/plan.js +50 -0
- package/dist/executors/config-core.js +0 -51
- package/dist/executors/dag-pi-executor.js +1 -1
- package/dist/executors/dag.js +0 -1
- package/dist/executors/index.js +0 -2
- package/dist/executors/model-routing.js +9 -9
- package/dist/executors/shell-executor.js +1 -1
- package/dist/governance/checks.js +6 -3
- package/dist/governance/exec-plans.js +545 -0
- package/dist/governance/manifest-types.js +24 -2
- package/dist/infrastructure/harness/loop-action-store.js +0 -3
- package/dist/records/harvest.js +2 -23
- package/dist/records/one-shot-runs.js +1 -1
- package/dist/shared/artifacts-core.js +24 -5
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +488 -0
- package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +29 -12
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +1 -2
- package/dist/worker/cli.js +32 -3
- package/dist/worker/delivery/final-verification.js +47 -11
- package/dist/worker/delivery/package.js +63 -10
- package/dist/worker/feature/run.js +60 -8
- package/dist/worker/loop-agent/loop-agent-client.js +329 -126
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/read-model.js +338 -83
- package/dist/worker/observe/paths.js +17 -0
- package/dist/worker/observe/routes.js +165 -21
- package/dist/worker/observe/server.js +59 -1
- package/dist/worker/observe/static/api.js +27 -0
- package/dist/worker/observe/static/app.js +120 -2317
- package/dist/worker/observe/static/constants.js +148 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/dag-helpers.js +172 -0
- package/dist/worker/observe/static/dag-model.js +72 -0
- package/dist/worker/observe/static/dom.js +61 -0
- package/dist/worker/observe/static/format-pool.js +67 -0
- package/dist/worker/observe/static/format.js +292 -0
- package/dist/worker/observe/static/index.html +300 -82
- package/dist/worker/observe/static/kpi.js +94 -0
- package/dist/worker/observe/static/relations.js +128 -0
- package/dist/worker/observe/static/router.js +85 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +68 -0
- package/dist/worker/observe/static/state.js +253 -0
- package/dist/worker/observe/static/styles.css +1720 -495
- package/dist/worker/observe/static/views/batch.js +226 -0
- package/dist/worker/observe/static/views/dag-graph.js +172 -0
- package/dist/worker/observe/static/views/dag-inspector.js +477 -0
- package/dist/worker/observe/static/views/dag.js +362 -0
- package/dist/worker/observe/static/views/dashboard.js +442 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +453 -0
- package/dist/worker/observe/static/views/pool.js +347 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +205 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +260 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/run-task/run-task.js +22 -12
- package/dist/worker/runner/run-ready.js +76 -12
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/init-hybrid.js +797 -27
- package/dist/workflows/dag/node-execution.js +183 -35
- package/dist/workflows/dag/repair-artifact.js +91 -0
- package/dist/workflows/dag/report.js +50 -0
- package/dist/workflows/dag/retry-policy.js +138 -0
- package/dist/workflows/dag/runner.js +77 -17
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +5 -4
- package/dist/workflows/dag/skill-snapshot.js +529 -0
- package/dist/workflows/dag/types.js +86 -10
- package/dist/workflows/dag/validate.js +73 -12
- package/dist/workflows/loop/actions/dag-action.js +0 -2
- package/dist/workflows/loop/actions/shared.js +1 -1
- package/dist/workflows/loop/actions.js +14 -31
- package/dist/workflows/loop/benchmark.js +1 -1
- package/dist/workflows/loop/index.js +1 -1
- package/dist/workflows/loop/policy/auto-policy.js +22 -14
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/docs/README.md +36 -33
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/agent-dag-runner.md +28 -3
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +140 -0
- package/docs/architecture/evolution.md +53 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +45 -17
- package/docs/architecture/system-overview.md +93 -0
- package/docs/architecture/worker-and-feature.md +81 -0
- package/docs/cursor-prompt-sidecar.md +36 -0
- package/docs/decisions/README.md +13 -1
- package/docs/design/README.md +43 -21
- package/docs/development-principles.md +2 -2
- package/docs/exec-plans/active/README.md +1 -3
- package/docs/exec-plans/completed/README.md +23 -0
- package/docs/feature-workflow.md +78 -4
- package/docs/harness-methodology-debugging.md +1 -1
- package/docs/harness-methodology-tdd.md +3 -3
- package/docs/init-surface.manifest.json +60 -25
- package/docs/loop-agent-harness.md +28 -4
- package/docs/progress/README.md +50 -1
- package/docs/reports/README.md +90 -18
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +23 -6
- package/docs/templates/agent-dag.base.json +0 -5
- package/docs/templates/agent-dag.final-verification.json +0 -5
- package/docs/templates/agent-dag.schema.json +70 -3
- package/docs/templates/agent-dag.supervised-implementation.json +9 -8
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +139 -0
- package/docs/templates/backend-test-dag.json +276 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -0
- package/docs/templates/frontend-design-contract.md +33 -0
- package/docs/templates/frontend-task-constraints.md +25 -0
- package/docs/templates/frontend-task-requirement.md +61 -0
- package/docs/templates/harness.schema.json +10 -12
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/project-start-checklist.md +2 -2
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +10 -0
- package/examples/decision-gate-agent-dag.json +87 -33
- package/examples/example-dag.json +0 -5
- package/examples/hybrid-loop-agent-dag.json +0 -5
- package/harness.json +7 -15
- package/package.json +22 -46
- package/scripts/check-product-line-docs.sh +10 -7
- package/skills/agent-worker/SKILL.md +37 -0
- package/skills/agent-worker/references/agent-worker-operator.md +43 -0
- package/skills/frontend-design-review/SKILL.md +59 -0
- package/skills/frontend-design-review/references/review-checklist.md +37 -0
- package/skills/frontend-implementation/SKILL.md +51 -0
- package/skills/frontend-implementation/references/code-standards.md +34 -0
- package/skills/frontend-implementation/references/design-spec.md +46 -0
- package/skills/frontend-implementation/references/node-contracts.md +32 -0
- package/skills/frontend-review/SKILL.md +53 -0
- package/skills/frontend-review/references/review-findings.md +42 -0
- package/skills/frontend-verification/SKILL.md +40 -0
- package/skills/frontend-verification/references/verification-checklist.md +56 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +88 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +60 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +14 -15
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +7 -7
- package/skills/loop-agent/references/hybrid-dag.md +36 -20
- package/skills/loop-agent/references/long-running-loop.md +4 -6
- package/skills/loop-agent/references/multi-worktree.md +6 -6
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/loop-agent/references/verification-and-failure-handling.md +6 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/dist/commands/cursor-worker.js +0 -43
- package/dist/cursor-worker-entry.js +0 -8
- package/dist/executors/cursor-artifacts.js +0 -33
- package/dist/executors/cursor-execution-log.js +0 -81
- package/dist/executors/cursor-executor-artifacts.js +0 -134
- package/dist/executors/cursor-run.js +0 -115
- package/dist/executors/cursor-tool.js +0 -94
- package/dist/executors/cursor-worker-client.js +0 -223
- package/dist/executors/cursor-worker-protocol.js +0 -18
- package/dist/executors/cursor-worker-server.js +0 -54
- package/dist/executors/cursor-worker.js +0 -3
- package/dist/executors/cursor.js +0 -6
- package/dist/executors/dag-cursor-executor.js +0 -87
- package/dist/workflows/loop/actions/cursor-fix.js +0 -191
- package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
- package/docs/cursor-executor-usage.md +0 -25
- package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import YAML from "yaml";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { controllerIdentitiesMatch, LOOP_AGENT_PACKAGE_NAME, } from "../loop-agent/loop-agent-client.js";
|
|
8
9
|
import { getRunsJsonlPath, getTaskPoolRoot, readAllTaskPoolStates, readJsonlFile } from "../pool/run-store.js";
|
|
9
10
|
import { acceptanceSpecSchema } from "../task-graph/acceptance-schema.js";
|
|
10
11
|
import { taskGraphSpecSchema } from "../task-graph/task-graph-schema.js";
|
|
@@ -34,10 +35,50 @@ export const deliveryManifestSchema = z.object({
|
|
|
34
35
|
riskSummary: z.object({ high: z.number().int().nonnegative(), medium: z.number().int().nonnegative(), low: z.number().int().nonnegative() }).strict(),
|
|
35
36
|
}).strict();
|
|
36
37
|
const waiverSchema = z.object({ schemaVersion: z.literal(1), featureId: z.string(), items: z.array(z.object({ acId: z.string(), owner: z.string().min(1), reason: z.string().min(1), decidedAt: z.string().datetime() }).strict()) }).strict();
|
|
37
|
-
const
|
|
38
|
+
const controllerIdentityEvidenceSchema = z.object({
|
|
39
|
+
schemaVersion: z.literal(1),
|
|
40
|
+
packageName: z.literal(LOOP_AGENT_PACKAGE_NAME),
|
|
41
|
+
binName: z.string().min(1),
|
|
42
|
+
requested: z.string().min(1),
|
|
43
|
+
entry: z.string().min(1),
|
|
44
|
+
realEntry: z.string().min(1),
|
|
45
|
+
launch: z.object({ command: z.string().min(1), argsPrefix: z.array(z.string()) }).strict(),
|
|
46
|
+
binarySha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
47
|
+
packageRoot: z.string().min(1),
|
|
48
|
+
packageVersion: z.string().min(1),
|
|
49
|
+
reportedVersion: z.string().min(1).optional(),
|
|
50
|
+
packageFingerprint: z.object({
|
|
51
|
+
algorithm: z.literal("sha256"),
|
|
52
|
+
scopeVersion: z.literal(1),
|
|
53
|
+
scope: z.tuple([
|
|
54
|
+
z.literal("package.json"),
|
|
55
|
+
z.literal("bin/**"),
|
|
56
|
+
z.literal("dist/**"),
|
|
57
|
+
z.literal("skills/**"),
|
|
58
|
+
]),
|
|
59
|
+
value: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
60
|
+
fileCount: z.number().int().positive(),
|
|
61
|
+
}).strict(),
|
|
62
|
+
resolvedAt: z.string().datetime(),
|
|
63
|
+
}).strict().superRefine((identity, ctx) => {
|
|
64
|
+
if (identity.reportedVersion !== undefined
|
|
65
|
+
&& identity.reportedVersion !== identity.packageVersion) {
|
|
66
|
+
ctx.addIssue({
|
|
67
|
+
code: z.ZodIssueCode.custom,
|
|
68
|
+
path: ["reportedVersion"],
|
|
69
|
+
message: "reportedVersion must match packageVersion",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const evidenceRunSchema = z.object({
|
|
74
|
+
taskId: z.string(),
|
|
75
|
+
workerRunId: z.string(),
|
|
76
|
+
recordedAt: z.string().datetime(),
|
|
77
|
+
controllerIdentity: controllerIdentityEvidenceSchema.optional(),
|
|
78
|
+
}).strict();
|
|
38
79
|
const qaEvidenceSchema = z.object({ schemaVersion: z.literal(1), featureId: z.string(), verdict: z.literal("passed"), acIds: z.array(z.string()).min(1), runs: z.array(evidenceRunSchema).min(1) }).strict();
|
|
39
80
|
const finalVerificationSchema = z.object({ schemaVersion: z.literal(1), featureId: z.string(), kind: z.literal("final-verification"), status: z.literal("passed"), headSha: z.string().regex(/^[a-f0-9]{40}$/), run: evidenceRunSchema, shellSummary: hashedRefSchema }).strict();
|
|
40
|
-
const workerRunEvidenceSchema = z.object({ schemaVersion: z.literal(1), status: z.literal("succeeded"), workerRunId: z.string(), businessId: z.string(), featureId: z.string(), reportDecision: z.object({ succeeded: z.literal(true) }).passthrough(), commands: z.array(z.object({ name: z.string(), result: z.object({ ok: z.literal(true) }).passthrough() }).passthrough()).min(1) }).passthrough();
|
|
81
|
+
const workerRunEvidenceSchema = z.object({ schemaVersion: z.literal(1), status: z.literal("succeeded"), workerRunId: z.string(), businessId: z.string(), featureId: z.string(), reportDecision: z.object({ succeeded: z.literal(true) }).passthrough(), commands: z.array(z.object({ name: z.string(), result: z.object({ ok: z.literal(true) }).passthrough() }).passthrough()).min(1), controllerIdentity: controllerIdentityEvidenceSchema.optional() }).passthrough();
|
|
41
82
|
export async function prepareFeatureDelivery(input) {
|
|
42
83
|
const repoRoot = path.resolve(input.repoRoot);
|
|
43
84
|
const featureDir = path.resolve(input.featureDir);
|
|
@@ -300,8 +341,8 @@ async function canonicalQaEvidence(repoRoot, ref, featureId, runs, specs, now, b
|
|
|
300
341
|
if (data.featureId !== featureId)
|
|
301
342
|
throw new Error("Feature ownership mismatch");
|
|
302
343
|
for (const entry of data.runs) {
|
|
303
|
-
assertEvidenceRun(entry, featureId, runs, specs, now, true);
|
|
304
|
-
await readCanonicalWorkerRun(repoRoot,
|
|
344
|
+
const run = assertEvidenceRun(entry, featureId, runs, specs, now, true);
|
|
345
|
+
await readCanonicalWorkerRun(repoRoot, run);
|
|
305
346
|
}
|
|
306
347
|
const allowedAcIds = new Set(data.runs.flatMap((entry) => specs.get(entry.taskId)?.acceptance_refs ?? []));
|
|
307
348
|
for (const acId of data.acIds)
|
|
@@ -322,13 +363,13 @@ async function canonicalFinalEvidence(repoRoot, ref, featureId, runs, specs, rec
|
|
|
322
363
|
const data = finalVerificationSchema.parse(JSON.parse(await readFile(await safeRepoPath(repoRoot, ref), "utf-8")));
|
|
323
364
|
if (data.featureId !== featureId)
|
|
324
365
|
throw new Error("Feature ownership mismatch");
|
|
325
|
-
assertEvidenceRun(data.run, featureId, runs, specs, now, true);
|
|
366
|
+
const taskPoolRun = assertEvidenceRun(data.run, featureId, runs, specs, now, true);
|
|
326
367
|
if (data.headSha !== record.lastCheckpoint)
|
|
327
368
|
throw new Error("final verification is not bound to Delivery HEAD");
|
|
328
369
|
const latestCheckpointAt = Math.max(...record.checkpoints.map((entry) => new Date(entry.createdAt).getTime()));
|
|
329
370
|
if (new Date(data.run.recordedAt).getTime() < latestCheckpointAt)
|
|
330
371
|
throw new Error("final verification predates the last checkpoint");
|
|
331
|
-
const runRecord = await readCanonicalWorkerRun(repoRoot,
|
|
372
|
+
const runRecord = await readCanonicalWorkerRun(repoRoot, taskPoolRun);
|
|
332
373
|
if (!runRecord.commands.some((command) => command.name === "run-dag"))
|
|
333
374
|
throw new Error("final verification run is missing a successful run-dag command");
|
|
334
375
|
const spec = specs.get(data.run.taskId);
|
|
@@ -360,6 +401,8 @@ function assertEvidenceRun(entry, featureId, runs, specs, now, requireQa) {
|
|
|
360
401
|
const age = now.getTime() - new Date(run.recordedAt).getTime();
|
|
361
402
|
if (age < 0 || age > 24 * 60 * 60 * 1000)
|
|
362
403
|
throw new Error(`run is not fresh within 24 hours: ${entry.workerRunId}`);
|
|
404
|
+
assertControllerIdentityPair(entry.controllerIdentity, run.controllerIdentity, "evidence and Task Pool run");
|
|
405
|
+
return run;
|
|
363
406
|
}
|
|
364
407
|
async function canonicalTaskRunEvidence(repoRoot, featureId, taskId, runs, blockers, workerRunId) {
|
|
365
408
|
const run = [...runs].reverse().find((candidate) => candidate.featureId === featureId && candidate.taskId === taskId && candidate.status === "succeeded" && candidate.runRecordPath && (!workerRunId || candidate.workerRunId === workerRunId));
|
|
@@ -369,7 +412,7 @@ async function canonicalTaskRunEvidence(repoRoot, featureId, taskId, runs, block
|
|
|
369
412
|
if (!hashed)
|
|
370
413
|
return undefined;
|
|
371
414
|
try {
|
|
372
|
-
await readCanonicalWorkerRun(repoRoot,
|
|
415
|
+
await readCanonicalWorkerRun(repoRoot, run);
|
|
373
416
|
return hashed;
|
|
374
417
|
}
|
|
375
418
|
catch (error) {
|
|
@@ -377,15 +420,25 @@ async function canonicalTaskRunEvidence(repoRoot, featureId, taskId, runs, block
|
|
|
377
420
|
return undefined;
|
|
378
421
|
}
|
|
379
422
|
}
|
|
380
|
-
async function readCanonicalWorkerRun(repoRoot,
|
|
381
|
-
const run = runs.find((candidate) => candidate.featureId === featureId && candidate.taskId === taskId && candidate.workerRunId === workerRunId && candidate.status === "succeeded" && candidate.runRecordPath);
|
|
423
|
+
async function readCanonicalWorkerRun(repoRoot, run) {
|
|
382
424
|
if (!run?.runRecordPath)
|
|
383
425
|
throw new Error("Task Pool run record path is missing");
|
|
384
426
|
const record = workerRunEvidenceSchema.parse(JSON.parse(await readFile(await safeRepoPath(repoRoot, run.runRecordPath), "utf-8")));
|
|
385
|
-
if (record.featureId !== featureId || record.businessId !== taskId || record.workerRunId !== workerRunId)
|
|
427
|
+
if (record.featureId !== run.featureId || record.businessId !== run.taskId || record.workerRunId !== run.workerRunId)
|
|
386
428
|
throw new Error("run record ownership mismatch");
|
|
429
|
+
assertControllerIdentityPair(run.controllerIdentity, record.controllerIdentity, "Task Pool run and worker run record");
|
|
387
430
|
return record;
|
|
388
431
|
}
|
|
432
|
+
function assertControllerIdentityPair(left, right, label) {
|
|
433
|
+
if (left === undefined && right === undefined)
|
|
434
|
+
return;
|
|
435
|
+
if (left === undefined || right === undefined)
|
|
436
|
+
throw new Error(`controller identity presence mismatch between ${label}`);
|
|
437
|
+
const parsedLeft = controllerIdentityEvidenceSchema.parse(left);
|
|
438
|
+
const parsedRight = controllerIdentityEvidenceSchema.parse(right);
|
|
439
|
+
if (!controllerIdentitiesMatch(parsedLeft, parsedRight))
|
|
440
|
+
throw new Error(`controller identity mismatch between ${label}`);
|
|
441
|
+
}
|
|
389
442
|
async function writeDeliveryArtifacts(artifacts, coverage, manifest) {
|
|
390
443
|
const deliveryDir = path.dirname(artifacts.manifest);
|
|
391
444
|
const parent = path.dirname(deliveryDir);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { controllerIdentityExpectationFailure, resolveControllerIdentity, } from "../loop-agent/loop-agent-client.js";
|
|
3
4
|
import { buildGlobalSnapshot } from "../observability/read-model.js";
|
|
4
5
|
import { createSnapshotStore } from "../observability/snapshot-store.js";
|
|
5
6
|
import { preflightTargetRepo } from "../preflight.js";
|
|
@@ -29,6 +30,37 @@ export async function runFeature(options) {
|
|
|
29
30
|
let gitTransaction;
|
|
30
31
|
const gitTasks = [];
|
|
31
32
|
const expectedArtifacts = expectedArtifactPaths(repoRoot, batchRunId);
|
|
33
|
+
let controllerIdentity;
|
|
34
|
+
try {
|
|
35
|
+
controllerIdentity = resolveControllerIdentity(options.client, options.controllerIdentity);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return failedResult({
|
|
39
|
+
featureId,
|
|
40
|
+
mode: options.dryRun ? "dry-run" : "executed",
|
|
41
|
+
steps: [{ id: "preflight", status: "failed", durationMs: 0, message: errorMessage(error) }],
|
|
42
|
+
limitApplied,
|
|
43
|
+
code: "controller-identity-client-mismatch",
|
|
44
|
+
message: errorMessage(error),
|
|
45
|
+
evidence: [],
|
|
46
|
+
expectedArtifacts,
|
|
47
|
+
controllerIdentity: options.controllerIdentity,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const identityFailure = controllerIdentityExpectationFailure(controllerIdentity, options.controllerExpectation);
|
|
51
|
+
if (identityFailure) {
|
|
52
|
+
return failedResult({
|
|
53
|
+
featureId,
|
|
54
|
+
mode: options.dryRun ? "dry-run" : "executed",
|
|
55
|
+
steps: [{ id: "preflight", status: "failed", durationMs: 0, message: identityFailure.message }],
|
|
56
|
+
limitApplied,
|
|
57
|
+
code: identityFailure.code,
|
|
58
|
+
message: identityFailure.message,
|
|
59
|
+
evidence: [],
|
|
60
|
+
expectedArtifacts,
|
|
61
|
+
controllerIdentity,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
32
64
|
progress.step("feature validate");
|
|
33
65
|
const validation = await timedStep(steps, "validate", async () => dependencies.validateFeature(featureDir));
|
|
34
66
|
if (!validation.value?.ok) {
|
|
@@ -44,6 +76,7 @@ export async function runFeature(options) {
|
|
|
44
76
|
evidence: errors.flatMap((error) => error.path ? [error.path] : []),
|
|
45
77
|
nextCommand: `agent-worker task validate-feature ${JSON.stringify(featureDir)}`,
|
|
46
78
|
expectedArtifacts,
|
|
79
|
+
controllerIdentity,
|
|
47
80
|
});
|
|
48
81
|
}
|
|
49
82
|
featureId = validation.value.featureId;
|
|
@@ -56,11 +89,14 @@ export async function runFeature(options) {
|
|
|
56
89
|
client: options.client,
|
|
57
90
|
runCheckRepo: options.runCheckRepo,
|
|
58
91
|
...(options.checkRepoCommand ? { checkRepoCommand: options.checkRepoCommand } : {}),
|
|
92
|
+
...(options.controllerExpectation ? { expectation: options.controllerExpectation } : {}),
|
|
59
93
|
}));
|
|
60
94
|
if (!preflight.value?.ok) {
|
|
61
95
|
markLastStepFailed(steps, preflight.value && !preflight.value.ok ? preflight.value.message : preflight.error ?? "target repo preflight failed");
|
|
62
|
-
return preflightFailure({ featureId, repoRoot, steps, limitApplied, preflight: preflight.value, fallbackMessage: preflight.error, expectedArtifacts
|
|
96
|
+
return preflightFailure({ featureId, repoRoot, steps, limitApplied, preflight: preflight.value, fallbackMessage: preflight.error, expectedArtifacts,
|
|
97
|
+
controllerIdentity: preflight.value?.controllerIdentity ?? controllerIdentity });
|
|
63
98
|
}
|
|
99
|
+
controllerIdentity = preflight.value.controllerIdentity ?? controllerIdentity;
|
|
64
100
|
steps.at(-1).artifacts = preflight.value.commands.map((command) => command.result.artifacts.resultPath);
|
|
65
101
|
}
|
|
66
102
|
if (checkpointMode) {
|
|
@@ -68,7 +104,8 @@ export async function runFeature(options) {
|
|
|
68
104
|
const canonicalFeature = await realpath(featureDir).catch(() => featureDir);
|
|
69
105
|
const featureRelative = path.relative(canonicalRepo, canonicalFeature);
|
|
70
106
|
if (featureRelative.startsWith("..") || path.isAbsolute(featureRelative)) {
|
|
71
|
-
return failedResult({ featureId, mode: options.dryRun ? "dry-run" : "executed", steps, limitApplied, code: "git-feature-repo-mismatch", message: "Feature Packet must be inside the Git target repo for checkpoint mode", evidence: [featureDir, repoRoot], expectedArtifacts
|
|
107
|
+
return failedResult({ featureId, mode: options.dryRun ? "dry-run" : "executed", steps, limitApplied, code: "git-feature-repo-mismatch", message: "Feature Packet must be inside the Git target repo for checkpoint mode", evidence: [featureDir, repoRoot], expectedArtifacts,
|
|
108
|
+
controllerIdentity });
|
|
72
109
|
}
|
|
73
110
|
if (options.dryRun) {
|
|
74
111
|
steps.push(planned("git-preflight", `create or reuse agent/${featureId.toLowerCase()} and checkpoint each task`));
|
|
@@ -76,14 +113,16 @@ export async function runFeature(options) {
|
|
|
76
113
|
else {
|
|
77
114
|
const gitStep = await timedStep(steps, "git-preflight", async () => dependencies.startGit({ repoRoot, featureId, now }));
|
|
78
115
|
if (!gitStep.value)
|
|
79
|
-
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "git-preflight-failed", message: gitStep.error ?? "Git transaction preflight failed", evidence: [], expectedArtifacts
|
|
116
|
+
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "git-preflight-failed", message: gitStep.error ?? "Git transaction preflight failed", evidence: [], expectedArtifacts,
|
|
117
|
+
controllerIdentity });
|
|
80
118
|
gitTransaction = gitStep.value;
|
|
81
119
|
steps.at(-1).artifacts = [gitTransaction.recordPath];
|
|
82
120
|
}
|
|
83
121
|
}
|
|
84
122
|
const beforeStep = await timedStep(steps, "review-before", async () => dependencies.review({ featureDir, repoRoot }));
|
|
85
123
|
if (!beforeStep.value) {
|
|
86
|
-
return failedResult({ featureId, mode: options.dryRun ? "dry-run" : "executed", steps, limitApplied, code: "feature-review-failed", message: beforeStep.error ?? "Feature review failed", evidence: [], expectedArtifacts
|
|
124
|
+
return failedResult({ featureId, mode: options.dryRun ? "dry-run" : "executed", steps, limitApplied, code: "feature-review-failed", message: beforeStep.error ?? "Feature review failed", evidence: [], expectedArtifacts,
|
|
125
|
+
controllerIdentity });
|
|
87
126
|
}
|
|
88
127
|
before = beforeStep.value;
|
|
89
128
|
const readyTasks = before.tasks.filter((task) => task.status === "Ready").map((task) => task.taskId);
|
|
@@ -108,6 +147,7 @@ export async function runFeature(options) {
|
|
|
108
147
|
batch: null,
|
|
109
148
|
gitTasks,
|
|
110
149
|
limitApplied,
|
|
150
|
+
controllerIdentity,
|
|
111
151
|
};
|
|
112
152
|
}
|
|
113
153
|
if (readyTasks.length > 0 && limitApplied > 0) {
|
|
@@ -122,6 +162,8 @@ export async function runFeature(options) {
|
|
|
122
162
|
progress,
|
|
123
163
|
skipTaskPreflight: true,
|
|
124
164
|
...(options.piModel ? { piModel: options.piModel } : {}),
|
|
165
|
+
...(controllerIdentity ? { controllerIdentity } : {}),
|
|
166
|
+
...(options.controllerExpectation ? { controllerExpectation: options.controllerExpectation } : {}),
|
|
125
167
|
...(gitTransaction ? { onTaskFinalized: async (task) => {
|
|
126
168
|
const gitResult = await dependencies.finalizeGit(gitTransaction, { ...task, keepFailedDiff: options.keepFailedDiff, now });
|
|
127
169
|
gitTasks.push(gitResult);
|
|
@@ -132,7 +174,8 @@ export async function runFeature(options) {
|
|
|
132
174
|
batch = runStep.value;
|
|
133
175
|
if (!batch) {
|
|
134
176
|
const kept = runStep.error?.startsWith("git-keep-failed-diff:") ?? false;
|
|
135
|
-
const failed = failedResult({ featureId, mode: "executed", steps, limitApplied, code: kept ? "git-failed-diff-kept" : "run-ready-failed", message: kept ? "Failed task diff was kept by explicit request; automatic execution stopped" : runStep.error ?? "run-ready failed", evidence: kept ? [runStep.error.slice("git-keep-failed-diff:".length)] : [], before, readyTasks, expectedArtifacts
|
|
177
|
+
const failed = failedResult({ featureId, mode: "executed", steps, limitApplied, code: kept ? "git-failed-diff-kept" : "run-ready-failed", message: kept ? "Failed task diff was kept by explicit request; automatic execution stopped" : runStep.error ?? "run-ready failed", evidence: kept ? [runStep.error.slice("git-keep-failed-diff:".length)] : [], before, readyTasks, expectedArtifacts,
|
|
178
|
+
controllerIdentity });
|
|
136
179
|
return kept ? { ...failed, status: "needs-action", gitTasks, artifacts: { ...failed.artifacts, gitTransaction: gitTransaction?.recordPath ?? null } } : failed;
|
|
137
180
|
}
|
|
138
181
|
steps.at(-1).artifacts = [batch.batchRunPath];
|
|
@@ -147,7 +190,8 @@ export async function runFeature(options) {
|
|
|
147
190
|
progress.step("refresh morning report");
|
|
148
191
|
const report = await timedStep(steps, "morning-report", async () => dependencies.writeReport({ repoRoot, ...(batch ? { batchRunId: batch.batchRunId } : {}), outputPath: morningReportPath }));
|
|
149
192
|
if (!report.value)
|
|
150
|
-
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "morning-report-failed", message: report.error ?? "morning report failed", evidence: [], before, readyTasks, batch, expectedArtifacts
|
|
193
|
+
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "morning-report-failed", message: report.error ?? "morning report failed", evidence: [], before, readyTasks, batch, expectedArtifacts,
|
|
194
|
+
controllerIdentity });
|
|
151
195
|
steps.at(-1).artifacts = [report.value];
|
|
152
196
|
const snapshotPath = path.join(getTaskPoolRoot(repoRoot), "observability", "snapshot.json");
|
|
153
197
|
progress.step("refresh Observe snapshot");
|
|
@@ -157,12 +201,14 @@ export async function runFeature(options) {
|
|
|
157
201
|
return snapshotPath;
|
|
158
202
|
});
|
|
159
203
|
if (!snapshot.value)
|
|
160
|
-
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "observe-snapshot-failed", message: snapshot.error ?? "Observe snapshot failed", evidence: [report.value], before, readyTasks, batch, artifacts: { morningReport: report.value }, expectedArtifacts
|
|
204
|
+
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "observe-snapshot-failed", message: snapshot.error ?? "Observe snapshot failed", evidence: [report.value], before, readyTasks, batch, artifacts: { morningReport: report.value }, expectedArtifacts,
|
|
205
|
+
controllerIdentity });
|
|
161
206
|
steps.at(-1).artifacts = [snapshot.value];
|
|
162
207
|
progress.step("derive final Feature review");
|
|
163
208
|
const after = await timedStep(steps, "review-after", async () => dependencies.review({ featureDir, repoRoot }));
|
|
164
209
|
if (!after.value)
|
|
165
|
-
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "feature-review-failed", message: after.error ?? "final Feature review failed", evidence: [report.value, snapshot.value], before, readyTasks, batch, artifacts: { morningReport: report.value, observeSnapshot: snapshot.value }, expectedArtifacts
|
|
210
|
+
return failedResult({ featureId, mode: "executed", steps, limitApplied, code: "feature-review-failed", message: after.error ?? "final Feature review failed", evidence: [report.value, snapshot.value], before, readyTasks, batch, artifacts: { morningReport: report.value, observeSnapshot: snapshot.value }, expectedArtifacts,
|
|
211
|
+
controllerIdentity });
|
|
166
212
|
const taskFailures = batch?.tasks.filter((task) => task.status === "failed") ?? [];
|
|
167
213
|
const runInfrastructureFailed = batch?.status === "failed";
|
|
168
214
|
const executionError = runInfrastructureFailed
|
|
@@ -190,6 +236,7 @@ export async function runFeature(options) {
|
|
|
190
236
|
gitTasks,
|
|
191
237
|
...(executionError ? { error: executionError } : {}),
|
|
192
238
|
limitApplied,
|
|
239
|
+
controllerIdentity,
|
|
193
240
|
};
|
|
194
241
|
}
|
|
195
242
|
export function renderFeatureRun(result) {
|
|
@@ -271,6 +318,7 @@ function failedResult(input) {
|
|
|
271
318
|
batch: input.batch ?? null,
|
|
272
319
|
error: { code: input.code, message: input.message, evidence: input.evidence, ...(input.nextCommand ? { nextCommand: input.nextCommand } : {}) },
|
|
273
320
|
limitApplied: input.limitApplied,
|
|
321
|
+
controllerIdentity: input.controllerIdentity,
|
|
274
322
|
};
|
|
275
323
|
}
|
|
276
324
|
function expectedArtifactPaths(repoRoot, batchRunId) {
|
|
@@ -309,5 +357,9 @@ function preflightFailure(input) {
|
|
|
309
357
|
evidence: failed?.commands.flatMap((command) => [command.result.artifacts.resultPath]) ?? [],
|
|
310
358
|
nextCommand: `cd ${JSON.stringify(input.repoRoot)} && loop-agent doctor`,
|
|
311
359
|
expectedArtifacts: input.expectedArtifacts,
|
|
360
|
+
controllerIdentity: failed?.controllerIdentity ?? input.controllerIdentity,
|
|
312
361
|
});
|
|
313
362
|
}
|
|
363
|
+
function errorMessage(error) {
|
|
364
|
+
return error instanceof Error ? error.message : String(error);
|
|
365
|
+
}
|