@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.
Files changed (37) hide show
  1. package/CHANGELOG.md +7 -11
  2. package/dist/executors/dag-pi-executor.js +44 -4
  3. package/dist/worker/cli.js +6 -3
  4. package/dist/worker/delivery/final-verification.js +96 -8
  5. package/dist/worker/delivery/package.js +23 -4
  6. package/dist/worker/delivery/verification-bundle.js +510 -0
  7. package/dist/worker/feature/fullstack-validate.js +337 -0
  8. package/dist/worker/feature/profile-schema.js +44 -0
  9. package/dist/worker/feature/ready-plan-projection.js +1 -0
  10. package/dist/worker/feature/reducer.js +2 -0
  11. package/dist/worker/feature/review.js +105 -11
  12. package/dist/worker/materialize/harness-task-materializer.js +5 -0
  13. package/dist/worker/observability/read-model.js +7 -0
  14. package/dist/worker/observe/static/views/task.js +1 -0
  15. package/dist/worker/outcomes/adapters.js +141 -0
  16. package/dist/worker/outcomes/gate.js +41 -0
  17. package/dist/worker/outcomes/projector.js +176 -0
  18. package/dist/worker/outcomes/registry.js +1 -0
  19. package/dist/worker/outcomes/store.js +131 -0
  20. package/dist/worker/outcomes/types.js +76 -0
  21. package/dist/worker/report/morning-report.js +4 -3
  22. package/dist/worker/run-task/run-task.js +66 -2
  23. package/dist/worker/runner/run-ready.js +32 -1
  24. package/dist/worker/task-graph/acceptance-schema.js +12 -0
  25. package/dist/worker/task-graph/ready-planner.js +125 -0
  26. package/dist/worker/task-graph/task-graph-schema.js +29 -0
  27. package/dist/worker/task-graph/validate.js +44 -4
  28. package/dist/worker/task-spec/schema.js +9 -0
  29. package/dist/worker/task-spec/validate.js +39 -0
  30. package/dist/worker/task-spec/workflow-routing.js +149 -0
  31. package/dist/workflows/dag/init-hybrid.js +3 -2
  32. package/dist/workflows/dag/types.js +1 -0
  33. package/docs/templates/agent-dag.schema.json +5 -0
  34. package/harness.json +1 -1
  35. package/package.json +1 -1
  36. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  37. 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
- outputContract: "Plain Markdown whose first non-empty 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.",
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 non-empty 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.",
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
@@ -59,7 +59,7 @@
59
59
  "pi": {
60
60
  "description": "Pi planning, review, diagnosis, and bounded writing when DAG toolProfile=write",
61
61
  "LOW": "gpt-5.3-codex-spark",
62
- "MED": "glm-5.2",
62
+ "MED": "grok-4.5",
63
63
  "HIGH": "gpt-5.6-sol"
64
64
  }
65
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.15.0",
3
+ "version": "0.16.1-beta.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -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 non-empty line
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` 以兼容;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`。
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