@tea-agent/loop-agent 0.39.0-next.25 → 0.39.0-next.27

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 (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/bin/loop-agent.js +7 -3
  3. package/dist/build-stamp.json +2 -2
  4. package/dist/executors/dag-pi-executor.js +58 -1
  5. package/dist/executors/pi-executor.js +51 -0
  6. package/dist/executors/pi-extension-resolver.js +233 -0
  7. package/dist/executors/pi-sdk-executor.js +182 -54
  8. package/dist/executors/shell-executor.js +54 -0
  9. package/dist/executors/shell-write-guard.js +7 -0
  10. package/dist/worker/observe/node-input.js +72 -3
  11. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  12. package/dist/worker/observe/static/state.js +2 -2
  13. package/dist/worker/observe/static/views/dag-inspector.js +51 -0
  14. package/dist/worker/observe/static/views/session-timeline.js +18 -5
  15. package/dist/workflows/dag/contract-output-registry.js +15 -0
  16. package/dist/workflows/dag/failure-category.js +4 -0
  17. package/dist/workflows/dag/frontend-implementation-contract.js +140 -39
  18. package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
  19. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  20. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  21. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  22. package/dist/workflows/dag/frontend-test-result-contract.js +10 -18
  23. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  24. package/dist/workflows/dag/init-hybrid.js +52 -94
  25. package/dist/workflows/dag/node-execution.js +119 -8
  26. package/dist/workflows/dag/prompt.js +15 -1
  27. package/dist/workflows/dag/rerun-plan.js +22 -3
  28. package/dist/workflows/dag/structured-output-repair.js +712 -0
  29. package/dist/workflows/dag/types.js +23 -0
  30. package/dist/workflows/dag/validate.js +41 -1
  31. package/docs/templates/agent-dag.schema.json +44 -0
  32. package/docs/templates/frontend-test-dag.json +8 -10
  33. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  34. package/package.json +1 -1
  35. package/skills/codebase-scout/SKILL.md +1 -1
@@ -10,6 +10,18 @@ export const dagNodeExecutorSchema = z.enum(["pi", "shell", "static"]);
10
10
  export const CURSOR_DAG_EXECUTOR_REMOVED_ERROR = 'executor "cursor" is no longer supported; regenerate the DAG with Pi-only writers (implement-pi / repair-pi)';
11
11
  export const CURSOR_EXECUTOR_MODELS_REMOVED_ERROR = "executorModels.cursor is no longer supported; use executorModels.pi only";
12
12
  export const dagToolProfileSchema = z.enum(["read-only", "write"]);
13
+ /**
14
+ * Frozen Pi extension short ids for backend implementation DAGs (plan
15
+ * 2026-08-21 D2): the DAG carries only these ids; the executor resolves them
16
+ * against the Pi settings package inventory at run time. Adding a package
17
+ * requires changing this enum and the plan.
18
+ */
19
+ export const dagPiExtensionIdSchema = z.enum(["pi-codegraph", "pi-lens"]);
20
+ /** Node-level explicit Pi extension allowlist (omitted = all off, R8.1). */
21
+ export const dagPiExtensionsSchema = z
22
+ .array(dagPiExtensionIdSchema)
23
+ .min(1)
24
+ .transform((ids) => Array.from(new Set(ids)));
13
25
  /** Static command capabilities only; tasks cannot inject executables or shell prefixes. */
14
26
  export const dagCommandCapabilitySchema = z.enum(["playwright-cli"]);
15
27
  export const dagCommandPolicySchema = z.discriminatedUnion("mode", [
@@ -427,6 +439,8 @@ export const dagBackendTestPipelineSchema = z.enum([
427
439
  "ingest-backend-test-gap",
428
440
  ]);
429
441
  export const dagFrontendBrowserToolPreflightSchema = z.object({}).strict();
442
+ export const dagFrontendTestStandardScenariosSchema = z.object({}).strict();
443
+ export const dagFrontendTestEnvironmentProbeSchema = z.object({}).strict();
430
444
  export const dagFrontendTestEvidenceValidationSchema = z.object({}).strict();
431
445
  export const dagFrontendTestCaseChecklistSchema = z.object({}).strict();
432
446
  export const dagFrontendTestHtmlReportSchema = z.object({}).strict();
@@ -517,6 +531,8 @@ export const dagShellConfigSchema = z.object({
517
531
  frontendReviewContext: dagFrontendReviewContextSchema.optional(),
518
532
  frontendTestCaseChecklist: dagFrontendTestCaseChecklistSchema.optional(),
519
533
  frontendBrowserToolPreflight: dagFrontendBrowserToolPreflightSchema.optional(),
534
+ frontendTestStandardScenarios: dagFrontendTestStandardScenariosSchema.optional(),
535
+ frontendTestEnvironmentProbe: dagFrontendTestEnvironmentProbeSchema.optional(),
520
536
  frontendTestEvidenceValidation: dagFrontendTestEvidenceValidationSchema.optional(),
521
537
  finalWriteSetApprovalGate: dagFinalWriteSetApprovalGateSchema.optional(),
522
538
  frontendTestL5Report: z.object({}).strict().optional(),
@@ -749,6 +765,13 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
749
765
  role: dagRoleSchema.optional(),
750
766
  skills: z.array(z.string()).optional(),
751
767
  toolProfile: dagToolProfileSchema.optional(),
768
+ /**
769
+ * Explicit Pi extension allowlist (backend implementation DAGs only):
770
+ * frozen short ids resolved at run time against the Pi settings package
771
+ * inventory. Omitted = extension discovery stays fully closed (R8.1).
772
+ * Only `executor: "pi"` nodes may declare it (validated fail-closed).
773
+ */
774
+ piExtensions: dagPiExtensionsSchema.optional(),
752
775
  /** Default deny: file write (toolProfile=write) does not grant command execution. */
753
776
  commandPolicy: dagCommandPolicySchema.optional(),
754
777
  writePolicy: dagWritePolicySchema.optional(),
@@ -1,4 +1,4 @@
1
- import { DEFAULT_DAG_EXECUTOR_MODELS, ENV_VAR_NAME_PATTERN, dagCommandPolicyAllows, resolveDagCommandPolicy, } from "./types.js";
1
+ import { DEFAULT_DAG_EXECUTOR_MODELS, ENV_VAR_NAME_PATTERN, dagCommandPolicyAllows, dagPiExtensionIdSchema, resolveDagCommandPolicy, } from "./types.js";
2
2
  import { resolveShellCommands } from "../../executors/shell-executor.js";
3
3
  import { pathMatchesPattern } from "../../shared/git-progress.js";
4
4
  import { resolveRepairTaskForGate } from "./repair-artifact.js";
@@ -465,6 +465,8 @@ function validateShellTaskConfig(task, spec, issues) {
465
465
  !shell.backendTestPipeline &&
466
466
  !shell.frontendPrewriteGate &&
467
467
  !shell.frontendBrowserToolPreflight &&
468
+ !shell.frontendTestStandardScenarios &&
469
+ !shell.frontendTestEnvironmentProbe &&
468
470
  !shell.frontendVerificationBundle &&
469
471
  !shell.frontendReviewContext &&
470
472
  !shell.frontendTestCaseChecklist &&
@@ -953,6 +955,7 @@ export function validateDagSpec(spec) {
953
955
  validateCommandPolicyTaskConfig(task, issues);
954
956
  validateDynamicChildCommandPolicy(task, issues);
955
957
  validateProjectGovernanceTaskConfig(task, spec, issues);
958
+ validatePiExtensionsTaskConfig(task, issues);
956
959
  validateFailureAwareDependsOn(task, spec, issues);
957
960
  }
958
961
  validateSameRankWriteSetConflicts(spec, ranks, issues);
@@ -963,6 +966,43 @@ export function validateDagSpec(spec) {
963
966
  }
964
967
  return issues;
965
968
  }
969
+ /**
970
+ * Fail-closed `piExtensions` validation (plan 2026-08-21 D1/D2):
971
+ * - only `executor: "pi"` nodes may declare it (shell/static reject);
972
+ * - unknown ids outside the frozen enum are rejected at parse time by zod,
973
+ * and here for task objects that bypass schema parsing;
974
+ * - closeout nodes must stay fully closed (extension-free) — the two-bucket
975
+ * generator never assigns it there, so any appearance is contract drift.
976
+ */
977
+ function validatePiExtensionsTaskConfig(task, issues) {
978
+ const ids = task.piExtensions;
979
+ if (ids === undefined)
980
+ return;
981
+ if (task.executor !== "pi") {
982
+ issues.push({
983
+ type: "invalid-pi-extensions-config",
984
+ message: `task ${task.id} piExtensions requires executor "pi" (found ${task.executor})`,
985
+ });
986
+ return;
987
+ }
988
+ const allowed = new Set(dagPiExtensionIdSchema.options);
989
+ for (const id of ids) {
990
+ if (!allowed.has(id)) {
991
+ issues.push({
992
+ type: "invalid-pi-extensions-config",
993
+ message: `task ${task.id} piExtensions has unknown id "${id}"; allowed: ${[
994
+ ...allowed,
995
+ ].join(", ")}`,
996
+ });
997
+ }
998
+ }
999
+ if (task.id === "closeout-pi" && ids.length > 0) {
1000
+ issues.push({
1001
+ type: "invalid-pi-extensions-config",
1002
+ message: "closeout-pi must stay extension-free (read buckets exclude closeout)",
1003
+ });
1004
+ }
1005
+ }
966
1006
  function validateProjectGovernanceTaskConfig(task, spec, issues) {
967
1007
  if (task.governanceStandardReview) {
968
1008
  if (task.executor !== "pi" ||
@@ -130,6 +130,18 @@
130
130
  "toolProfile": {
131
131
  "enum": ["read-only", "write"]
132
132
  },
133
+ "piExtensionId": {
134
+ "type": "string",
135
+ "enum": ["pi-codegraph", "pi-lens"],
136
+ "description": "Frozen Pi extension short id resolved at run time against the Pi settings package inventory. Adding a package requires changing the plan and this enum."
137
+ },
138
+ "piExtensions": {
139
+ "type": "array",
140
+ "minItems": 1,
141
+ "uniqueItems": true,
142
+ "items": { "$ref": "#/$defs/piExtensionId" },
143
+ "description": "Node-level explicit Pi extension allowlist (backend implementation DAGs only). Omitted = extension discovery stays fully closed (R8.1). Only executor 'pi' nodes may declare it."
144
+ },
133
145
  "role": {
134
146
  "enum": ["planner", "scout", "implementer", "reviewer", "supervisor", "verifier", "closeout"]
135
147
  },
@@ -356,6 +368,31 @@
356
368
  "properties": { "schemaVersion": { "const": 1 }, "requireBaseline": { "const": true } }
357
369
  },
358
370
  "frontendTestCaseChecklist": { "type": "object", "additionalProperties": false },
371
+ "frontendBrowserToolPreflight": { "type": "object", "additionalProperties": false },
372
+ "frontendTestStandardScenarios": { "type": "object", "additionalProperties": false },
373
+ "frontendTestEnvironmentProbe": { "type": "object", "additionalProperties": false },
374
+ "frontendTestCaseManifest": {
375
+ "type": "object",
376
+ "additionalProperties": false,
377
+ "properties": {
378
+ "maxCases": { "type": "integer", "minimum": 1 },
379
+ "declaredAcIds": { "type": "array", "items": { "type": "string" } }
380
+ }
381
+ },
382
+ "frontendTestResultFinalize": {
383
+ "type": "object",
384
+ "additionalProperties": false,
385
+ "properties": {
386
+ "declaredAcIds": { "type": "array", "items": { "type": "string" } }
387
+ }
388
+ },
389
+ "frontendTestReports": {
390
+ "type": "object",
391
+ "additionalProperties": false,
392
+ "properties": {
393
+ "l5": { "type": "boolean" }
394
+ }
395
+ },
359
396
  "frontendTestEvidenceValidation": { "type": "object", "additionalProperties": false },
360
397
  "frontendTestHtmlReport": { "type": "object", "additionalProperties": false },
361
398
  "backendTestPipeline": {
@@ -383,6 +420,12 @@
383
420
  { "required": ["frontendVerificationBundle"] },
384
421
  { "required": ["frontendReviewContext"] },
385
422
  { "required": ["frontendTestCaseChecklist"] },
423
+ { "required": ["frontendBrowserToolPreflight"] },
424
+ { "required": ["frontendTestStandardScenarios"] },
425
+ { "required": ["frontendTestEnvironmentProbe"] },
426
+ { "required": ["frontendTestCaseManifest"] },
427
+ { "required": ["frontendTestResultFinalize"] },
428
+ { "required": ["frontendTestReports"] },
386
429
  { "required": ["frontendTestEvidenceValidation"] },
387
430
  { "required": ["frontendTestHtmlReport"] },
388
431
  { "required": ["backendTestPipeline"] }
@@ -485,6 +528,7 @@
485
528
  "items": { "type": "string", "minLength": 1 }
486
529
  },
487
530
  "toolProfile": { "$ref": "#/$defs/toolProfile" },
531
+ "piExtensions": { "$ref": "#/$defs/piExtensions" },
488
532
  "governanceStandardReview": {
489
533
  "type": "boolean",
490
534
  "description": "Explicitly opts this node into writer-change-scoped AGENTS.md and repository-local code-standard review. Never inferred from node id or role."
@@ -66,12 +66,11 @@
66
66
  ".harness/**",
67
67
  "artifacts/**"
68
68
  ],
69
- "outputContract": "Write testcase/frontend/rag/standard-scenarios.v1.json for generate-time standard scenario coverage.",
70
- "subtask_prompt": "Materialize frontend-test standard scenarios v1 into the RAG package.",
69
+ "outputContract": "Write testcase/frontend/rag/standard-scenarios.v1.json for generate-time standard scenario coverage. Copy docs/templates or harness.json governanceRoot templates (including ai_workspace/loop-agent/templates) when present; otherwise write the minimal STD-FE-SMOKE-ENTRY fallback.",
70
+ "subtask_prompt": "Prepare frontend-test package: materialize standard-scenarios.v1.json into the RAG package from docs/templates, governanceRoot/templates, or the init-projected ai_workspace/loop-agent/templates path.",
71
71
  "shell": {
72
- "commands": [
73
- "node -e \"const fs=require('fs'),path=require('path');const dest='testcase/frontend/rag/standard-scenarios.v1.json';const candidates=[path.join('docs','templates','frontend-test-standard-scenarios.v1.json')];let src=null;for(const c of candidates){if(fs.existsSync(c)){src=c;break;}}fs.mkdirSync(path.dirname(dest),{recursive:true});if(src){fs.copyFileSync(src,dest);process.stdout.write(JSON.stringify({status:'copied',from:src,to:dest}));}else{const minimal={schemaVersion:1,id:'frontend-test-standard-scenarios-v1',scenarios:[{id:'STD-FE-SMOKE-ENTRY',title:'入口可打开',category:'smoke',priority:'must',testPoints:['open'],minCases:1}]};fs.writeFileSync(dest,JSON.stringify(minimal,null,2)+'\\n');process.stdout.write(JSON.stringify({status:'fallback',to:dest}));}"
74
- ],
72
+ "commands": [],
73
+ "frontendTestStandardScenarios": {},
75
74
  "cwd": ".",
76
75
  "timeoutMs": 60000
77
76
  }
@@ -120,12 +119,11 @@
120
119
  ".harness/**",
121
120
  "artifacts/**"
122
121
  ],
123
- "outputContract": "Fail-closed environment preflight: absolute non-production baseUrl + curl HTTP reachability; writes environmentProbe facts; unreachable => blockedReason frontend-base-url-unreachable (node ERROR so generate/map do not run).",
124
- "subtask_prompt": "Parse the concrete baseUrl selected by retrieve-frontend-test-context-pi from testcase/frontend/rag/context.md. Reject production, non-http(s), credentials, query and fragment. Probe with curl (HEAD then GET fallback; connect/max-time; no auth/cookie). 2xx/3xx => reachable and continue. 4xx/5xx/DNS/timeout/connection refused/TLS => blockedReason frontend-base-url-unreachable. Missing curl => blockedReason curl-unavailable. Do not start the app. Runtime hybrid generator embeds the authoritative probe script.",
122
+ "outputContract": "Fail-closed environment preflight: absolute non-production baseUrl + curl HTTP reachability; writes environmentProbe facts; unreachable => blockedReason frontend-base-url-unreachable with errorClass (connection-refused / dns-unresolved / connect-timeout / http-N). Node ERROR so generate/map do not run. Does not start the app.",
123
+ "subtask_prompt": "Parse the concrete baseUrl selected by retrieve-frontend-test-context-pi from testcase/frontend/rag/context.md. Reject production, non-http(s), credentials, query and fragment. Probe with curl (HEAD then GET fallback; connect/max-time; no auth/cookie). 2xx/3xx => reachable and continue. Connection refused records errorClass=connection-refused and tells the operator to start the local app then rerun from this node. 4xx/5xx/DNS/timeout/TLS => blockedReason frontend-base-url-unreachable. Missing curl => blockedReason curl-unavailable. Do not start the app.",
125
124
  "shell": {
126
- "commands": [
127
- "node -e \"console.log('template placeholder: runtime hybrid DAG embeds curl preflight; do not use this static command as source of truth')\""
128
- ],
125
+ "commands": [],
126
+ "frontendTestEnvironmentProbe": {},
129
127
  "cwd": ".",
130
128
  "timeoutMs": 60000
131
129
  }
@@ -17,5 +17,5 @@ Do not claim the environment is reachable until preflight completes. Preflight d
17
17
 
18
18
  ## Standard scenario coverage
19
19
 
20
- - Copy or reference `docs/templates/frontend-test-standard-scenarios.v1.json` into `testcase/frontend/rag/standard-scenarios.v1.json` when available.
20
+ - Copy or reference `docs/templates/frontend-test-standard-scenarios.v1.json`, `harness.json` `governanceRoot`/templates, or the init-projected `ai_workspace/loop-agent/templates/frontend-test-standard-scenarios.v1.json` into `testcase/frontend/rag/standard-scenarios.v1.json` when available.
21
21
  - Add `## Standard scenario coverage` to coverage-map.md with planned/n/a for each must scenario.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.39.0-next.25",
3
+ "version": "0.39.0-next.27",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -10,7 +10,7 @@ description: 用于只读 scout 节点,在实现前定位现有代码、测试
10
10
  ## 规则
11
11
 
12
12
  - 从 repo 指令、task source 与邻近测试入手。
13
- - 可用时优先 CodeGraph;否则用 `rg` 与聚焦文件阅读。
13
+ - 可用时优先 CodeGraph;否则 Shell 搜索优先 `rg`,按名找文件优先 `fd`,再聚焦阅读文件。
14
14
  - 在提议新抽象前,识别现有 helper 与 ownership 边界。
15
15
  - 只返回事实,不做编辑。
16
16