@tea-agent/loop-agent 0.8.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 +10 -0
- package/CHANGELOG.md +101 -1
- package/README.md +69 -5
- package/dist/application/dag/args.js +13 -16
- 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 +75 -9
- 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/shared/reference-context.js +48 -22
- 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 +30 -13
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +2 -3
- 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 +345 -101
- 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/routes.js +78 -20
- package/dist/worker/observe/server.js +8 -6
- package/dist/worker/observe/static/app.js +1045 -177
- package/dist/worker/observe/static/index.html +70 -43
- package/dist/worker/observe/static/styles.css +553 -610
- 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 -12
- 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 +21 -0
- 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 -5
- 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/scripts/check-task-pool-root.sh +1 -1
- 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/loop-agent/references/verification-and-failure-handling.md +8 -3
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, mkdir, readFile, readdir, rename, } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { hostname as localHostname } from "node:os";
|
|
3
4
|
import { writeJsonAtomic, } from "../../infrastructure/harness/atomic-write.js";
|
|
4
5
|
import { parseDagSpec } from "./types.js";
|
|
5
6
|
import { normalizeDagFailureCategory, } from "./failure-category.js";
|
|
@@ -161,10 +162,98 @@ export async function humanApprovalArtifactExists(runDir, nodeId) {
|
|
|
161
162
|
return false;
|
|
162
163
|
}
|
|
163
164
|
}
|
|
165
|
+
export function assessDagRunLiveness(input) {
|
|
166
|
+
if (input.state.status !== "running")
|
|
167
|
+
return { status: "unknown" };
|
|
168
|
+
const runner = input.state.runner;
|
|
169
|
+
if (!runner)
|
|
170
|
+
return { status: "unknown" };
|
|
171
|
+
if (runner.hostname !== (input.hostname ?? localHostname()))
|
|
172
|
+
return { status: "unknown-host" };
|
|
173
|
+
const isAlive = input.isProcessAlive ?? ((pid) => {
|
|
174
|
+
try {
|
|
175
|
+
process.kill(pid, 0);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
if (!isAlive(runner.pid))
|
|
183
|
+
return { status: "orphaned", runnerAlive: false };
|
|
184
|
+
const heartbeatMs = Date.parse(runner.heartbeatAt);
|
|
185
|
+
const nowMs = (input.now ?? new Date()).getTime();
|
|
186
|
+
if (!Number.isNaN(heartbeatMs) && nowMs - heartbeatMs > (input.staleThresholdMs ?? 90_000)) {
|
|
187
|
+
return { status: "stale", runnerAlive: true };
|
|
188
|
+
}
|
|
189
|
+
const activeNode = Object.values(input.state.nodes).find((node) => node.status === "RUNNING");
|
|
190
|
+
const nodeActivityMs = Date.parse(activeNode?.lastActivityAt ?? activeNode?.startedAt ?? "");
|
|
191
|
+
if (!Number.isNaN(nodeActivityMs) && nowMs - nodeActivityMs > (input.nodeQuietThresholdMs ?? 300_000)) {
|
|
192
|
+
return { status: "node-quiet", runnerAlive: true };
|
|
193
|
+
}
|
|
194
|
+
return { status: "active", runnerAlive: true };
|
|
195
|
+
}
|
|
196
|
+
export function deriveDagRunEffectiveStatus(input) {
|
|
197
|
+
if (input.state.status === "superseded")
|
|
198
|
+
return "superseded";
|
|
199
|
+
if (input.state.status === "abandoned")
|
|
200
|
+
return "abandoned";
|
|
201
|
+
if (input.lifecycle === "paused")
|
|
202
|
+
return "paused";
|
|
203
|
+
if (input.lifecycle === "completed") {
|
|
204
|
+
return input.state.status === "finished" ? "finished" : "failed";
|
|
205
|
+
}
|
|
206
|
+
if (input.state.status === "pending")
|
|
207
|
+
return "pending";
|
|
208
|
+
if (isTerminalDagRunStatus(input.state.status)) {
|
|
209
|
+
return input.state.status === "finished" ? "finished" : "failed";
|
|
210
|
+
}
|
|
211
|
+
if (input.liveness === "orphaned" || input.liveness === "stale")
|
|
212
|
+
return "interrupted";
|
|
213
|
+
if (input.liveness === "node-quiet")
|
|
214
|
+
return "running-quiet";
|
|
215
|
+
if (input.liveness === "unknown-host")
|
|
216
|
+
return "remote-unknown";
|
|
217
|
+
if (input.liveness === "active")
|
|
218
|
+
return "running";
|
|
219
|
+
return "unknown";
|
|
220
|
+
}
|
|
221
|
+
export function assessDagRunRecoveryEligibility(input) {
|
|
222
|
+
const reasons = [];
|
|
223
|
+
const canResume = input.lifecycle === "active"
|
|
224
|
+
&& input.state.status === "running"
|
|
225
|
+
&& Boolean(input.state.humanDecisionNodeId)
|
|
226
|
+
&& Boolean(input.hasHumanApproval);
|
|
227
|
+
if (!canResume)
|
|
228
|
+
reasons.push("standard-resume-preconditions-not-met");
|
|
229
|
+
let canReconcile = true;
|
|
230
|
+
if (input.lifecycle === "completed" || isTerminalDagRunStatus(input.state.status)) {
|
|
231
|
+
canReconcile = false;
|
|
232
|
+
reasons.push("run-already-terminal");
|
|
233
|
+
}
|
|
234
|
+
if (input.lifecycle === "active"
|
|
235
|
+
&& ["active", "node-quiet", "stale", "unknown-host", "unknown"].includes(input.liveness)) {
|
|
236
|
+
canReconcile = false;
|
|
237
|
+
reasons.push("runner-not-proven-dead-or-stopped");
|
|
238
|
+
}
|
|
239
|
+
if (input.lifecycle === "paused" && input.state.status !== "paused") {
|
|
240
|
+
reasons.push("lifecycle-status-mismatch");
|
|
241
|
+
}
|
|
242
|
+
if (!input.state.runner)
|
|
243
|
+
reasons.push("missing-runner-metadata");
|
|
244
|
+
return {
|
|
245
|
+
canResume,
|
|
246
|
+
canReconcile,
|
|
247
|
+
allowedActions: canReconcile ? ["supersede", "abandon"] : [],
|
|
248
|
+
reasons: [...new Set(reasons)],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
164
251
|
export const TERMINAL_RUN_STATUSES = new Set([
|
|
165
252
|
"finished",
|
|
166
253
|
"failed",
|
|
167
254
|
"partial_failed",
|
|
255
|
+
"superseded",
|
|
256
|
+
"abandoned",
|
|
168
257
|
]);
|
|
169
258
|
export function isTerminalDagRunStatus(status) {
|
|
170
259
|
return TERMINAL_RUN_STATUSES.has(status);
|
|
@@ -248,6 +337,31 @@ export async function detectDagRunHealthIssues(input) {
|
|
|
248
337
|
advisoryAction: "Use dag doctor; approve/reject/resume may fail until lifecycle facts are consistent.",
|
|
249
338
|
});
|
|
250
339
|
}
|
|
340
|
+
const liveness = assessDagRunLiveness({ state });
|
|
341
|
+
if (liveness.status === "orphaned") {
|
|
342
|
+
issues.push({
|
|
343
|
+
code: "runner-process-missing",
|
|
344
|
+
severity: "error",
|
|
345
|
+
message: `Runner PID ${state.runner?.pid} is not alive on host ${state.runner?.hostname}`,
|
|
346
|
+
advisoryAction: "Treat this run as orphaned; inspect artifacts and start a new run instead of resuming it.",
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else if (liveness.status === "stale") {
|
|
350
|
+
issues.push({
|
|
351
|
+
code: "runner-heartbeat-stale",
|
|
352
|
+
severity: "warning",
|
|
353
|
+
message: `Runner heartbeat is stale since ${state.runner?.heartbeatAt}`,
|
|
354
|
+
advisoryAction: "Inspect node artifacts and process liveness before stopping or retrying.",
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
else if (liveness.status === "node-quiet") {
|
|
358
|
+
issues.push({
|
|
359
|
+
code: "node-activity-quiet",
|
|
360
|
+
severity: "warning",
|
|
361
|
+
message: "Runner heartbeat is fresh but the current RUNNING node has produced no state activity for more than 5 minutes",
|
|
362
|
+
advisoryAction: "Inspect the node session events and executor logs before deciding whether to wait or abort.",
|
|
363
|
+
});
|
|
364
|
+
}
|
|
251
365
|
if (lifecycle === "active" &&
|
|
252
366
|
state.status === "running" &&
|
|
253
367
|
state.humanDecisionNodeId) {
|
|
@@ -340,6 +454,14 @@ export async function buildDagOperatorRunSummary(entry) {
|
|
|
340
454
|
pendingNodes: [],
|
|
341
455
|
finishedNodes: [],
|
|
342
456
|
healthIssues,
|
|
457
|
+
effectiveStatus: entry.lifecycle === "paused" ? "paused" : "unknown",
|
|
458
|
+
stateConsistent: false,
|
|
459
|
+
recoveryEligibility: {
|
|
460
|
+
canResume: false,
|
|
461
|
+
canReconcile: false,
|
|
462
|
+
allowedActions: [],
|
|
463
|
+
reasons: ["missing-state-json"],
|
|
464
|
+
},
|
|
343
465
|
nextRecommendedAction: deriveOperatorNextAction({
|
|
344
466
|
lifecycle: entry.lifecycle,
|
|
345
467
|
state: {
|
|
@@ -363,9 +485,22 @@ export async function buildDagOperatorRunSummary(entry) {
|
|
|
363
485
|
runDir: entry.runDir,
|
|
364
486
|
state,
|
|
365
487
|
});
|
|
488
|
+
const liveness = assessDagRunLiveness({ state });
|
|
366
489
|
const hasHumanApproval = state.humanDecisionNodeId
|
|
367
490
|
? await humanApprovalArtifactExists(entry.runDir, state.humanDecisionNodeId)
|
|
368
491
|
: false;
|
|
492
|
+
const effectiveStatus = deriveDagRunEffectiveStatus({
|
|
493
|
+
lifecycle: entry.lifecycle,
|
|
494
|
+
state,
|
|
495
|
+
liveness: liveness.status,
|
|
496
|
+
});
|
|
497
|
+
const stateConsistent = !healthIssues.some((issue) => ["lifecycle-status-mismatch", "non-terminal-in-completed"].includes(issue.code));
|
|
498
|
+
const recoveryEligibility = assessDagRunRecoveryEligibility({
|
|
499
|
+
lifecycle: entry.lifecycle,
|
|
500
|
+
state,
|
|
501
|
+
liveness: liveness.status,
|
|
502
|
+
hasHumanApproval,
|
|
503
|
+
});
|
|
369
504
|
return {
|
|
370
505
|
runId: state.runId,
|
|
371
506
|
title: state.title,
|
|
@@ -380,6 +515,10 @@ export async function buildDagOperatorRunSummary(entry) {
|
|
|
380
515
|
pendingNodes: listPendingNodeIds(state),
|
|
381
516
|
finishedNodes: listFinishedNodeIds(state),
|
|
382
517
|
healthIssues,
|
|
518
|
+
liveness: liveness.status,
|
|
519
|
+
effectiveStatus,
|
|
520
|
+
stateConsistent,
|
|
521
|
+
recoveryEligibility,
|
|
383
522
|
nextRecommendedAction: deriveOperatorNextAction({
|
|
384
523
|
lifecycle: entry.lifecycle,
|
|
385
524
|
state,
|
|
@@ -552,6 +691,13 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
|
|
|
552
691
|
"",
|
|
553
692
|
`- run id: ${runId}`,
|
|
554
693
|
`- lifecycle: ${located.lifecycle}`,
|
|
694
|
+
`- effective status: ${summary.effectiveStatus}`,
|
|
695
|
+
`- raw status: ${summary.status}`,
|
|
696
|
+
`- state consistent: ${summary.stateConsistent ? "yes" : "no"}`,
|
|
697
|
+
`- liveness: ${summary.liveness ?? "unknown"}`,
|
|
698
|
+
`- can resume: ${summary.recoveryEligibility.canResume ? "yes" : "no"}`,
|
|
699
|
+
`- can reconcile: ${summary.recoveryEligibility.canReconcile ? "yes" : "no"}`,
|
|
700
|
+
`- health issues: ${summary.healthIssues.length > 0 ? summary.healthIssues.map((issue) => `${issue.code}: ${issue.message}`).join("; ") : "none"}`,
|
|
555
701
|
`- failed node: ${failure.nodeId ?? "-"}`,
|
|
556
702
|
`- raw failure: ${rawFailureCategory ?? "-"}`,
|
|
557
703
|
`- normalized category: ${normalizedCategory}`,
|
|
@@ -3,6 +3,7 @@ import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHuman
|
|
|
3
3
|
import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
|
|
4
4
|
import { buildDagNodePromptEnvelope } from "./prompt.js";
|
|
5
5
|
import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
|
|
6
|
+
import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
6
7
|
import { resolveDagSkillInstructions, skillInstructionMetadata, } from "./skill-instructions.js";
|
|
7
8
|
import { resolveDagNodeSkills } from "./skills.js";
|
|
8
9
|
import { parseRepairArtifactFromText, validateRepairArtifactScope, } from "./repair-artifact.js";
|
|
@@ -17,7 +18,7 @@ export function buildNodePrompt(spec, task, upstream) {
|
|
|
17
18
|
}
|
|
18
19
|
export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd) {
|
|
19
20
|
const skillNames = resolveDagNodeSkills(spec, task);
|
|
20
|
-
const resolvedSkillInstructions = task.executor === "
|
|
21
|
+
const resolvedSkillInstructions = task.executor === "pi"
|
|
21
22
|
? await resolveDagSkillInstructions(skillNames, {
|
|
22
23
|
cwd,
|
|
23
24
|
includeLearnedPatterns: task.role === "implementer",
|
|
@@ -55,7 +56,7 @@ function assertRepairArtifactVerdictMatchesSupervisor(input) {
|
|
|
55
56
|
}
|
|
56
57
|
function findRepairTaskForGate(input) {
|
|
57
58
|
return Array.from(input.tasksById.values()).find((candidate) => candidate.depends_on.includes(input.gateTask.id) &&
|
|
58
|
-
candidate.id === "repair-
|
|
59
|
+
candidate.id === "repair-pi");
|
|
59
60
|
}
|
|
60
61
|
function parseSupervisorRepairArtifact(node) {
|
|
61
62
|
const text = `${node?.assistantText ?? ""}\n${node?.stdout ?? ""}`;
|
|
@@ -111,17 +112,54 @@ export async function executeDagNode(input) {
|
|
|
111
112
|
const { nodeId, tasksById, state, spec, cwd, runDir, executeNode } = input;
|
|
112
113
|
const task = tasksById.get(nodeId);
|
|
113
114
|
const node = state.nodes[nodeId];
|
|
115
|
+
const failSkillSnapshot = async (error) => {
|
|
116
|
+
const failedAt = new Date().toISOString();
|
|
117
|
+
node.startedAt ??= failedAt;
|
|
118
|
+
node.status = "ERROR";
|
|
119
|
+
node.stderr = error instanceof Error ? error.message : String(error);
|
|
120
|
+
node.failureCategory = "skill-snapshot-integrity";
|
|
121
|
+
node.finishedAt = failedAt;
|
|
122
|
+
node.lastActivityAt = node.finishedAt;
|
|
123
|
+
node.durationMs = Math.max(0, Date.now() - new Date(node.startedAt).getTime());
|
|
124
|
+
state.nodes[nodeId].nodeRecordPath = path.join(runDir, `${nodeId}.json`);
|
|
125
|
+
await writeNodeRecord(runDir, nodeId, state.nodes[nodeId]);
|
|
126
|
+
await input.persistState();
|
|
127
|
+
await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
|
|
128
|
+
};
|
|
129
|
+
const isDynamicTask = Boolean(task.dynamicExpansion
|
|
130
|
+
|| task.dynamicReduction
|
|
131
|
+
|| task.dynamicCondition
|
|
132
|
+
|| task.dynamicLoopUntil);
|
|
133
|
+
let snapshotPrompt;
|
|
134
|
+
if (state.skillSnapshotRef) {
|
|
135
|
+
try {
|
|
136
|
+
const skillSnapshot = await readSkillSnapshot(runDir, state.skillSnapshotRef, {
|
|
137
|
+
expectedRunId: state.runId,
|
|
138
|
+
});
|
|
139
|
+
assertSkillSnapshotCoversSpec(skillSnapshot, spec, state);
|
|
140
|
+
if (!isDynamicTask) {
|
|
141
|
+
snapshotPrompt = buildNodePromptFromSnapshot({
|
|
142
|
+
spec,
|
|
143
|
+
task,
|
|
144
|
+
upstream: state.nodes,
|
|
145
|
+
snapshot: skillSnapshot,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
await failSkillSnapshot(error);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
114
154
|
node.status = "RUNNING";
|
|
115
155
|
node.startedAt = new Date().toISOString();
|
|
156
|
+
node.lastActivityAt = node.startedAt;
|
|
116
157
|
if (task.shell?.verifyEvidence) {
|
|
117
158
|
node.verifyEvidence = task.shell.verifyEvidence;
|
|
118
159
|
}
|
|
119
160
|
await input.persistState();
|
|
120
161
|
await notifyNodeObserver(input.observer, "onNodeStart", nodeId, state);
|
|
121
|
-
if (
|
|
122
|
-
task.dynamicReduction ||
|
|
123
|
-
task.dynamicCondition ||
|
|
124
|
-
task.dynamicLoopUntil) {
|
|
162
|
+
if (isDynamicTask) {
|
|
125
163
|
const started = Date.now();
|
|
126
164
|
try {
|
|
127
165
|
const result = await input.executeDynamicNode({
|
|
@@ -140,6 +178,7 @@ export async function executeDagNode(input) {
|
|
|
140
178
|
node.stderr = result.stderr;
|
|
141
179
|
node.failureCategory = result.failureCategory;
|
|
142
180
|
node.finishedAt = new Date().toISOString();
|
|
181
|
+
node.lastActivityAt = node.finishedAt;
|
|
143
182
|
node.status = result.ok ? "FINISHED" : "ERROR";
|
|
144
183
|
}
|
|
145
184
|
catch (error) {
|
|
@@ -163,7 +202,24 @@ export async function executeDagNode(input) {
|
|
|
163
202
|
await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
|
|
164
203
|
return;
|
|
165
204
|
}
|
|
166
|
-
|
|
205
|
+
let prompt;
|
|
206
|
+
let resolvedSkills;
|
|
207
|
+
try {
|
|
208
|
+
if (snapshotPrompt) {
|
|
209
|
+
prompt = snapshotPrompt.prompt;
|
|
210
|
+
resolvedSkills = snapshotPrompt.resolvedSkills;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
({ prompt, resolvedSkills } =
|
|
214
|
+
await buildNodePromptWithResolvedSkillInstructions(spec, task, state.nodes, cwd));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (!isDagSkillSnapshotIntegrityError(error))
|
|
219
|
+
throw error;
|
|
220
|
+
await failSkillSnapshot(error);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
167
223
|
node.resolvedSkills = resolvedSkills;
|
|
168
224
|
await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
|
|
169
225
|
const model = resolveModelForTask(task, spec.executorModels);
|
|
@@ -202,6 +258,7 @@ export async function executeDagNode(input) {
|
|
|
202
258
|
await notifyNodeObserver(input.observer, "onNodeOutput", nodeId, state, outputChunk);
|
|
203
259
|
}
|
|
204
260
|
node.finishedAt = new Date().toISOString();
|
|
261
|
+
node.lastActivityAt = node.finishedAt;
|
|
205
262
|
node.status = result.ok ? "FINISHED" : "ERROR";
|
|
206
263
|
if (result.ok) {
|
|
207
264
|
const decisionRecord = await recordDecisionEnvelopeForNode({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_DAG_OUTPUT_LANGUAGE, } from "./types.js";
|
|
1
2
|
import { formatStdoutPreview, formatUpstreamArtifactPointerMap, } from "./upstream-artifacts.js";
|
|
2
3
|
export const MAX_UPSTREAM_CHARS = 2_000;
|
|
3
4
|
/** Shared bullets for DAG authoring templates, docs, and planner-node envelopes. */
|
|
@@ -41,6 +42,20 @@ function formatResolvedSkillInstructions(instructions) {
|
|
|
41
42
|
.map((instruction) => instruction.promptText)
|
|
42
43
|
.join("\n\n---\n\n");
|
|
43
44
|
}
|
|
45
|
+
export function formatOutputLanguageBlock(language = DEFAULT_DAG_OUTPUT_LANGUAGE) {
|
|
46
|
+
if (language === "en") {
|
|
47
|
+
return [
|
|
48
|
+
"Write prose, analysis, reports, summaries, and documentation in English.",
|
|
49
|
+
"Keep code, commands, paths, identifiers, JSON keys, exact protocol tokens, verdict lines, and output-contract literals unchanged.",
|
|
50
|
+
"If the node task explicitly requires another language, follow the explicit task requirement.",
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
return [
|
|
54
|
+
"使用简体中文撰写说明、分析、报告、总结和文档正文。",
|
|
55
|
+
"代码、命令、路径、标识符、JSON 字段、精确协议 token、VERDICT 行以及 output contract 中要求的字面量保持原样,不要翻译。",
|
|
56
|
+
"如果当前节点任务明确要求其他语言,以节点的明确要求为准。",
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
44
59
|
export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHARS) {
|
|
45
60
|
const sections = [];
|
|
46
61
|
for (const depId of task.depends_on) {
|
|
@@ -91,6 +106,7 @@ export function buildDagNodePromptEnvelope(input) {
|
|
|
91
106
|
`<dag_objective>\n${objective}\n</dag_objective>`,
|
|
92
107
|
`<success_criteria>\n${successCriteria}\n</success_criteria>`,
|
|
93
108
|
`<global_constraints>\n${globalConstraints}\n</global_constraints>`,
|
|
109
|
+
`<output_language>\n${formatOutputLanguageBlock(spec.outputLanguage)}\n</output_language>`,
|
|
94
110
|
[
|
|
95
111
|
"<node_contract>",
|
|
96
112
|
`Role: ${role}`,
|
|
@@ -20,6 +20,8 @@ const dagRunStatusSchema = z.enum([
|
|
|
20
20
|
"partial_failed",
|
|
21
21
|
"failed",
|
|
22
22
|
"paused",
|
|
23
|
+
"superseded",
|
|
24
|
+
"abandoned",
|
|
23
25
|
]);
|
|
24
26
|
const dagRecoveryActionSchema = z.enum(DAG_RECOVERY_ACTIONS);
|
|
25
27
|
const dagProductLineFailureCategorySchema = z.enum(dagProductLineFailureCategoryValues);
|