@tea-agent/loop-agent 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +48 -38
- package/README.md +3 -3
- package/dist/application/dag/args.js +9 -1
- package/dist/application/dag/run-dag.js +16 -2
- package/dist/application/dag/validate-dag.js +14 -1
- package/dist/cli/command-definitions.js +22 -4
- package/dist/cli/help.js +3 -2
- package/dist/cli/program.js +7 -5
- package/dist/commands/import-prd.js +76 -0
- package/dist/commands/init.js +230 -32
- package/dist/commands/instructions.js +90 -58
- package/dist/executors/config-core.js +3 -2
- package/dist/executors/dag-pi-executor.js +1 -0
- package/dist/executors/model-routing.js +43 -0
- package/dist/executors/pi-sdk-executor.js +63 -1
- package/dist/governance/manifest-types.js +9 -1
- package/dist/shared/preview.js +39 -0
- package/dist/task/source-references.js +221 -0
- package/dist/worker/cli.js +62 -1
- package/dist/worker/loop-agent/loop-agent-client.js +97 -5
- package/dist/worker/materialize/harness-task-materializer.js +162 -5
- package/dist/worker/observability/event-store.js +82 -0
- package/dist/worker/observability/events.js +79 -0
- package/dist/worker/observability/progress-composite.js +33 -0
- package/dist/worker/observability/read-model.js +1013 -0
- package/dist/worker/observability/snapshot-store.js +43 -0
- package/dist/worker/observability/types.js +1 -0
- package/dist/worker/observe/paths.js +64 -0
- package/dist/worker/observe/routes.js +423 -0
- package/dist/worker/observe/server.js +61 -0
- package/dist/worker/observe/static/app.js +1419 -0
- package/dist/worker/observe/static/index.html +63 -0
- package/dist/worker/observe/static/styles.css +613 -0
- package/dist/worker/pool/failure-routing.js +41 -6
- package/dist/worker/pool/run-store.js +59 -1
- package/dist/worker/progress-reporter.js +0 -18
- package/dist/worker/run-task/run-task.js +327 -92
- package/dist/worker/runner/run-ready.js +112 -4
- package/dist/workflows/dag/event-observer.js +132 -0
- package/dist/workflows/dag/init-hybrid.js +150 -26
- package/dist/workflows/dag/observer-compose.js +52 -0
- package/dist/workflows/dag/skill-instructions.js +4 -0
- package/dist/workflows/dag/types.js +1 -1
- package/dist/workflows/dag/validate.js +3 -2
- package/docs/README.md +2 -0
- package/docs/architecture/runtime-boundaries.md +18 -3
- package/docs/design/README.md +22 -9
- package/docs/exec-plans/active/README.md +6 -1
- package/docs/exec-plans/completed/README.md +12 -0
- package/docs/init-surface.manifest.json +32 -2
- package/docs/loop-agent-harness.md +13 -0
- package/docs/reports/README.md +4 -0
- package/docs/templates/agent-dag.base.json +1 -1
- package/docs/templates/agent-dag.final-verification.json +1 -1
- package/docs/templates/agent-dag.supervised-implementation.json +1 -1
- package/docs/templates/hybrid-dag.json +1 -1
- package/docs/templates/worker-dogfood-evidence.md +52 -0
- package/docs/templates/worker-dogfood-setup.md +48 -0
- package/examples/example-dag.json +1 -1
- package/examples/hybrid-loop-agent-dag.json +1 -1
- package/harness.json +5 -29
- package/package.json +6 -6
- package/skills/loop-agent/SKILL.md +5 -3
- package/skills/loop-agent/references/command-reference.md +12 -3
- package/skills/loop-agent/references/harness-policy.md +7 -3
- package/skills/loop-agent/references/task-workflow.md +8 -3
|
@@ -52,12 +52,14 @@ export function deriveFailureRoute(result) {
|
|
|
52
52
|
if (result.status === "succeeded")
|
|
53
53
|
return undefined;
|
|
54
54
|
const primaryFailure = readObject(result.reportDecision.primaryFailure);
|
|
55
|
-
const category =
|
|
56
|
-
|
|
57
|
-
readProductCategory(primaryFailure, "
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
const category = looksLikeExecutorConfigurationFailure(primaryFailure)
|
|
56
|
+
? "EnvFailure"
|
|
57
|
+
: readProductCategory(primaryFailure, "productLineFailureCategory") ??
|
|
58
|
+
readProductCategory(primaryFailure, "productLineCategory") ??
|
|
59
|
+
readProductCategory(primaryFailure, "product_line_failure_category") ??
|
|
60
|
+
mapDagCategory(readString(primaryFailure, "failureCategory")) ??
|
|
61
|
+
mapDagCategory(readString(primaryFailure, "normalizedFailureCategory")) ??
|
|
62
|
+
categoryFromDecisionReason(result.reportDecision.reason);
|
|
61
63
|
const recommendedFollowUpKind = readString(primaryFailure, "recommendedFollowUpKind") ??
|
|
62
64
|
readString(primaryFailure, "recommendedFollowUp") ??
|
|
63
65
|
FOLLOW_UP_BY_CATEGORY[category];
|
|
@@ -68,6 +70,18 @@ export function deriveFailureRoute(result) {
|
|
|
68
70
|
source: primaryFailure ? "report-primary-failure" : "fallback",
|
|
69
71
|
};
|
|
70
72
|
}
|
|
73
|
+
/** Route a failure raised before a DAG report can be produced. */
|
|
74
|
+
export function deriveFailureRouteFromError(message, businessId) {
|
|
75
|
+
const category = looksLikeExecutorConfigurationText(message)
|
|
76
|
+
? "EnvFailure"
|
|
77
|
+
: "Unknown";
|
|
78
|
+
return {
|
|
79
|
+
category,
|
|
80
|
+
recommendedFollowUpKind: FOLLOW_UP_BY_CATEGORY[category],
|
|
81
|
+
derivedFollowUpTaskId: `${FOLLOW_UP_PREFIX_BY_CATEGORY[category]}-${businessId}`,
|
|
82
|
+
source: "runner-error",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
71
85
|
function readProductCategory(value, key) {
|
|
72
86
|
const candidate = readString(value, key);
|
|
73
87
|
return candidate && PRODUCT_CATEGORIES.has(candidate)
|
|
@@ -85,6 +99,27 @@ function categoryFromDecisionReason(reason) {
|
|
|
85
99
|
}
|
|
86
100
|
return "Unknown";
|
|
87
101
|
}
|
|
102
|
+
function looksLikeExecutorConfigurationFailure(value) {
|
|
103
|
+
if (!value)
|
|
104
|
+
return false;
|
|
105
|
+
return looksLikeExecutorConfigurationText([
|
|
106
|
+
"failureCategory",
|
|
107
|
+
"normalizedFailureCategory",
|
|
108
|
+
"message",
|
|
109
|
+
"error",
|
|
110
|
+
"stderr",
|
|
111
|
+
"detail",
|
|
112
|
+
]
|
|
113
|
+
.map((key) => readString(value, key))
|
|
114
|
+
.filter((candidate) => Boolean(candidate))
|
|
115
|
+
.join("\n"));
|
|
116
|
+
}
|
|
117
|
+
function looksLikeExecutorConfigurationText(message) {
|
|
118
|
+
const value = message.toLowerCase();
|
|
119
|
+
const executorTerms = /provider|model|executor|api[ _-]?key|credential|thinking level/;
|
|
120
|
+
const configurationTerms = /unknown|invalid|unsupported|missing|not configured|configuration|unavailable/;
|
|
121
|
+
return executorTerms.test(value) && configurationTerms.test(value);
|
|
122
|
+
}
|
|
88
123
|
function readObject(value) {
|
|
89
124
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
90
125
|
return undefined;
|
|
@@ -22,6 +22,48 @@ export async function findRunByWorkerRunId(repoRoot, workerRunId) {
|
|
|
22
22
|
const runs = await readJsonlFile(getRunsJsonlPath(repoRoot));
|
|
23
23
|
return runs.find((run) => run.workerRunId === workerRunId);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Explicitly requeue a failed task without deleting or rewriting prior evidence.
|
|
27
|
+
* The runner consumes `retryOfWorkerRunId` and derives a guaranteed fresh run id.
|
|
28
|
+
*/
|
|
29
|
+
export async function prepareTaskPoolRetry(input) {
|
|
30
|
+
const current = await readTaskPoolState(input.repoRoot, input.taskId);
|
|
31
|
+
if (!current) {
|
|
32
|
+
throw new Error(`cannot retry ${input.taskId}: Task Pool state does not exist`);
|
|
33
|
+
}
|
|
34
|
+
if (current.status !== "Failed") {
|
|
35
|
+
throw new Error(`cannot retry ${input.taskId}: expected Failed state, found ${current.status}`);
|
|
36
|
+
}
|
|
37
|
+
if (!current.workerRunId) {
|
|
38
|
+
throw new Error(`cannot retry ${input.taskId}: failed state is missing workerRunId`);
|
|
39
|
+
}
|
|
40
|
+
const retryRequestedAt = (input.now ?? new Date()).toISOString();
|
|
41
|
+
const result = {
|
|
42
|
+
taskId: input.taskId,
|
|
43
|
+
status: "Ready",
|
|
44
|
+
previousWorkerRunId: current.workerRunId,
|
|
45
|
+
retryRequestedAt,
|
|
46
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
47
|
+
};
|
|
48
|
+
await writeTaskPoolState(input.repoRoot, {
|
|
49
|
+
taskId: current.taskId,
|
|
50
|
+
status: "Ready",
|
|
51
|
+
updatedAt: retryRequestedAt,
|
|
52
|
+
retryOfWorkerRunId: current.workerRunId,
|
|
53
|
+
retryRequestedAt,
|
|
54
|
+
...(input.reason ? { retryReason: input.reason } : {}),
|
|
55
|
+
});
|
|
56
|
+
await ensurePoolDirs(input.repoRoot);
|
|
57
|
+
await appendJsonlFile(getEventsJsonlPath(input.repoRoot), {
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
at: retryRequestedAt,
|
|
60
|
+
type: "task-retry-requested",
|
|
61
|
+
taskId: input.taskId,
|
|
62
|
+
previousWorkerRunId: current.workerRunId,
|
|
63
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
64
|
+
});
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
25
67
|
export async function readTaskPoolState(repoRoot, taskId) {
|
|
26
68
|
const statePath = getTaskStatePath(repoRoot, taskId);
|
|
27
69
|
try {
|
|
@@ -74,7 +116,15 @@ export async function readJsonlFile(filePath) {
|
|
|
74
116
|
}
|
|
75
117
|
async function appendJsonlFile(filePath, value) {
|
|
76
118
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
77
|
-
|
|
119
|
+
try {
|
|
120
|
+
await appendFile(filePath, `${JSON.stringify(value)}\n`, "utf-8");
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
124
|
+
// Include path so callers/tests can identify which JSONL file failed
|
|
125
|
+
// (Node EISDIR messages on Windows often omit the path).
|
|
126
|
+
throw new Error(`failed to append ${filePath}: ${detail}`, { cause: error });
|
|
127
|
+
}
|
|
78
128
|
}
|
|
79
129
|
async function ensurePoolDirs(repoRoot) {
|
|
80
130
|
await mkdir(path.join(getTaskPoolRoot(repoRoot), "runs"), { recursive: true });
|
|
@@ -89,11 +139,19 @@ function stateFromRun(run) {
|
|
|
89
139
|
workerRunId: run.workerRunId,
|
|
90
140
|
...(run.runRecordPath ? { lastRunRecordPath: run.runRecordPath } : {}),
|
|
91
141
|
...(run.failure ? { failure: run.failure } : {}),
|
|
142
|
+
...(run.retryOfWorkerRunId
|
|
143
|
+
? { retryOfWorkerRunId: run.retryOfWorkerRunId }
|
|
144
|
+
: {}),
|
|
92
145
|
};
|
|
93
146
|
}
|
|
94
147
|
function stateStatusFromRun(run) {
|
|
95
148
|
if (run.status === "succeeded")
|
|
96
149
|
return "Done";
|
|
150
|
+
// A pre-DAG provider/executor configuration failure is retryable after the
|
|
151
|
+
// operator corrects the environment; do not bury it in a non-retryable Blocked state.
|
|
152
|
+
if (run.status === "run-error" && run.failure?.category === "EnvFailure") {
|
|
153
|
+
return "Failed";
|
|
154
|
+
}
|
|
97
155
|
if (run.status === "run-error")
|
|
98
156
|
return "Blocked";
|
|
99
157
|
return "Failed";
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Human-readable progress reporting for long-running worker commands.
|
|
3
|
-
*
|
|
4
|
-
* Why this exists: agent-worker batch runs can take many minutes per task with
|
|
5
|
-
* zero stdout/stderr feedback, which looks indistinguishable from a hang. The
|
|
6
|
-
* system writes rich state to disk (state.json, run.json, .task-pool artifacts)
|
|
7
|
-
* but never echoes it to the triggering terminal, so users assume it is dead.
|
|
8
|
-
*
|
|
9
|
-
* Design:
|
|
10
|
-
* - Progress goes to STDERR only. STDOUT stays reserved for the final
|
|
11
|
-
* machine-readable JSON payload, so `... | jq` and pipes keep working.
|
|
12
|
-
* - Default ON. `--quiet` disables it. Non-interactive use that only wants JSON
|
|
13
|
-
* can pass `--quiet` (or redirect stderr).
|
|
14
|
-
* - TTY-aware for ANSI only: if stderr is not a TTY, we still print the lines
|
|
15
|
-
* (they are line-buffered text and flush fine) but skip ANSI decoration.
|
|
16
|
-
* - A `noop` reporter keeps library callers (and tests that don't care) quiet
|
|
17
|
-
* with zero changes to their call sites beyond accepting the option.
|
|
18
|
-
*/
|
|
19
1
|
const ANSI = {
|
|
20
2
|
bold: "\x1b[1m",
|
|
21
3
|
green: "\x1b[32m",
|