@tea-agent/loop-agent 0.9.0 → 0.10.0-alpha.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 +8 -0
- package/CHANGELOG.md +92 -11
- package/README.md +69 -5
- package/dist/application/dag/args.js +4 -14
- package/dist/application/dag/generate-task-dag.js +32 -2
- package/dist/application/dag/run-dag.js +1 -27
- package/dist/application/dag/validate-dag.js +2 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +7 -11
- package/dist/cli/program.js +9 -21
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/dag-reconcile-run.js +118 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +60 -40
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- 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/manifest-types.js +33 -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 +353 -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 +243 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/delivery/final-verification.js +194 -0
- package/dist/worker/delivery/git-transaction.js +354 -0
- package/dist/worker/delivery/package.js +502 -0
- package/dist/worker/feature/decision-loader.js +68 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/next-action.js +74 -0
- package/dist/worker/feature/reducer.js +133 -0
- package/dist/worker/feature/review.js +502 -0
- package/dist/worker/feature/run.js +365 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +270 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/loop-agent-client.js +329 -126
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/read-model.js +282 -15
- package/dist/worker/observe/paths.js +17 -5
- package/dist/worker/observe/static/app.js +443 -61
- package/dist/worker/observe/static/index.html +3 -1
- package/dist/worker/observe/static/styles.css +85 -18
- package/dist/worker/pool/run-store.js +14 -2
- package/dist/worker/pool/validation.js +59 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/report/morning-report.js +41 -6
- package/dist/worker/run-task/run-task.js +23 -13
- package/dist/worker/runner/run-ready.js +89 -11
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/governance-profile.js +10 -0
- package/dist/workflows/dag/init-hybrid.js +601 -26
- package/dist/workflows/dag/lifecycle.js +146 -0
- package/dist/workflows/dag/node-execution.js +64 -7
- package/dist/workflows/dag/prompt.js +16 -0
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/runner.js +176 -119
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/skill-snapshot.js +527 -0
- package/dist/workflows/dag/types.js +45 -9
- package/dist/workflows/dag/validate.js +5 -8
- 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 +35 -16
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +134 -0
- package/docs/architecture/evolution.md +52 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +41 -15
- 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 +39 -13
- package/docs/development-principles.md +1 -1
- package/docs/exec-plans/active/README.md +2 -2
- package/docs/exec-plans/completed/README.md +14 -1
- package/docs/feature-workflow.md +44 -4
- package/docs/init-surface.manifest.json +63 -1
- package/docs/loop-agent-harness.md +65 -3
- package/docs/progress/README.md +27 -0
- package/docs/reports/README.md +74 -8
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +4 -2
- 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 +1 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -6
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
- package/docs/templates/backend-test-dag.json +213 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -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 +8 -5
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/init-evolution-review.md +4 -2
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +17 -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 +6 -11
- package/package.json +22 -44
- package/scripts/check-product-line-docs.sh +10 -3
- 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 +48 -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/init-capability-evolution/SKILL.md +1 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +28 -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 +13 -15
- 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/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
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { writeTextAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
6
|
+
import { buildDagNodePromptEnvelope } from "./prompt.js";
|
|
7
|
+
import { DEFAULT_SKILL_INSTRUCTION_MAX_CHARS, DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS, resolveDagSkillInstructions, } from "./skill-instructions.js";
|
|
8
|
+
import { resolveDagNodeSkills } from "./skills.js";
|
|
9
|
+
export const SKILL_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
10
|
+
export const SKILL_SNAPSHOT_RESOLVER_VERSION = 1;
|
|
11
|
+
export const SKILL_SNAPSHOT_REL_PATH = ".runtime/skill-snapshot.json";
|
|
12
|
+
const skillReferenceSchema = z.object({
|
|
13
|
+
name: z.string().min(1),
|
|
14
|
+
path: z.string().min(1),
|
|
15
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
16
|
+
bytes: z.number().int().nonnegative(),
|
|
17
|
+
truncated: z.boolean().optional(),
|
|
18
|
+
}).strict();
|
|
19
|
+
const unresolvedReferenceSchema = z.object({
|
|
20
|
+
name: z.string().min(1).optional(),
|
|
21
|
+
path: z.string().min(1),
|
|
22
|
+
reason: z.enum([
|
|
23
|
+
"invalid-path",
|
|
24
|
+
"missing",
|
|
25
|
+
"budget",
|
|
26
|
+
"read-error",
|
|
27
|
+
"cycle",
|
|
28
|
+
"already-resolved",
|
|
29
|
+
]),
|
|
30
|
+
}).strict();
|
|
31
|
+
const resolvedInstructionSchema = z.object({
|
|
32
|
+
name: z.string().min(1),
|
|
33
|
+
resolution: z.enum(["resolved", "missing", "error"]),
|
|
34
|
+
path: z.string().min(1).optional(),
|
|
35
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
36
|
+
bytes: z.number().int().nonnegative().optional(),
|
|
37
|
+
truncated: z.boolean().optional(),
|
|
38
|
+
searchedPaths: z.array(z.string().min(1)).optional(),
|
|
39
|
+
error: z.string().min(1).optional(),
|
|
40
|
+
injectionMode: z.enum(["bounded-inline", "name-only"]),
|
|
41
|
+
references: z.array(skillReferenceSchema).optional(),
|
|
42
|
+
unresolvedReferences: z.array(unresolvedReferenceSchema).optional(),
|
|
43
|
+
promptText: z.string(),
|
|
44
|
+
}).strict();
|
|
45
|
+
const skillSnapshotProfileSchema = z.object({
|
|
46
|
+
id: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
47
|
+
skills: z.array(z.string()),
|
|
48
|
+
includeLearnedPatterns: z.boolean(),
|
|
49
|
+
perSkillMaxChars: z.number().int().nonnegative(),
|
|
50
|
+
totalMaxChars: z.number().int().nonnegative(),
|
|
51
|
+
resolvedInstructions: z.array(resolvedInstructionSchema),
|
|
52
|
+
}).strict();
|
|
53
|
+
const skillSnapshotBindingSchema = z.discriminatedUnion("kind", [
|
|
54
|
+
z.object({
|
|
55
|
+
kind: z.literal("task"),
|
|
56
|
+
taskId: z.string().min(1),
|
|
57
|
+
profileId: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
58
|
+
}).strict(),
|
|
59
|
+
z.object({
|
|
60
|
+
kind: z.literal("dynamic-map-child"),
|
|
61
|
+
parentTaskId: z.string().min(1),
|
|
62
|
+
workflowNodeId: z.string().min(1),
|
|
63
|
+
profileId: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
64
|
+
}).strict(),
|
|
65
|
+
z.object({
|
|
66
|
+
kind: z.literal("dynamic-loop-body"),
|
|
67
|
+
parentTaskId: z.string().min(1),
|
|
68
|
+
workflowNodeId: z.string().min(1),
|
|
69
|
+
bodyTaskId: z.string().min(1),
|
|
70
|
+
profileId: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
71
|
+
}).strict(),
|
|
72
|
+
]);
|
|
73
|
+
const skillSnapshotSchema = z.object({
|
|
74
|
+
schemaVersion: z.literal(SKILL_SNAPSHOT_SCHEMA_VERSION),
|
|
75
|
+
resolverVersion: z.literal(SKILL_SNAPSHOT_RESOLVER_VERSION),
|
|
76
|
+
runId: z.string().min(1),
|
|
77
|
+
createdAt: z.string().datetime(),
|
|
78
|
+
mode: z.enum(["run-start", "legacy-resume-backfill"]),
|
|
79
|
+
initialTaskIds: z.array(z.string().min(1)),
|
|
80
|
+
bindings: z.array(skillSnapshotBindingSchema),
|
|
81
|
+
profiles: z.array(skillSnapshotProfileSchema),
|
|
82
|
+
}).strict();
|
|
83
|
+
const skillSnapshotRefSchema = z.object({
|
|
84
|
+
schemaVersion: z.literal(SKILL_SNAPSHOT_SCHEMA_VERSION),
|
|
85
|
+
path: z.literal(SKILL_SNAPSHOT_REL_PATH),
|
|
86
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
87
|
+
createdAt: z.string().datetime(),
|
|
88
|
+
mode: z.enum(["run-start", "legacy-resume-backfill"]),
|
|
89
|
+
}).strict();
|
|
90
|
+
export class DagSkillSnapshotIntegrityError extends Error {
|
|
91
|
+
constructor(message, options) {
|
|
92
|
+
super(message, options);
|
|
93
|
+
this.name = "DagSkillSnapshotIntegrityError";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function isDagSkillSnapshotIntegrityError(error) {
|
|
97
|
+
return error instanceof DagSkillSnapshotIntegrityError;
|
|
98
|
+
}
|
|
99
|
+
function sha256(content) {
|
|
100
|
+
return createHash("sha256").update(content).digest("hex");
|
|
101
|
+
}
|
|
102
|
+
function serializeSnapshot(snapshot) {
|
|
103
|
+
return `${JSON.stringify(snapshot, null, 2)}\n`;
|
|
104
|
+
}
|
|
105
|
+
function profileAnchor(input) {
|
|
106
|
+
return JSON.stringify({
|
|
107
|
+
resolverVersion: SKILL_SNAPSHOT_RESOLVER_VERSION,
|
|
108
|
+
skills: [...input.skills],
|
|
109
|
+
includeLearnedPatterns: input.includeLearnedPatterns,
|
|
110
|
+
perSkillMaxChars: input.perSkillMaxChars,
|
|
111
|
+
totalMaxChars: input.totalMaxChars,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function profileId(input) {
|
|
115
|
+
return `sha256:${sha256(profileAnchor(input))}`;
|
|
116
|
+
}
|
|
117
|
+
export function buildDagSkillSnapshotProfileRequest(spec, task) {
|
|
118
|
+
if (task.executor !== "pi")
|
|
119
|
+
return undefined;
|
|
120
|
+
const request = {
|
|
121
|
+
skills: [...resolveDagNodeSkills(spec, task)],
|
|
122
|
+
includeLearnedPatterns: task.role === "implementer",
|
|
123
|
+
perSkillMaxChars: DEFAULT_SKILL_INSTRUCTION_MAX_CHARS,
|
|
124
|
+
totalMaxChars: DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS,
|
|
125
|
+
};
|
|
126
|
+
return { id: profileId(request), ...request };
|
|
127
|
+
}
|
|
128
|
+
function isDynamicRuntimeTask(task) {
|
|
129
|
+
return Boolean(task.dynamicExpansion
|
|
130
|
+
|| task.dynamicReduction
|
|
131
|
+
|| task.dynamicCondition
|
|
132
|
+
|| task.dynamicLoopUntil);
|
|
133
|
+
}
|
|
134
|
+
function bindingKey(binding) {
|
|
135
|
+
switch (binding.kind) {
|
|
136
|
+
case "task":
|
|
137
|
+
return `task:${binding.taskId}`;
|
|
138
|
+
case "dynamic-map-child":
|
|
139
|
+
return `dynamic-map-child:${binding.parentTaskId}:${binding.workflowNodeId}`;
|
|
140
|
+
case "dynamic-loop-body":
|
|
141
|
+
return `dynamic-loop-body:${binding.parentTaskId}:${binding.workflowNodeId}:${binding.bodyTaskId}`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function collectDeclaredSkillSnapshotTargets(spec, initialTaskIds) {
|
|
145
|
+
const tasksById = new Map(spec.tasks.map((task) => [task.id, task]));
|
|
146
|
+
const targets = [];
|
|
147
|
+
for (const taskId of initialTaskIds) {
|
|
148
|
+
const task = tasksById.get(taskId);
|
|
149
|
+
if (!task) {
|
|
150
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot initial task is missing from run spec: ${taskId}`);
|
|
151
|
+
}
|
|
152
|
+
if (!isDynamicRuntimeTask(task)) {
|
|
153
|
+
targets.push({
|
|
154
|
+
binding: { kind: "task", taskId: task.id },
|
|
155
|
+
task,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (task.dynamicExpansion) {
|
|
159
|
+
targets.push({
|
|
160
|
+
binding: {
|
|
161
|
+
kind: "dynamic-map-child",
|
|
162
|
+
parentTaskId: task.id,
|
|
163
|
+
workflowNodeId: task.dynamicExpansion.workflowNodeId,
|
|
164
|
+
},
|
|
165
|
+
task: task.dynamicExpansion.childTask,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
for (const bodyTask of task.dynamicLoopUntil?.bodyTasks ?? []) {
|
|
169
|
+
targets.push({
|
|
170
|
+
binding: {
|
|
171
|
+
kind: "dynamic-loop-body",
|
|
172
|
+
parentTaskId: task.id,
|
|
173
|
+
workflowNodeId: task.dynamicLoopUntil.workflowNodeId,
|
|
174
|
+
bodyTaskId: bodyTask.id,
|
|
175
|
+
},
|
|
176
|
+
task: bodyTask,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return targets;
|
|
181
|
+
}
|
|
182
|
+
function matchesExpandedMapChild(taskId, parent) {
|
|
183
|
+
const expansion = parent.dynamicExpansion;
|
|
184
|
+
if (!expansion)
|
|
185
|
+
return false;
|
|
186
|
+
const prefix = `${expansion.childIdPrefix}-`;
|
|
187
|
+
if (!taskId.startsWith(prefix))
|
|
188
|
+
return false;
|
|
189
|
+
const suffix = taskId.slice(prefix.length);
|
|
190
|
+
if (!/^\d+$/.test(suffix))
|
|
191
|
+
return false;
|
|
192
|
+
const index = Number(suffix);
|
|
193
|
+
return (Number.isSafeInteger(index)
|
|
194
|
+
&& index >= 1
|
|
195
|
+
&& index <= Math.min(expansion.maxItems, expansion.maxExpandedNodes)
|
|
196
|
+
&& suffix === String(index).padStart(4, "0"));
|
|
197
|
+
}
|
|
198
|
+
function matchesExpandedLoopChild(taskId, parent, bodyTaskId) {
|
|
199
|
+
const loop = parent.dynamicLoopUntil;
|
|
200
|
+
if (!loop)
|
|
201
|
+
return false;
|
|
202
|
+
const prefix = `${parent.id}-r`;
|
|
203
|
+
const suffix = `-${bodyTaskId}`;
|
|
204
|
+
if (!taskId.startsWith(prefix) || !taskId.endsWith(suffix))
|
|
205
|
+
return false;
|
|
206
|
+
const iterationText = taskId.slice(prefix.length, -suffix.length);
|
|
207
|
+
if (!/^\d+$/.test(iterationText))
|
|
208
|
+
return false;
|
|
209
|
+
const iteration = Number(iterationText);
|
|
210
|
+
return (Number.isSafeInteger(iteration)
|
|
211
|
+
&& iteration >= 1
|
|
212
|
+
&& iteration <= loop.maxIterations
|
|
213
|
+
&& iterationText === String(iteration));
|
|
214
|
+
}
|
|
215
|
+
function resolveGeneratedTaskTarget(spec, task, initialTaskIds) {
|
|
216
|
+
const matches = [];
|
|
217
|
+
for (const parent of spec.tasks) {
|
|
218
|
+
if (!initialTaskIds.has(parent.id))
|
|
219
|
+
continue;
|
|
220
|
+
if (matchesExpandedMapChild(task.id, parent)) {
|
|
221
|
+
matches.push({
|
|
222
|
+
binding: {
|
|
223
|
+
kind: "dynamic-map-child",
|
|
224
|
+
parentTaskId: parent.id,
|
|
225
|
+
workflowNodeId: parent.dynamicExpansion.workflowNodeId,
|
|
226
|
+
},
|
|
227
|
+
task,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
for (const bodyTask of parent.dynamicLoopUntil?.bodyTasks ?? []) {
|
|
231
|
+
if (!matchesExpandedLoopChild(task.id, parent, bodyTask.id))
|
|
232
|
+
continue;
|
|
233
|
+
matches.push({
|
|
234
|
+
binding: {
|
|
235
|
+
kind: "dynamic-loop-body",
|
|
236
|
+
parentTaskId: parent.id,
|
|
237
|
+
workflowNodeId: parent.dynamicLoopUntil.workflowNodeId,
|
|
238
|
+
bodyTaskId: bodyTask.id,
|
|
239
|
+
},
|
|
240
|
+
task,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (matches.length > 1) {
|
|
245
|
+
throw new DagSkillSnapshotIntegrityError(`dynamic task ${task.id} matches multiple skill snapshot bindings`);
|
|
246
|
+
}
|
|
247
|
+
return matches[0];
|
|
248
|
+
}
|
|
249
|
+
export function collectDagSkillSnapshotBindings(spec, initialTaskIds = spec.tasks.map((task) => task.id)) {
|
|
250
|
+
const bindings = [];
|
|
251
|
+
for (const target of collectDeclaredSkillSnapshotTargets(spec, initialTaskIds)) {
|
|
252
|
+
const request = buildDagSkillSnapshotProfileRequest(spec, target.task);
|
|
253
|
+
if (!request)
|
|
254
|
+
continue;
|
|
255
|
+
bindings.push({ ...target.binding, profileId: request.id });
|
|
256
|
+
}
|
|
257
|
+
return bindings;
|
|
258
|
+
}
|
|
259
|
+
export function collectDagSkillSnapshotProfileRequests(spec, initialTaskIds = spec.tasks.map((task) => task.id)) {
|
|
260
|
+
const profiles = new Map();
|
|
261
|
+
for (const target of collectDeclaredSkillSnapshotTargets(spec, initialTaskIds)) {
|
|
262
|
+
const profile = buildDagSkillSnapshotProfileRequest(spec, target.task);
|
|
263
|
+
if (profile && !profiles.has(profile.id))
|
|
264
|
+
profiles.set(profile.id, profile);
|
|
265
|
+
}
|
|
266
|
+
return [...profiles.values()];
|
|
267
|
+
}
|
|
268
|
+
function validateResolvedInstructionOrder(profile) {
|
|
269
|
+
const primaryNames = profile.resolvedInstructions
|
|
270
|
+
.slice(0, profile.skills.length)
|
|
271
|
+
.map((instruction) => instruction.name);
|
|
272
|
+
if (JSON.stringify(primaryNames) !== JSON.stringify(profile.skills)) {
|
|
273
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot profile ${profile.id} resolved instruction order does not match its requested skills`);
|
|
274
|
+
}
|
|
275
|
+
const extras = profile.resolvedInstructions.slice(profile.skills.length);
|
|
276
|
+
if (extras.length > 1
|
|
277
|
+
|| (extras.length === 1
|
|
278
|
+
&& (!profile.includeLearnedPatterns
|
|
279
|
+
|| !profile.skills.includes("loop-agent")
|
|
280
|
+
|| extras[0]?.name !== "loop-agent-learned"))) {
|
|
281
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot profile ${profile.id} contains unexpected resolved instructions`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function validateSnapshotSemantics(snapshot) {
|
|
285
|
+
if (new Set(snapshot.initialTaskIds).size !== snapshot.initialTaskIds.length) {
|
|
286
|
+
throw new DagSkillSnapshotIntegrityError("skill snapshot contains duplicate initial task ids");
|
|
287
|
+
}
|
|
288
|
+
const seen = new Set();
|
|
289
|
+
for (const profile of snapshot.profiles) {
|
|
290
|
+
if (profile.id !== profileId(profile)) {
|
|
291
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot profile id mismatch: ${profile.id}`);
|
|
292
|
+
}
|
|
293
|
+
if (seen.has(profile.id)) {
|
|
294
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot contains duplicate profile: ${profile.id}`);
|
|
295
|
+
}
|
|
296
|
+
seen.add(profile.id);
|
|
297
|
+
validateResolvedInstructionOrder(profile);
|
|
298
|
+
}
|
|
299
|
+
const bindingKeys = new Set();
|
|
300
|
+
const referencedProfiles = new Set();
|
|
301
|
+
for (const binding of snapshot.bindings) {
|
|
302
|
+
const key = bindingKey(binding);
|
|
303
|
+
if (bindingKeys.has(key)) {
|
|
304
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot contains duplicate binding: ${key}`);
|
|
305
|
+
}
|
|
306
|
+
if (!seen.has(binding.profileId)) {
|
|
307
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot binding ${key} references missing profile ${binding.profileId}`);
|
|
308
|
+
}
|
|
309
|
+
bindingKeys.add(key);
|
|
310
|
+
referencedProfiles.add(binding.profileId);
|
|
311
|
+
}
|
|
312
|
+
for (const profile of snapshot.profiles) {
|
|
313
|
+
if (!referencedProfiles.has(profile.id)) {
|
|
314
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot contains unbound profile: ${profile.id}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function parseSnapshot(value) {
|
|
319
|
+
try {
|
|
320
|
+
const snapshot = skillSnapshotSchema.parse(value);
|
|
321
|
+
validateSnapshotSemantics(snapshot);
|
|
322
|
+
return snapshot;
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (isDagSkillSnapshotIntegrityError(error))
|
|
326
|
+
throw error;
|
|
327
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot schema validation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
export async function createSkillSnapshot(input) {
|
|
331
|
+
const initialTaskIds = input.initialTaskIds
|
|
332
|
+
?? input.spec.tasks.map((task) => task.id);
|
|
333
|
+
const profiles = [];
|
|
334
|
+
for (const request of collectDagSkillSnapshotProfileRequests(input.spec, initialTaskIds)) {
|
|
335
|
+
const resolvedInstructions = await resolveDagSkillInstructions(request.skills, {
|
|
336
|
+
cwd: input.cwd,
|
|
337
|
+
includeLearnedPatterns: request.includeLearnedPatterns,
|
|
338
|
+
perSkillMaxChars: request.perSkillMaxChars,
|
|
339
|
+
totalMaxChars: request.totalMaxChars,
|
|
340
|
+
});
|
|
341
|
+
profiles.push({ ...request, resolvedInstructions });
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
schemaVersion: SKILL_SNAPSHOT_SCHEMA_VERSION,
|
|
345
|
+
resolverVersion: SKILL_SNAPSHOT_RESOLVER_VERSION,
|
|
346
|
+
runId: input.runId,
|
|
347
|
+
createdAt: (input.now ?? new Date()).toISOString(),
|
|
348
|
+
mode: input.mode,
|
|
349
|
+
initialTaskIds,
|
|
350
|
+
bindings: collectDagSkillSnapshotBindings(input.spec, initialTaskIds),
|
|
351
|
+
profiles,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
export async function writeSkillSnapshot(runDir, snapshotValue) {
|
|
355
|
+
const snapshot = parseSnapshot(snapshotValue);
|
|
356
|
+
const raw = serializeSnapshot(snapshot);
|
|
357
|
+
await writeTextAtomic(path.join(runDir, ...SKILL_SNAPSHOT_REL_PATH.split("/")), raw);
|
|
358
|
+
return {
|
|
359
|
+
schemaVersion: SKILL_SNAPSHOT_SCHEMA_VERSION,
|
|
360
|
+
path: SKILL_SNAPSHOT_REL_PATH,
|
|
361
|
+
sha256: sha256(raw),
|
|
362
|
+
createdAt: snapshot.createdAt,
|
|
363
|
+
mode: snapshot.mode,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
export async function readSkillSnapshot(runDir, refValue, options) {
|
|
367
|
+
let ref;
|
|
368
|
+
try {
|
|
369
|
+
ref = skillSnapshotRefSchema.parse(refValue);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot ref is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
373
|
+
}
|
|
374
|
+
const snapshotPath = path.join(runDir, ...ref.path.split("/"));
|
|
375
|
+
let raw;
|
|
376
|
+
try {
|
|
377
|
+
raw = await readFile(snapshotPath);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot missing or unreadable at ${ref.path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
381
|
+
}
|
|
382
|
+
const actualHash = sha256(raw);
|
|
383
|
+
if (actualHash !== ref.sha256) {
|
|
384
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot integrity check failed at ${ref.path}: expected sha256 ${ref.sha256}, got ${actualHash}`);
|
|
385
|
+
}
|
|
386
|
+
let decoded;
|
|
387
|
+
try {
|
|
388
|
+
decoded = JSON.parse(raw.toString("utf-8"));
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
392
|
+
}
|
|
393
|
+
const snapshot = parseSnapshot(decoded);
|
|
394
|
+
if (snapshot.createdAt !== ref.createdAt || snapshot.mode !== ref.mode) {
|
|
395
|
+
throw new DagSkillSnapshotIntegrityError("skill snapshot ref metadata does not match the snapshot artifact");
|
|
396
|
+
}
|
|
397
|
+
if (options?.expectedRunId && snapshot.runId !== options.expectedRunId) {
|
|
398
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot run ownership mismatch: expected ${options.expectedRunId}, got ${snapshot.runId}`);
|
|
399
|
+
}
|
|
400
|
+
return snapshot;
|
|
401
|
+
}
|
|
402
|
+
export async function prepareSkillSnapshotForContinuation(input) {
|
|
403
|
+
if (input.ref) {
|
|
404
|
+
const snapshot = await readSkillSnapshot(input.runDir, input.ref, {
|
|
405
|
+
expectedRunId: input.runId,
|
|
406
|
+
});
|
|
407
|
+
assertSkillSnapshotCoversSpec(snapshot, input.spec, input.state);
|
|
408
|
+
return { ref: input.ref, backfilled: false };
|
|
409
|
+
}
|
|
410
|
+
const snapshotPath = path.join(input.runDir, ...SKILL_SNAPSHOT_REL_PATH.split("/"));
|
|
411
|
+
try {
|
|
412
|
+
await readFile(snapshotPath);
|
|
413
|
+
throw new DagSkillSnapshotIntegrityError("skill snapshot artifact exists without a state ref; refusing legacy backfill");
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
if (isDagSkillSnapshotIntegrityError(error))
|
|
417
|
+
throw error;
|
|
418
|
+
if (!(error && typeof error === "object" && "code" in error
|
|
419
|
+
&& error.code === "ENOENT")) {
|
|
420
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot artifact is unreadable during legacy backfill: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const snapshot = await createSkillSnapshot({
|
|
424
|
+
runId: input.runId,
|
|
425
|
+
spec: input.spec,
|
|
426
|
+
cwd: input.cwd,
|
|
427
|
+
mode: "legacy-resume-backfill",
|
|
428
|
+
});
|
|
429
|
+
assertSkillSnapshotCoversSpec(snapshot, input.spec, input.state);
|
|
430
|
+
return {
|
|
431
|
+
ref: await writeSkillSnapshot(input.runDir, snapshot),
|
|
432
|
+
backfilled: true,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
export function assertSkillSnapshotCoversSpec(snapshot, spec, state) {
|
|
436
|
+
const currentInitialIds = spec.tasks
|
|
437
|
+
.slice(0, snapshot.initialTaskIds.length)
|
|
438
|
+
.map((task) => task.id);
|
|
439
|
+
if (JSON.stringify(currentInitialIds) !== JSON.stringify(snapshot.initialTaskIds)) {
|
|
440
|
+
throw new DagSkillSnapshotIntegrityError("skill snapshot initial task scope does not match the current run spec");
|
|
441
|
+
}
|
|
442
|
+
const expectedBindings = collectDagSkillSnapshotBindings(spec, snapshot.initialTaskIds);
|
|
443
|
+
const actualByKey = new Map(snapshot.bindings.map((binding) => [bindingKey(binding), binding]));
|
|
444
|
+
const expectedByKey = new Map(expectedBindings.map((binding) => [bindingKey(binding), binding]));
|
|
445
|
+
for (const [key, expected] of expectedByKey) {
|
|
446
|
+
const actual = actualByKey.get(key);
|
|
447
|
+
if (!actual) {
|
|
448
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot binding mismatch: missing ${key}`);
|
|
449
|
+
}
|
|
450
|
+
if (actual.profileId !== expected.profileId) {
|
|
451
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot binding mismatch for ${key}: expected ${actual.profileId}, got ${expected.profileId}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (const key of actualByKey.keys()) {
|
|
455
|
+
if (!expectedByKey.has(key)) {
|
|
456
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot binding mismatch: unexpected ${key}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const initialIds = new Set(snapshot.initialTaskIds);
|
|
460
|
+
for (const task of spec.tasks.slice(snapshot.initialTaskIds.length)) {
|
|
461
|
+
if (!state
|
|
462
|
+
|| !state.nodes[task.id]
|
|
463
|
+
|| !state.ranks.some((rank) => rank.includes(task.id))) {
|
|
464
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot cannot prove runtime origin for appended task: ${task.id}`);
|
|
465
|
+
}
|
|
466
|
+
const generated = resolveGeneratedTaskTarget(spec, task, initialIds);
|
|
467
|
+
if (!generated) {
|
|
468
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot run spec contains an unexpected task: ${task.id}`);
|
|
469
|
+
}
|
|
470
|
+
const key = bindingKey(generated.binding);
|
|
471
|
+
const binding = actualByKey.get(key);
|
|
472
|
+
const request = buildDagSkillSnapshotProfileRequest(spec, generated.task);
|
|
473
|
+
if (!request) {
|
|
474
|
+
if (binding) {
|
|
475
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot generated task binding mismatch for ${task.id}`);
|
|
476
|
+
}
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (!binding || binding.profileId !== request.id) {
|
|
480
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot generated task binding mismatch for ${task.id}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
export function resolveNodeSkillsFromSnapshot(snapshot, spec, task) {
|
|
485
|
+
const request = buildDagSkillSnapshotProfileRequest(spec, task);
|
|
486
|
+
if (!request)
|
|
487
|
+
return [];
|
|
488
|
+
const initialIds = new Set(snapshot.initialTaskIds);
|
|
489
|
+
const generated = initialIds.has(task.id)
|
|
490
|
+
? undefined
|
|
491
|
+
: resolveGeneratedTaskTarget(spec, task, initialIds);
|
|
492
|
+
const targetBinding = generated?.binding ?? {
|
|
493
|
+
kind: "task",
|
|
494
|
+
taskId: task.id,
|
|
495
|
+
};
|
|
496
|
+
const key = bindingKey(targetBinding);
|
|
497
|
+
const binding = snapshot.bindings.find((candidate) => bindingKey(candidate) === key);
|
|
498
|
+
if (!binding || binding.profileId !== request.id) {
|
|
499
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot binding mismatch for ${key}`);
|
|
500
|
+
}
|
|
501
|
+
const profile = snapshot.profiles.find((candidate) => candidate.id === binding.profileId);
|
|
502
|
+
if (!profile) {
|
|
503
|
+
throw new DagSkillSnapshotIntegrityError(`skill snapshot profile mismatch: missing ${binding.profileId}`);
|
|
504
|
+
}
|
|
505
|
+
return profile.resolvedInstructions;
|
|
506
|
+
}
|
|
507
|
+
function stripPromptText(instruction) {
|
|
508
|
+
const { promptText: _promptText, ...metadata } = instruction;
|
|
509
|
+
return metadata;
|
|
510
|
+
}
|
|
511
|
+
export function buildNodePromptFromSnapshot(input) {
|
|
512
|
+
const skillNames = resolveDagNodeSkills(input.spec, input.task);
|
|
513
|
+
const resolvedSkillInstructions = resolveNodeSkillsFromSnapshot(input.snapshot, input.spec, input.task);
|
|
514
|
+
return {
|
|
515
|
+
prompt: buildDagNodePromptEnvelope({
|
|
516
|
+
spec: input.spec,
|
|
517
|
+
task: input.task,
|
|
518
|
+
upstream: input.upstream,
|
|
519
|
+
resolvedSkills: skillNames,
|
|
520
|
+
resolvedSkillInstructions,
|
|
521
|
+
}),
|
|
522
|
+
resolvedSkills: resolvedSkillInstructions.map(stripPromptText),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
export function hashSkillSnapshot(snapshot) {
|
|
526
|
+
return sha256(serializeSnapshot(snapshot));
|
|
527
|
+
}
|
|
@@ -2,11 +2,12 @@ import { z } from "zod";
|
|
|
2
2
|
import { assertDagPromptSourceRule } from "./prompt-source.js";
|
|
3
3
|
export const dagComplexitySchema = z.enum(["HIGH", "MED", "LOW"]);
|
|
4
4
|
export const dagNodeExecutorSchema = z.enum([
|
|
5
|
-
"cursor",
|
|
6
5
|
"pi",
|
|
7
6
|
"shell",
|
|
8
7
|
"static",
|
|
9
8
|
]);
|
|
9
|
+
export const CURSOR_DAG_EXECUTOR_REMOVED_ERROR = 'executor "cursor" is no longer supported; regenerate the DAG with Pi-only writers (implement-pi / repair-pi)';
|
|
10
|
+
export const CURSOR_EXECUTOR_MODELS_REMOVED_ERROR = 'executorModels.cursor is no longer supported; use executorModels.pi only';
|
|
10
11
|
export const dagToolProfileSchema = z.enum(["read-only", "write"]);
|
|
11
12
|
export const dagShellPresetSchema = z.enum(["loop-agent-standard-verify"]);
|
|
12
13
|
export const dagVerifyQuotaSchema = z.enum(["1", "3", "full"]);
|
|
@@ -230,13 +231,16 @@ export const dagModelsSchema = z
|
|
|
230
231
|
.partial();
|
|
231
232
|
export const dagExecutorModelsSchema = z
|
|
232
233
|
.object({
|
|
233
|
-
cursor: dagModelsSchema.optional(),
|
|
234
234
|
pi: dagModelsSchema.optional(),
|
|
235
235
|
})
|
|
236
|
-
.
|
|
237
|
-
export const
|
|
236
|
+
.strict();
|
|
237
|
+
export const dagOutputLanguageSchema = z.enum(["zh-CN", "en"]);
|
|
238
|
+
export const DEFAULT_DAG_OUTPUT_LANGUAGE = "zh-CN";
|
|
239
|
+
export const dagSpecSchema = z
|
|
240
|
+
.object({
|
|
238
241
|
version: dagVersionSchema,
|
|
239
242
|
title: z.string().min(1),
|
|
243
|
+
outputLanguage: dagOutputLanguageSchema.optional(),
|
|
240
244
|
objective: z.string().optional(),
|
|
241
245
|
successCriteria: z.array(z.string()).optional(),
|
|
242
246
|
globalConstraints: z.array(z.string()).optional(),
|
|
@@ -246,13 +250,20 @@ export const dagSpecSchema = z.object({
|
|
|
246
250
|
skillsByRole: z.record(z.string(), z.array(z.string())).optional(),
|
|
247
251
|
executorModels: dagExecutorModelsSchema.optional(),
|
|
248
252
|
tasks: z.array(dagTaskSchema).min(1),
|
|
253
|
+
})
|
|
254
|
+
.superRefine((spec, ctx) => {
|
|
255
|
+
// Catch raw cursor keys that Zod .strict() on nested objects already rejects when parsed
|
|
256
|
+
// via parseDagSpec; this refine covers typed object construction paths.
|
|
257
|
+
const models = spec.executorModels;
|
|
258
|
+
if (models && "cursor" in models) {
|
|
259
|
+
ctx.addIssue({
|
|
260
|
+
code: z.ZodIssueCode.custom,
|
|
261
|
+
message: CURSOR_EXECUTOR_MODELS_REMOVED_ERROR,
|
|
262
|
+
path: ["executorModels", "cursor"],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
249
265
|
});
|
|
250
266
|
export const DEFAULT_DAG_EXECUTOR_MODELS = {
|
|
251
|
-
cursor: {
|
|
252
|
-
LOW: "composer-2.5",
|
|
253
|
-
MED: "composer-2.5",
|
|
254
|
-
HIGH: "gpt-5.5",
|
|
255
|
-
},
|
|
256
267
|
pi: {
|
|
257
268
|
LOW: "gpt-5.3-codex-spark",
|
|
258
269
|
MED: "glm-5.2",
|
|
@@ -266,6 +277,31 @@ export function parseDagSpec(raw) {
|
|
|
266
277
|
if (typeof raw === "object" && raw !== null && "models" in raw) {
|
|
267
278
|
throw new Error(LEGACY_TOP_LEVEL_MODELS_ERROR);
|
|
268
279
|
}
|
|
280
|
+
if (typeof raw === "object" && raw !== null) {
|
|
281
|
+
const obj = raw;
|
|
282
|
+
const executorModels = obj.executorModels;
|
|
283
|
+
if (typeof executorModels === "object" &&
|
|
284
|
+
executorModels !== null &&
|
|
285
|
+
"cursor" in executorModels) {
|
|
286
|
+
throw new Error(CURSOR_EXECUTOR_MODELS_REMOVED_ERROR);
|
|
287
|
+
}
|
|
288
|
+
const tasks = obj.tasks;
|
|
289
|
+
if (Array.isArray(tasks)) {
|
|
290
|
+
for (const task of tasks) {
|
|
291
|
+
if (typeof task === "object" &&
|
|
292
|
+
task !== null &&
|
|
293
|
+
task.executor === "cursor") {
|
|
294
|
+
throw new Error(CURSOR_DAG_EXECUTOR_REMOVED_ERROR);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const defaults = obj.defaults;
|
|
299
|
+
if (typeof defaults === "object" &&
|
|
300
|
+
defaults !== null &&
|
|
301
|
+
defaults.executor === "cursor") {
|
|
302
|
+
throw new Error(CURSOR_DAG_EXECUTOR_REMOVED_ERROR);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
269
305
|
return dagSpecSchema.parse(raw);
|
|
270
306
|
}
|
|
271
307
|
export function resolveModelForTask(task, executorModels) {
|
|
@@ -319,20 +319,17 @@ function validateSameRankWriteSetConflicts(spec, ranks, issues) {
|
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
|
-
function
|
|
323
|
-
return
|
|
324
|
-
(task?.executor === "pi" && task.toolProfile === "write"));
|
|
322
|
+
function isAgentTask(task) {
|
|
323
|
+
return task?.executor === "pi";
|
|
325
324
|
}
|
|
326
325
|
function validateSameRankAgentAttributionRisks(spec, ranks, issues) {
|
|
327
326
|
const taskById = new Map(spec.tasks.map((task) => [task.id, task]));
|
|
328
327
|
for (const rank of ranks) {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
.filter(isWriteGuardedAgentTask);
|
|
332
|
-
const hasExclusiveWriter = guardedTasks.some((task) => resolveWritePolicy(task, spec) === "exclusive");
|
|
328
|
+
const agentTasks = rank.map((id) => taskById.get(id)).filter(isAgentTask);
|
|
329
|
+
const hasExclusiveWriter = agentTasks.some((task) => resolveWritePolicy(task, spec) === "exclusive");
|
|
333
330
|
if (!hasExclusiveWriter)
|
|
334
331
|
continue;
|
|
335
|
-
for (const task of
|
|
332
|
+
for (const task of agentTasks) {
|
|
336
333
|
const policy = resolveWritePolicy(task, spec) ?? "read-only";
|
|
337
334
|
if (policy === "read-only" || policy === "none") {
|
|
338
335
|
issues.push({
|
|
@@ -64,8 +64,6 @@ export async function runLoopDagAction(repoRoot, taskId, options = {}) {
|
|
|
64
64
|
if (options.maxConcurrent !== undefined) {
|
|
65
65
|
runArgs.push("--max-concurrent", String(options.maxConcurrent));
|
|
66
66
|
}
|
|
67
|
-
if (options.noCursor)
|
|
68
|
-
runArgs.push("--no-cursor");
|
|
69
67
|
const runSummary = asRecord(await runner("run-dag", runArgs), "run-dag");
|
|
70
68
|
runId = stringField(runSummary, "runId", "run-dag");
|
|
71
69
|
const report = await runner("dag-report", [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { repoRelativePath, toPosixPath } from "../../../shared/path-refs.js";
|
|
3
3
|
import { workflowReportSchema } from "../../dynamic/artifacts.js";
|
|
4
|
-
import { normalizePattern } from "../policy/
|
|
4
|
+
import { normalizePattern } from "../policy/path-patterns.js";
|
|
5
5
|
export const SUMMARY_LIMIT = 1200;
|
|
6
6
|
export function truncateSummary(text) {
|
|
7
7
|
const normalized = text.trim();
|