@tea-agent/loop-agent 0.15.0 → 0.16.1-beta.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/CHANGELOG.md +7 -11
- package/dist/executors/dag-pi-executor.js +44 -4
- package/dist/worker/cli.js +6 -3
- package/dist/worker/delivery/final-verification.js +96 -8
- package/dist/worker/delivery/package.js +23 -4
- package/dist/worker/delivery/verification-bundle.js +510 -0
- package/dist/worker/feature/fullstack-validate.js +337 -0
- package/dist/worker/feature/profile-schema.js +44 -0
- package/dist/worker/feature/ready-plan-projection.js +1 -0
- package/dist/worker/feature/reducer.js +2 -0
- package/dist/worker/feature/review.js +105 -11
- package/dist/worker/materialize/harness-task-materializer.js +5 -0
- package/dist/worker/observability/read-model.js +7 -0
- package/dist/worker/observe/static/views/task.js +1 -0
- package/dist/worker/outcomes/adapters.js +141 -0
- package/dist/worker/outcomes/gate.js +41 -0
- package/dist/worker/outcomes/projector.js +176 -0
- package/dist/worker/outcomes/registry.js +1 -0
- package/dist/worker/outcomes/store.js +131 -0
- package/dist/worker/outcomes/types.js +76 -0
- package/dist/worker/report/morning-report.js +4 -3
- package/dist/worker/run-task/run-task.js +66 -2
- package/dist/worker/runner/run-ready.js +32 -1
- package/dist/worker/task-graph/acceptance-schema.js +12 -0
- package/dist/worker/task-graph/ready-planner.js +125 -0
- package/dist/worker/task-graph/task-graph-schema.js +29 -0
- package/dist/worker/task-graph/validate.js +44 -4
- package/dist/worker/task-spec/schema.js +9 -0
- package/dist/worker/task-spec/validate.js +39 -0
- package/dist/worker/task-spec/workflow-routing.js +149 -0
- package/dist/workflows/dag/init-hybrid.js +3 -2
- package/dist/workflows/dag/types.js +1 -0
- package/docs/templates/agent-dag.schema.json +5 -0
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Explicit runtime workflow that routes a TaskSpec to a controller DAG.
|
|
4
|
+
*
|
|
5
|
+
* The workflow is orthogonal to the business {@link TaskSpec.type} and the
|
|
6
|
+
* governance `loop_agent` profile. It is the only field the materializer uses
|
|
7
|
+
* to derive {@link TaskKind} (written to `task.json.taskKind`) which the
|
|
8
|
+
* published controller turns into a concrete DAG.
|
|
9
|
+
*/
|
|
10
|
+
export const workflowSchema = z.enum([
|
|
11
|
+
"agent-dag",
|
|
12
|
+
"frontend-implementation",
|
|
13
|
+
"backend-test",
|
|
14
|
+
"frontend-test",
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic workflow → taskKind mapping.
|
|
18
|
+
*
|
|
19
|
+
* - `agent-dag` → `standard` (the default agent DAG).
|
|
20
|
+
* - The three remaining workflows map 1:1 to their same-named taskKind.
|
|
21
|
+
*/
|
|
22
|
+
export const WORKFLOW_TASK_KIND = {
|
|
23
|
+
"agent-dag": "standard",
|
|
24
|
+
"frontend-implementation": "frontend-implementation",
|
|
25
|
+
"backend-test": "backend-test",
|
|
26
|
+
"frontend-test": "frontend-test",
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Legacy QA business types that have no deterministic backend/frontend split.
|
|
30
|
+
* Without an explicit {@link Workflow} they MUST surface migration guidance
|
|
31
|
+
* rather than silently guessing `backend-test` or `frontend-test`.
|
|
32
|
+
*/
|
|
33
|
+
export const LEGACY_QA_TYPES = new Set([
|
|
34
|
+
"qa-casegen",
|
|
35
|
+
"qa-testcode",
|
|
36
|
+
"qa-execute",
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* Legacy business types that deterministically route to `frontend-implementation`.
|
|
40
|
+
*/
|
|
41
|
+
const FRONTEND_FEATURE_TYPES = new Set(["frontend-feature"]);
|
|
42
|
+
/**
|
|
43
|
+
* Legacy business types that deterministically route to the `agent-dag`/`standard`
|
|
44
|
+
* DAG. Anything that is not a QA type and not a frontend-feature lands here.
|
|
45
|
+
*/
|
|
46
|
+
export function legacyDefaultWorkflow(type) {
|
|
47
|
+
return FRONTEND_FEATURE_TYPES.has(type) ? "frontend-implementation" : "agent-dag";
|
|
48
|
+
}
|
|
49
|
+
/** Migration guidance surfaced whenever a legacy QA type lacks an explicit workflow. */
|
|
50
|
+
export const LEGACY_QA_MIGRATION_GUIDANCE = "Legacy qa-casegen/qa-testcode/qa-execute tasks no longer auto-select a backend or frontend workflow. " +
|
|
51
|
+
"Add `execution.workflow` with one of: agent-dag, frontend-implementation, backend-test, frontend-test. " +
|
|
52
|
+
"Use backend-test for backend-only test generation/execution, frontend-test for browser/UI tests, " +
|
|
53
|
+
"or agent-dag when the QA task is a generic agent study.";
|
|
54
|
+
export class WorkflowRoutingError extends Error {
|
|
55
|
+
code;
|
|
56
|
+
migrationGuidance;
|
|
57
|
+
constructor(code, message, migrationGuidance) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = "WorkflowRoutingError";
|
|
60
|
+
this.code = code;
|
|
61
|
+
this.migrationGuidance = migrationGuidance;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the runtime {@link Workflow} and {@link TaskKind} for a TaskSpec.
|
|
66
|
+
*
|
|
67
|
+
* Resolution order:
|
|
68
|
+
* 1. Explicit `taskSpec.execution?.workflow` (source: `explicit`).
|
|
69
|
+
* 2. Legacy `frontend-feature` → `frontend-implementation`.
|
|
70
|
+
* 3. Legacy `backend-feature` (and any non-QA legacy type) → `agent-dag`.
|
|
71
|
+
* 4. Legacy QA types ({@link LEGACY_QA_TYPES}) without an explicit workflow →
|
|
72
|
+
* preserve their historic default `standard` execution as `agent-dag` and
|
|
73
|
+
* emit migration guidance during validation. We never guess backend vs frontend.
|
|
74
|
+
*
|
|
75
|
+
* Compatibility between an explicit workflow and the business `type` must be
|
|
76
|
+
* validated first via {@link validateWorkflowCompatibility}; this function
|
|
77
|
+
* assumes the combination is already known-valid.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveWorkflow(taskSpec) {
|
|
80
|
+
const explicit = taskSpec.execution?.workflow;
|
|
81
|
+
if (explicit) {
|
|
82
|
+
return {
|
|
83
|
+
workflow: explicit,
|
|
84
|
+
taskKind: WORKFLOW_TASK_KIND[explicit],
|
|
85
|
+
source: "explicit",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (LEGACY_QA_TYPES.has(taskSpec.type)) {
|
|
89
|
+
return {
|
|
90
|
+
workflow: "agent-dag",
|
|
91
|
+
taskKind: WORKFLOW_TASK_KIND["agent-dag"],
|
|
92
|
+
source: "legacy-warning",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const workflow = legacyDefaultWorkflow(taskSpec.type);
|
|
96
|
+
return {
|
|
97
|
+
workflow,
|
|
98
|
+
taskKind: WORKFLOW_TASK_KIND[workflow],
|
|
99
|
+
source: "legacy-deterministic",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Deterministic compatibility validation between an explicit workflow and the
|
|
104
|
+
* business {@link TaskSpec.type}. Legacy QA types without an explicit workflow
|
|
105
|
+
* surface a single migration-guidance issue instead of a routing decision.
|
|
106
|
+
*
|
|
107
|
+
* Incompatible combinations (e.g. `frontend-feature` + `backend-test`,
|
|
108
|
+
* `backend-feature` + `frontend-implementation`, or a `qa-*` type paired with
|
|
109
|
+
* an implementation/test workflow from the wrong surface) are reported so the
|
|
110
|
+
* validator can fail fast rather than silently materializing a wrong DAG.
|
|
111
|
+
*/
|
|
112
|
+
export function validateWorkflowCompatibility(taskSpec) {
|
|
113
|
+
const explicit = taskSpec.execution?.workflow;
|
|
114
|
+
if (!explicit) {
|
|
115
|
+
if (LEGACY_QA_TYPES.has(taskSpec.type)) {
|
|
116
|
+
return { issues: [], migrationGuidance: LEGACY_QA_MIGRATION_GUIDANCE };
|
|
117
|
+
}
|
|
118
|
+
// Legacy backend/frontend feature and other non-QA types route deterministically.
|
|
119
|
+
return { issues: [] };
|
|
120
|
+
}
|
|
121
|
+
const issues = [];
|
|
122
|
+
const type = taskSpec.type;
|
|
123
|
+
if (type === "backend-feature" && explicit !== "agent-dag" && explicit !== "backend-test") {
|
|
124
|
+
issues.push({
|
|
125
|
+
code: "workflow-type-incompatible",
|
|
126
|
+
message: `backend-feature is incompatible with workflow "${explicit}" (expected agent-dag or backend-test)`,
|
|
127
|
+
path: "execution.workflow",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (type === "frontend-feature" && explicit !== "frontend-implementation" && explicit !== "frontend-test") {
|
|
131
|
+
issues.push({
|
|
132
|
+
code: "workflow-type-incompatible",
|
|
133
|
+
message: `frontend-feature is incompatible with workflow "${explicit}" (expected frontend-implementation or frontend-test)`,
|
|
134
|
+
path: "execution.workflow",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (LEGACY_QA_TYPES.has(type)) {
|
|
138
|
+
// qa-* types are allowed to opt into a concrete test/implementation workflow
|
|
139
|
+
// but never into a feature implementation surface that does not match QA intent.
|
|
140
|
+
if (explicit === "frontend-implementation") {
|
|
141
|
+
issues.push({
|
|
142
|
+
code: "workflow-type-incompatible",
|
|
143
|
+
message: `${type} is a QA type and cannot route to frontend-implementation; use frontend-test, backend-test, or agent-dag`,
|
|
144
|
+
path: "execution.workflow",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { issues };
|
|
149
|
+
}
|
|
@@ -1338,10 +1338,11 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
|
|
|
1338
1338
|
allowedPaths: readOnlyPaths,
|
|
1339
1339
|
forbiddenPaths,
|
|
1340
1340
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
1341
|
-
|
|
1341
|
+
firstProtocolLine: "MOCK_STRATEGY:",
|
|
1342
|
+
outputContract: "Plain Markdown whose first line is MOCK_STRATEGY: native|browser-intercept|request-adapter|not-needed|blocked, followed by Mock Decision, API Contract Evidence, Specification Evidence, Service Evidence, Backend Readiness, Selection Evidence, Endpoint / Fixture Matrix, Activation, Target Files, Production Safety, Verification Plan, Real Integration Gap, and Blocking Issues. No file writes.",
|
|
1342
1343
|
subtask_prompt: [
|
|
1343
1344
|
"Perform read-only Mock assessment and select one safe frontend data strategy.",
|
|
1344
|
-
"The first
|
|
1345
|
+
"The first line must be exactly one of: MOCK_STRATEGY: native, MOCK_STRATEGY: browser-intercept, MOCK_STRATEGY: request-adapter, MOCK_STRATEGY: not-needed, or MOCK_STRATEGY: blocked. Do not emit blank lines, headings, or explanatory preamble before it.",
|
|
1345
1346
|
"Prefer an existing native Mock facility. Use browser-intercept only with an existing browser/e2e harness. When no Mock exists but the API layer is writable, use request-adapter by adding a minimal reversible adapter/DI seam within the approved writeSet; the real adapter must remain the production default.",
|
|
1346
1347
|
autoMaySkipMissingMock
|
|
1347
1348
|
? "Auto mode may skip Mock when no project Mock capability is confirmed. Select not-needed with positive evidence from contract/scout that no project Mock capability is confirmed, continue without adding Mock files or dependencies, run the fixed verification entrypoints, and record any unproved real API behavior in Real Integration Gap. Do not block solely because no project Mock capability, browser interception harness, or request adapter exists."
|
|
@@ -267,6 +267,7 @@ export const dagTaskSchema = z.object({
|
|
|
267
267
|
shell: dagShellConfigSchema.optional(),
|
|
268
268
|
static: dagStaticConfigSchema.optional(),
|
|
269
269
|
outputContract: z.string().optional(),
|
|
270
|
+
firstProtocolLine: z.string().min(1).optional(),
|
|
270
271
|
allowedPaths: z.array(z.string()).optional().default([]),
|
|
271
272
|
forbiddenPaths: z.array(z.string()).optional().default([]),
|
|
272
273
|
decisionGate: dagDecisionGateSchema.optional(),
|
|
@@ -366,6 +366,11 @@
|
|
|
366
366
|
"type": "string",
|
|
367
367
|
"minLength": 1
|
|
368
368
|
},
|
|
369
|
+
"firstProtocolLine": {
|
|
370
|
+
"type": "string",
|
|
371
|
+
"minLength": 1,
|
|
372
|
+
"description": "Optional protocol prefix whose first matching Pi assistant-output line is promoted to the canonical first line. Missing matches are not synthesized."
|
|
373
|
+
},
|
|
369
374
|
"allowedPaths": {
|
|
370
375
|
"type": "array",
|
|
371
376
|
"items": { "type": "string", "minLength": 1 },
|
package/harness.json
CHANGED
package/package.json
CHANGED
|
@@ -6,9 +6,9 @@ Pre-write nodes are read-only. Preserve IDs, labels, commands, language, require
|
|
|
6
6
|
|
|
7
7
|
- **`frontend-contract-pi`**: `Scope`, `Non-goals`, `Acceptance Criteria`, `UI States`, `Target Runtime Environment`, `Risks`, `Verification Expectations`. No guessed requirements.
|
|
8
8
|
- **`frontend-scout-pi`**: routes, components, tokens, data/API/Mock, scripts, tests, assets. Fact vs inference vs gap. Knowledge base first; else search+read `<repoRoot>/openSpec/**` before repo fallback. Output stack, routes, components, styling, conventions, state/data, test entry points, reuse, risks.
|
|
9
|
-
- **`frontend-mock-assess-pi` + gate**: first
|
|
9
|
+
- **`frontend-mock-assess-pi` + gate**: declares `firstProtocolLine: "MOCK_STRATEGY:"`; canonical output first line
|
|
10
10
|
`MOCK_STRATEGY: native|browser-intercept|request-adapter|not-needed|blocked`
|
|
11
|
-
Prefer native Mock; browser intercept only with existing e2e; request-adapter only for reversible local preview. Default `auto` may select `not-needed` when contract/scout evidence confirms no project Mock capability, without adding Mock files/deps, while keeping real requests default and recording the Real Integration Gap. Other `not-needed` cases need positive no-remote/stable-backend evidence; invalid when `frontendMock.policy=required`. `blocked` for missing/conflicting contracts, unsafe paths/deps, unread specs, production-default-on, unverifiable entrypoints. Output Mock Decision, API/spec/service evidence, backend readiness, selection evidence, endpoint/fixture matrix, activation, targets, production safety, verification plan, real-integration gap, blocking issues. Never invent fields, store secrets, comment real requests, import test mocks into production, or treat Mock as real integration. Gate uses `first-non-empty` only; never authorizes writes. Unsafe required contracts → no writer.
|
|
11
|
+
Pi output mapping promotes the first matching protocol line ahead of any preamble without inventing or replacing its value; missing, malformed, or blocked strategies still fail closed. Prefer native Mock; browser intercept only with existing e2e; request-adapter only for reversible local preview. Default `auto` may select `not-needed` when contract/scout evidence confirms no project Mock capability, without adding Mock files/deps, while keeping real requests default and recording the Real Integration Gap. Other `not-needed` cases need positive no-remote/stable-backend evidence; invalid when `frontendMock.policy=required`. `blocked` for missing/conflicting contracts, unsafe paths/deps, unread specs, production-default-on, unverifiable entrypoints. Output Mock Decision, API/spec/service evidence, backend readiness, selection evidence, endpoint/fixture matrix, activation, targets, production safety, verification plan, real-integration gap, blocking issues. Never invent fields, store secrets, comment real requests, import test mocks into production, or treat Mock as real integration. Gate uses `first-non-empty` only; never authorizes writes. Unsafe required contracts → no writer.
|
|
12
12
|
- **`frontend-plan-pi` + design loop**: AC → steps, in-bound files, UI states, reuse, deps, activation/rollback, frozen verify entrypoints, real-integration gap. First gate: `VERDICT: pass|request-revision`. Pass may emit `PASS_NO_REVISION_NEEDED`; else full corrected plan without invented evidence. Final review rechecks plan/findings/revision/assessment/Mock safety. Only final `VERDICT: pass` authorizes writes; failure → replan/rerun (not dev-fix).
|
|
13
13
|
- **`frontend-implement-pi`**: sole exclusive writer. Stay in `writeSet`; real requests default-on; Mock reversible, dev/test-only, production-off. Atomic handler/intercept/adapter with consumer+tests. Stop on forbidden paths or guesses. Output changed files, behavior, UI states, styling notes, verification attempted, residual risks. Optional mock-verify when frozen; static+behavior always; behavior must prove page consumption. Skipped-Mock `not-needed` keeps real integration pending unless the real backend path has fresh evidence.
|
|
14
14
|
|
|
@@ -117,7 +117,7 @@ contract-pi → scout-src ∥ scout-tests → plan-pi → write-set-audit-pi
|
|
|
117
117
|
| `authority-surface-audit-pi` + `authority-surface-gate-shell` | 可选 permission/state/tool-exposure audit;仅 authority signal 或显式 `authority-surface-audit` marker 时插入;gate 仅接受 `VERDICT: pass` |
|
|
118
118
|
| `review-pi` + `review-gate-shell` | Critical/Important → `request-revision`;node JSON 上 `shell.verdictGate` block,除非 extracted verdict 行为 `VERDICT: pass` |
|
|
119
119
|
|
|
120
|
-
**Verdict gate contract(`shell.verdictGate`)**:声明 `fromNodeId`、`accept[]`、可选 `label`、可选 `lineMode`。runner 展开为一条 shell command,从 injected current run directory 读 `$HARNESS_DAG_RUN_DIR/<fromNodeId>.json`,对 extracted `assistantText ?? stdout` verdict line 与 `accept[]` exact-match。默认 `lineMode` 为 `first-non-empty`
|
|
120
|
+
**Verdict gate contract(`shell.verdictGate`)**:声明 `fromNodeId`、`accept[]`、可选 `label`、可选 `lineMode`。runner 展开为一条 shell command,从 injected current run directory 读 `$HARNESS_DAG_RUN_DIR/<fromNodeId>.json`,对 extracted `assistantText ?? stdout` verdict line 与 `accept[]` exact-match。默认 `lineMode` 为 `first-non-empty` 以兼容;需要固定非 `VERDICT:` 协议首行的 Pi 节点可显式声明 `firstProtocolLine`,executor 会把第一条匹配前缀的行提升为 canonical 首行,不匹配时不伪造。`frontend-mock-assess-pi` 用它固定 `MOCK_STRATEGY:`,gate 仍保持 `first-non-empty` exact-match。supervised gate 用 `first-verdict-line` 选 Pi 在 preamble 或常见整行 Markdown emphasis(如 `**VERDICT: pass**`)后第一条 normalized `VERDICT:` line。勿用 `result.summary.md`、grep VERDICT、latest-active-run discovery 或 multi-command stateful gate。`--strict-governance` 对 anti-pattern fail。supervisor 仍为 `executor: pi` 上的 `role: supervisor`。
|
|
121
121
|
|
|
122
122
|
**Repair artifact gate contract(`shell.repairArtifactGate`)**:声明 `fromNodeId`(supervisor artifact 节点)与 `repairNodeId`(承接修订的 Pi 修复节点)。runner **不再**按节点名(历史 `repair-cursor` / `repair-pi`)猜测 repair 节点:显式 `repairNodeId` 必须存在、直接 `depends_on` gate、且是受治理 Pi writer(`executor: pi`、`toolProfile: write`、`writePolicy: exclusive`、`allowedPaths`+`writeSet` 非空且 `writeSet` 不与 `forbiddenPaths` 冲突)。新生成的 supervised DAG 总是写入 `repairNodeId`;旧 DAG 缺失时只在能唯一、安全地推导出下游 Pi writer 时兼容,零个或多个候选、或候选不满足契约都在执行前 fail closed。validation 覆盖存在性、直接下游、writer 属性与路径边界。
|
|
123
123
|
|