@tea-agent/loop-agent 0.25.6 → 0.26.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 (162) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +1020 -1006
  3. package/bin/loop-agent.js +21 -21
  4. package/dist/commands/cursor-prompt.js +6 -6
  5. package/dist/commands/loop-benchmark.js +11 -11
  6. package/dist/commands/pi-reuse-benchmark.js +16 -16
  7. package/dist/executors/dag-pi-executor.js +26 -20
  8. package/dist/executors/model-routing.js +34 -18
  9. package/dist/governance/manifest-types.js +33 -5
  10. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  11. package/dist/task/task-demand-routing.js +3 -1
  12. package/dist/worker/console/chat/model-resolver.js +15 -3
  13. package/dist/worker/observe/static/constants.js +3 -2
  14. package/dist/worker/observe/static/copy.js +67 -67
  15. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  16. package/dist/worker/observe/static/dag-layout.js +83 -83
  17. package/dist/worker/observe/static/dag-model.js +1 -0
  18. package/dist/worker/observe/static/dom.js +220 -220
  19. package/dist/worker/observe/static/relations.js +133 -133
  20. package/dist/worker/observe/static/router.js +93 -93
  21. package/dist/worker/observe/static/run-processing.js +148 -148
  22. package/dist/worker/observe/static/styles.css +182 -42
  23. package/dist/worker/observe/static/views/batch.js +227 -227
  24. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  25. package/dist/worker/observe/static/views/failures.js +143 -143
  26. package/dist/worker/observe/static/views/feature.js +492 -492
  27. package/dist/worker/observe/static/views/run.js +453 -453
  28. package/dist/worker/observe/static/views/shell.js +7 -7
  29. package/dist/worker/observe/static/views/timeline.js +163 -163
  30. package/dist/workflows/dag/canvas-observer.js +275 -275
  31. package/dist/workflows/dag/lifecycle.js +40 -30
  32. package/dist/workflows/dag/node-execution.js +13 -0
  33. package/dist/workflows/dag/types.js +59 -19
  34. package/docs/skills/README.md +7 -7
  35. package/docs/templates/adr.md +60 -60
  36. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  37. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  38. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  39. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  40. package/docs/templates/agent-dag-report.schema.json +473 -473
  41. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  42. package/docs/templates/backend-test-result.schema.json +99 -99
  43. package/docs/templates/feature-spec.md +53 -53
  44. package/docs/templates/frontend-design-contract.md +42 -42
  45. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  46. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  47. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  48. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  49. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  50. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  51. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  52. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  53. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  54. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  55. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  56. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  57. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  58. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  59. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  60. package/docs/templates/frontend-eval/metrics.md +138 -138
  61. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  62. package/docs/templates/frontend-task-constraints.md +35 -35
  63. package/docs/templates/frontend-task-requirement.md +70 -70
  64. package/docs/templates/harness.schema.json +29 -7
  65. package/docs/templates/init-evolution-review.md +35 -35
  66. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  67. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  68. package/docs/templates/knowledge-sync-dag.json +178 -178
  69. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  70. package/docs/templates/product-line/closeout.yaml +9 -9
  71. package/docs/templates/product-line/design.md +13 -13
  72. package/docs/templates/product-line/links.md +10 -10
  73. package/docs/templates/product-line/requirement.md +17 -17
  74. package/docs/templates/product-line/test-plan.md +7 -7
  75. package/docs/templates/production-readiness-checklist.md +57 -57
  76. package/docs/templates/project-start-checklist.md +9 -9
  77. package/docs/templates/qa-report.md +48 -48
  78. package/docs/templates/sprint-contract.md +29 -29
  79. package/docs/templates/worker-dogfood-evidence.md +80 -80
  80. package/docs/templates/worker-dogfood-setup.md +68 -68
  81. package/harness.json +1 -2
  82. package/package.json +1 -1
  83. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  84. package/scripts/kb-graph-incremental-prepare.mjs +386 -386
  85. package/scripts/kb-graph-materialize.mjs +105 -105
  86. package/scripts/kb-graph-promote.mjs +164 -164
  87. package/scripts/kb-query.mjs +554 -554
  88. package/skills/ai-engineering-context/SKILL.md +48 -48
  89. package/skills/analyze-product-dependencies/SKILL.md +67 -67
  90. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
  91. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
  92. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
  93. package/skills/analyze-product-dependencies/references/example.md +76 -76
  94. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
  95. package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
  96. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
  97. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
  98. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
  99. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
  100. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
  101. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
  102. package/skills/analyze-product-requirements/SKILL.md +90 -90
  103. package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
  104. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
  105. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
  106. package/skills/analyze-product-requirements/references/example.md +86 -86
  107. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
  108. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
  109. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
  110. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
  111. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
  112. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
  113. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
  114. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
  115. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
  116. package/skills/browser-tools/browser-content.js +103 -103
  117. package/skills/browser-tools/browser-cookies.js +35 -35
  118. package/skills/browser-tools/browser-eval.js +53 -53
  119. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  120. package/skills/browser-tools/browser-nav.js +44 -44
  121. package/skills/browser-tools/browser-pick.js +162 -162
  122. package/skills/browser-tools/browser-screenshot.js +34 -34
  123. package/skills/browser-tools/browser-start.js +86 -86
  124. package/skills/browser-tools/package-lock.json +2556 -2556
  125. package/skills/browser-tools/package.json +19 -19
  126. package/skills/code-review-core/SKILL.md +20 -20
  127. package/skills/codebase-scout/SKILL.md +19 -19
  128. package/skills/grill-me/SKILL.md +10 -10
  129. package/skills/loop-agent/references/README.md +67 -67
  130. package/skills/loop-agent/references/docs-converge.md +126 -126
  131. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  132. package/skills/loop-agent/references/learned/README.md +21 -21
  133. package/skills/loop-agent/references/long-running-loop.md +57 -57
  134. package/skills/loop-agent/references/model-routing.md +2 -0
  135. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  136. package/skills/loop-agent/references/pi-prompt.md +23 -23
  137. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  138. package/skills/playwright-cli/SKILL.md +420 -420
  139. package/skills/playwright-cli/references/element-attributes.md +23 -23
  140. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  141. package/skills/playwright-cli/references/request-mocking.md +87 -87
  142. package/skills/playwright-cli/references/running-code.md +241 -241
  143. package/skills/playwright-cli/references/session-management.md +225 -225
  144. package/skills/playwright-cli/references/storage-state.md +275 -275
  145. package/skills/playwright-cli/references/test-generation.md +433 -433
  146. package/skills/playwright-cli/references/tracing.md +139 -139
  147. package/skills/playwright-cli/references/video-recording.md +143 -143
  148. package/skills/requesting-code-review/SKILL.md +101 -101
  149. package/skills/requesting-code-review/code-reviewer.md +168 -168
  150. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  151. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  152. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  153. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  154. package/skills/systematic-debugging/find-polluter.sh +63 -63
  155. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  156. package/skills/systematic-debugging/test-academic.md +14 -14
  157. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  158. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  159. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  160. package/skills/using-git-worktrees/SKILL.md +215 -215
  161. package/skills/verification-before-completion/SKILL.md +154 -154
  162. package/skills/webapp-testing/SKILL.md +19 -19
package/bin/loop-agent.js CHANGED
@@ -1,21 +1,21 @@
1
- #!/usr/bin/env node
2
- import { existsSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath, pathToFileURL } from "node:url";
5
-
6
- const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
7
- const cliEntry = join(packageRoot, "dist", "cli.js");
8
-
9
- if (!existsSync(cliEntry)) {
10
- console.error(
11
- `loop-agent: cannot find built CLI at ${cliEntry}. Run \`npm run build\` before using the package bin.`,
12
- );
13
- process.exit(1);
14
- }
15
-
16
- try {
17
- await import(pathToFileURL(cliEntry).href);
18
- } catch (error) {
19
- console.error(error instanceof Error ? error.message : String(error));
20
- process.exit(1);
21
- }
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+
6
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
7
+ const cliEntry = join(packageRoot, "dist", "cli.js");
8
+
9
+ if (!existsSync(cliEntry)) {
10
+ console.error(
11
+ `loop-agent: cannot find built CLI at ${cliEntry}. Run \`npm run build\` before using the package bin.`,
12
+ );
13
+ process.exit(1);
14
+ }
15
+
16
+ try {
17
+ await import(pathToFileURL(cliEntry).href);
18
+ } catch (error) {
19
+ console.error(error instanceof Error ? error.message : String(error));
20
+ process.exit(1);
21
+ }
@@ -141,7 +141,7 @@ async function runCursorPromptBatch(task, cwd, model, timeoutMs) {
141
141
  }
142
142
  async function runCursorPromptStreaming(task, cwd, model, timeoutMs) {
143
143
  const startedAt = Date.now();
144
- process.stderr.write(`[cursor-prompt] streaming (model=${model}, cwd=${cwd})
144
+ process.stderr.write(`[cursor-prompt] streaming (model=${model}, cwd=${cwd})
145
145
  `);
146
146
  const runDir = (await computeRunDir(cwd, task)) ?? undefined;
147
147
  const result = await executeCursorPromptStream({
@@ -164,15 +164,15 @@ async function runCursorPromptStreaming(task, cwd, model, timeoutMs) {
164
164
  });
165
165
  const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
166
166
  if (result.ok) {
167
- process.stderr.write(`
168
- [cursor-prompt] done in ${elapsed}s, status=${result.status}
167
+ process.stderr.write(`
168
+ [cursor-prompt] done in ${elapsed}s, status=${result.status}
169
169
  `);
170
170
  }
171
171
  else {
172
172
  const stderr = result.stderr || "(no output)";
173
- process.stderr.write(`
174
- [cursor-prompt] FAILED in ${elapsed}s (${result.failureCategory}):
175
- ${stderr}
173
+ process.stderr.write(`
174
+ [cursor-prompt] FAILED in ${elapsed}s (${result.failureCategory}):
175
+ ${stderr}
176
176
  `);
177
177
  process.exit(1);
178
178
  }
@@ -37,17 +37,17 @@ export function parseLoopBenchmarkArgs(args) {
37
37
  return { json, markdown, outputPath };
38
38
  }
39
39
  export function printLoopBenchmarkUsage() {
40
- console.log(`usage: loop-benchmark [options]
41
-
42
- Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
43
-
44
- Options:
45
- --json Emit JSON (default when no format flag is set)
46
- --markdown Emit Markdown report
47
- --output <path> Write Markdown report to a repo-relative or absolute path
48
- -h, --help Show this help
49
-
50
- Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
40
+ console.log(`usage: loop-benchmark [options]
41
+
42
+ Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
43
+
44
+ Options:
45
+ --json Emit JSON (default when no format flag is set)
46
+ --markdown Emit Markdown report
47
+ --output <path> Write Markdown report to a repo-relative or absolute path
48
+ -h, --help Show this help
49
+
50
+ Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
51
51
  Recommendation never changes convergence.enabled default.`);
52
52
  }
53
53
  export async function runLoopBenchmark(repoRoot, rawArgs) {
@@ -106,22 +106,22 @@ export function parsePiReuseBenchmarkArgs(args) {
106
106
  };
107
107
  }
108
108
  export function printPiReuseBenchmarkUsage() {
109
- console.log(`usage: pi-reuse-benchmark [options]
110
-
111
- Deterministic Pi runtime reuse benchmark/decision summary (no live Pi calls).
112
-
113
- Options:
114
- --report <path> Benchmark report markdown (approval status)
115
- --approval <path> Explicit approval JSON artifact
116
- --off-executor <path> Baseline executor.jsonl (reuse off)
117
- --on-executor <path> Treatment executor.jsonl (reuse on)
118
- --off-task <task-id> Resolve baseline from .harness/tasks/<id>/logs/executor.jsonl
119
- --on-task <task-id> Resolve treatment from .harness/tasks/<id>/logs/executor.jsonl
120
- --json Emit JSON (default when no format flag is set)
121
- --markdown Emit Markdown summary
122
- -h, --help Show this help
123
-
124
- Recommendations: defer | maintain-opt-in | eligible-for-human-review
109
+ console.log(`usage: pi-reuse-benchmark [options]
110
+
111
+ Deterministic Pi runtime reuse benchmark/decision summary (no live Pi calls).
112
+
113
+ Options:
114
+ --report <path> Benchmark report markdown (approval status)
115
+ --approval <path> Explicit approval JSON artifact
116
+ --off-executor <path> Baseline executor.jsonl (reuse off)
117
+ --on-executor <path> Treatment executor.jsonl (reuse on)
118
+ --off-task <task-id> Resolve baseline from .harness/tasks/<id>/logs/executor.jsonl
119
+ --on-task <task-id> Resolve treatment from .harness/tasks/<id>/logs/executor.jsonl
120
+ --json Emit JSON (default when no format flag is set)
121
+ --markdown Emit Markdown summary
122
+ -h, --help Show this help
123
+
124
+ Recommendations: defer | maintain-opt-in | eligible-for-human-review
125
125
  Never changes CODE_AGENT_PI_REUSE_RUNTIME default (off).`);
126
126
  }
127
127
  function resolveRepoRelative(repoRoot, filePath) {
@@ -169,14 +169,25 @@ export function buildDagPiUserMessage(task, persona, step) {
169
169
  "Do not wrap the output in code fences and do not add conversational preamble.",
170
170
  ].join(" ");
171
171
  }
172
- function resolveDagPiModelConfig(model) {
173
- const provider = DAG_PI_MODEL_PROVIDERS[model] ?? DEFAULT_DAG_PI_PROVIDER;
172
+ export function resolveDagPiModelConfig(modelReference, options) {
173
+ const separatorIndex = modelReference.indexOf("/");
174
+ const qualified = separatorIndex >= 0;
175
+ const provider = qualified
176
+ ? modelReference.slice(0, separatorIndex)
177
+ : (DAG_PI_MODEL_PROVIDERS[modelReference] ?? DEFAULT_DAG_PI_PROVIDER);
178
+ const model = qualified
179
+ ? modelReference.slice(separatorIndex + 1)
180
+ : modelReference;
181
+ if (!provider || !model) {
182
+ throw new Error(`invalid DAG Pi model reference "${modelReference}": expected non-empty provider/model`);
183
+ }
184
+ const explicitThinking = options?.thinking?.trim();
185
+ const thinking = explicitThinking ??
186
+ (provider === "wizard-local" && model === "gpt-5.5" ? "low" : undefined);
174
187
  return {
175
188
  provider,
176
189
  model,
177
- ...(provider === "wizard-local" && model === "gpt-5.5"
178
- ? { thinking: "low" }
179
- : {}),
190
+ ...(thinking ? { thinking } : {}),
180
191
  };
181
192
  }
182
193
  const SUMMARY_STDOUT_MAX = 4_000;
@@ -291,7 +302,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
291
302
  : undefined;
292
303
  const result = await piStepFn({
293
304
  attachedFiles: [],
294
- modelConfig: resolveDagPiModelConfig(input.model),
305
+ modelConfig: resolveDagPiModelConfig(input.model, input.thinking ? { thinking: input.thinking } : undefined),
295
306
  prompt: input.prompt,
296
307
  repoRoot: input.cwd,
297
308
  step,
@@ -304,8 +315,8 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
304
315
  abortGraceMs: input.abortGraceMs,
305
316
  onActivity: bridgeActivity
306
317
  ? (activity) => {
307
- if (activity.kind === "lease"
308
- || activity.kind === "synthetic-heartbeat") {
318
+ if (activity.kind === "lease" ||
319
+ activity.kind === "synthetic-heartbeat") {
309
320
  return;
310
321
  }
311
322
  bridgeActivity(activity.kind, activity.at);
@@ -372,7 +383,9 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
372
383
  }
373
384
  }
374
385
  }
375
- if (writeGuardOk && beforeStatus !== undefined && changeManifestChangedFiles !== undefined) {
386
+ if (writeGuardOk &&
387
+ beforeStatus !== undefined &&
388
+ changeManifestChangedFiles !== undefined) {
376
389
  await persistWriterChangeManifest({
377
390
  runDir: meta.runDir,
378
391
  nodeId: input.task.id,
@@ -445,12 +458,8 @@ function parseWriterImplementationOutcome(text) {
445
458
  const normalized = normalizeProtocolLine(line, WRITER_OUTCOME_PROTOCOL_LINE, nextLine);
446
459
  if (normalized === undefined)
447
460
  continue;
448
- const value = normalized
449
- .slice(WRITER_OUTCOME_PROTOCOL_LINE.length)
450
- .trim();
451
- const outcome = isWriterImplementationOutcome(value)
452
- ? value
453
- : undefined;
461
+ const value = normalized.slice(WRITER_OUTCOME_PROTOCOL_LINE.length).trim();
462
+ const outcome = isWriterImplementationOutcome(value) ? value : undefined;
454
463
  candidates.push({
455
464
  lineIndex,
456
465
  value,
@@ -474,9 +483,7 @@ function parseWriterImplementationOutcome(text) {
474
483
  };
475
484
  }
476
485
  function isWriterImplementationOutcome(value) {
477
- return (value === "changed" ||
478
- value === "already-satisfied" ||
479
- value === "blocked");
486
+ return (value === "changed" || value === "already-satisfied" || value === "blocked");
480
487
  }
481
488
  function writerOutcomeDiagnostics(text, parsed, changedFiles) {
482
489
  const firstNonEmpty = text
@@ -543,8 +550,7 @@ function canonicalizeProtocolFirstLine(assistantText, firstProtocolLine) {
543
550
  if (protocolNextLineIndex === protocolIndex + 1) {
544
551
  after.shift();
545
552
  }
546
- while (before.at(-1)?.trim() === "" &&
547
- after.at(0)?.trim() === "") {
553
+ while (before.at(-1)?.trim() === "" && after.at(0)?.trim() === "") {
548
554
  after.shift();
549
555
  }
550
556
  const bodyLines = [...before, ...after];
@@ -1,4 +1,5 @@
1
- import { DEFAULT_DAG_EXECUTOR_MODELS, } from '../workflows/dag/types.js';
1
+ import { DEFAULT_DAG_EXECUTOR_MODELS, } from "../workflows/dag/types.js";
2
+ import { normalizeExecutorTierValue, } from "../governance/manifest-types.js";
2
3
  export const DEFAULT_DAG_MODELS = {
3
4
  HIGH: "gpt-5.5",
4
5
  MED: "gpt-5.5",
@@ -8,6 +9,16 @@ export const DEFAULT_DAG_MODELS = {
8
9
  * DAG executor model tier keys that may carry a per-complexity override.
9
10
  */
10
11
  const EXECUTOR_MODEL_TIERS = ["LOW", "MED", "HIGH"];
12
+ /** Resolve one tier to a model and optional explicit thinking override. */
13
+ export function resolveExecutorTierSelection(execConfig, tier) {
14
+ const tierSelection = normalizeExecutorTierValue(execConfig?.[tier]);
15
+ if (tierSelection)
16
+ return tierSelection;
17
+ const defaultModel = execConfig?.defaultModel;
18
+ if (defaultModel && defaultModel !== "default")
19
+ return { model: defaultModel };
20
+ return { model: DEFAULT_DAG_EXECUTOR_MODELS.pi[tier] };
21
+ }
11
22
  /**
12
23
  * Resolve the DAG executor model matrix for Pi from harness `executors.pi`.
13
24
  *
@@ -19,22 +30,23 @@ const EXECUTOR_MODEL_TIERS = ["LOW", "MED", "HIGH"];
19
30
  * The "default" literal (injected by the schema `.default("default")`) and
20
31
  * absent/undefined both mean "no override, fall through".
21
32
  */
22
- export function resolveExecutorModelMatrix(executor, execConfig) {
23
- const tierValue = (tier) => {
24
- const tierOverride = execConfig?.[tier];
25
- if (tierOverride && tierOverride !== "default")
26
- return tierOverride;
27
- const defaultModel = execConfig?.defaultModel;
28
- if (defaultModel && defaultModel !== "default")
29
- return defaultModel;
30
- return DEFAULT_DAG_EXECUTOR_MODELS[executor][tier];
31
- };
33
+ export function resolveExecutorModelMatrix(_executor, execConfig) {
32
34
  return {
33
- LOW: tierValue("LOW"),
34
- MED: tierValue("MED"),
35
- HIGH: tierValue("HIGH"),
35
+ LOW: resolveExecutorTierSelection(execConfig, "LOW").model,
36
+ MED: resolveExecutorTierSelection(execConfig, "MED").model,
37
+ HIGH: resolveExecutorTierSelection(execConfig, "HIGH").model,
36
38
  };
37
39
  }
40
+ /** Resolve only explicitly configured per-tier thinking values. */
41
+ export function resolveExecutorThinkingMatrix(execConfig) {
42
+ const result = {};
43
+ for (const tier of EXECUTOR_MODEL_TIERS) {
44
+ const selection = normalizeExecutorTierValue(execConfig?.[tier]);
45
+ if (selection?.thinking)
46
+ result[tier] = selection.thinking;
47
+ }
48
+ return result;
49
+ }
38
50
  /**
39
51
  * Resolve the Pi DAG executor model matrix from a harness manifest.
40
52
  */
@@ -55,7 +67,9 @@ export function resolveModelSelection(manifest, taskConfig, step, options) {
55
67
  };
56
68
  }
57
69
  const profileName = resolveProfileName(manifest, taskConfig.complexity, step, retryAttempt);
58
- const profile = profileName ? manifest.modelProfiles?.[profileName] : undefined;
70
+ const profile = profileName
71
+ ? manifest.modelProfiles?.[profileName]
72
+ : undefined;
59
73
  if (!profile) {
60
74
  return {
61
75
  modelConfig: manifest.models?.[step],
@@ -74,7 +88,9 @@ export function resolveModelSelection(manifest, taskConfig, step, options) {
74
88
  };
75
89
  }
76
90
  function resolveProfileName(manifest, complexity, step, retryAttempt) {
77
- if (step === 'implement' && retryAttempt > 0 && manifest.modelRouting?.implementRetry) {
91
+ if (step === "implement" &&
92
+ retryAttempt > 0 &&
93
+ manifest.modelRouting?.implementRetry) {
78
94
  return manifest.modelRouting.implementRetry;
79
95
  }
80
96
  const route = manifest.modelRouting?.[step];
@@ -85,11 +101,11 @@ function resolveProfileName(manifest, complexity, step, retryAttempt) {
85
101
  }
86
102
  export function formatModelSelectionLabel(modelConfig, profileName) {
87
103
  if (!modelConfig) {
88
- return profileName ? `${profileName}` : 'default';
104
+ return profileName ? `${profileName}` : "default";
89
105
  }
90
106
  const base = modelConfig.provider && modelConfig.model
91
107
  ? `${modelConfig.provider}/${modelConfig.model}`
92
- : modelConfig.model ?? 'default';
108
+ : (modelConfig.model ?? "default");
93
109
  return profileName ? `${profileName}:${base}` : base;
94
110
  }
95
111
  export function formatFallbackLabel(profile) {
@@ -18,6 +18,34 @@ export const worktreeManifestConfigSchema = z.object({
18
18
  });
19
19
  export const taskExecutorSchema = z.enum(["pi"]);
20
20
  export const CURSOR_TASK_EXECUTOR_REMOVED_ERROR = 'task executor "cursor" is no longer supported; governed runtime is Pi-only';
21
+ /** Per-tier model override: bare model id string, or `{ model, thinking? }`. */
22
+ export const executorTierModelSchema = z.union([
23
+ z.string(),
24
+ z.object({
25
+ model: z.string().min(1),
26
+ thinking: z.string().optional(),
27
+ }),
28
+ ]);
29
+ /** Normalize an executors.pi LOW|MED|HIGH value. */
30
+ export function normalizeExecutorTierValue(value) {
31
+ if (value === null || value === undefined)
32
+ return undefined;
33
+ if (typeof value === "string") {
34
+ if (value === "default" || value.length === 0)
35
+ return undefined;
36
+ return { model: value };
37
+ }
38
+ if (typeof value !== "object" || Array.isArray(value))
39
+ return undefined;
40
+ const model = value.model;
41
+ if (typeof model !== "string" || model.length === 0 || model === "default") {
42
+ return undefined;
43
+ }
44
+ const thinking = value.thinking;
45
+ return typeof thinking === "string" && thinking.length > 0
46
+ ? { model, thinking }
47
+ : { model };
48
+ }
21
49
  export const executorManifestSchema = z.object({
22
50
  description: z.string().optional(),
23
51
  enabled: z.boolean().optional(),
@@ -27,9 +55,9 @@ export const executorManifestSchema = z.object({
27
55
  * these take priority over defaultModel for the matching DAG executor tier.
28
56
  * "default" literal and absent/undefined both mean "no override, fall through".
29
57
  */
30
- LOW: z.string().optional(),
31
- MED: z.string().optional(),
32
- HIGH: z.string().optional(),
58
+ LOW: executorTierModelSchema.optional(),
59
+ MED: executorTierModelSchema.optional(),
60
+ HIGH: executorTierModelSchema.optional(),
33
61
  requiresApiKey: z.string().optional(),
34
62
  });
35
63
  export const workflowPolicyProfileNameSchema = z.enum([
@@ -98,7 +126,7 @@ export const workflowPolicySchema = z
98
126
  })
99
127
  .optional()
100
128
  .default({});
101
- export const CURSOR_HARNESS_EXECUTOR_REMOVED_ERROR = 'harness executors.cursor is no longer supported; remove it and use executors.pi only (Cursor is available only via cursor-prompt sidecar)';
129
+ export const CURSOR_HARNESS_EXECUTOR_REMOVED_ERROR = "harness executors.cursor is no longer supported; remove it and use executors.pi only (Cursor is available only via cursor-prompt sidecar)";
102
130
  export const harnessManifestSchema = z
103
131
  .object({
104
132
  version: z.number(),
@@ -160,7 +188,7 @@ export const harnessManifestSchema = z
160
188
  if ("cursorExecutorUsage" in entrypoints) {
161
189
  ctx.addIssue({
162
190
  code: z.ZodIssueCode.custom,
163
- message: 'entrypoints.cursorExecutorUsage is no longer supported; remove it (Cursor is cursor-prompt sidecar only)',
191
+ message: "entrypoints.cursorExecutorUsage is no longer supported; remove it (Cursor is cursor-prompt sidecar only)",
164
192
  path: ["entrypoints", "cursorExecutorUsage"],
165
193
  });
166
194
  }
@@ -29,7 +29,7 @@ export function resolveArtifactWriteDir(options) {
29
29
  }
30
30
  export function buildArtifactPathPrompt(writeDir) {
31
31
  if (!writeDir)
32
- return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
32
+ return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
33
33
  ${ARTIFACT_INSTRUCTIONS}`;
34
34
  return [
35
35
  `After changes, write the following files:`,
@@ -156,7 +156,7 @@ function isDocumentationOnlyClause(clause) {
156
156
  if (DOCUMENTATION_ONLY_MARKERS.test(clause)) {
157
157
  return !PRODUCT_AND_DOCUMENT_DELIVERY.test(clause);
158
158
  }
159
- return TEST_ONLY_MARKERS.test(clause) && !PRODUCT_AND_TEST_DELIVERY.test(clause);
159
+ return (TEST_ONLY_MARKERS.test(clause) && !PRODUCT_AND_TEST_DELIVERY.test(clause));
160
160
  }
161
161
  function masksExistingBackendDependency(clause) {
162
162
  return clause
@@ -335,6 +335,8 @@ export function classifyTaskDemand(input) {
335
335
  }
336
336
  const backendDelivery = titleSignals.backendDelivery || requirementSignals.backendDelivery;
337
337
  const frontendProjectDefaultImplementation = hasStrongFrontendProjectEvidence &&
338
+ frontendPath &&
339
+ !hasBackendTaskType &&
338
340
  !backendDelivery &&
339
341
  !frontendNegated &&
340
342
  !allowedPathsOnlyCoverNonProductArtifacts;
@@ -20,6 +20,18 @@ import { readFile } from "node:fs/promises";
20
20
  import path from "node:path";
21
21
  /** Tier keys in harness executors.pi that may override the default. */
22
22
  const EXECUTOR_TIERS = ["LOW", "MED", "HIGH"];
23
+ function modelIdFromTierValue(value) {
24
+ if (typeof value === "string") {
25
+ return value && value !== "default" ? value : undefined;
26
+ }
27
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
28
+ return undefined;
29
+ }
30
+ const model = value.model;
31
+ return typeof model === "string" && model && model !== "default"
32
+ ? model
33
+ : undefined;
34
+ }
23
35
  /**
24
36
  * Read executors.pi.<tier> from the repo harness.json. Returns undefined when
25
37
  * harness.json is absent or the tier is unset / "default" sentinel.
@@ -33,9 +45,9 @@ export async function readHarnessPiModelTier(repoRoot, tier = "MED") {
33
45
  const pi = manifest.executors?.pi;
34
46
  if (!pi)
35
47
  return undefined;
36
- const tierValue = pi[tier];
37
- if (tierValue && tierValue !== "default")
38
- return tierValue;
48
+ const tierModel = modelIdFromTierValue(pi[tier]);
49
+ if (tierModel)
50
+ return tierModel;
39
51
  const defaultModel = pi.defaultModel;
40
52
  if (defaultModel && defaultModel !== "default")
41
53
  return defaultModel;
@@ -67,8 +67,8 @@ export const STATUS_LABELS = {
67
67
  done: "完成",
68
68
  failed: "失败",
69
69
  error: "错误",
70
- partial_failed: "部分失败",
71
- partialfailed: "部分失败",
70
+ partial_failed: "部分成功",
71
+ partialfailed: "部分成功",
72
72
  blocked: "阻塞",
73
73
  stale: "心跳失联",
74
74
  reused: "复用",
@@ -104,6 +104,7 @@ export const DAG_EFFECTIVE_STATUS_LABELS = {
104
104
  interrupted: "执行已中断",
105
105
  "remote-unknown": "远端状态未知",
106
106
  finished: "已完成",
107
+ partial_failed: "部分成功",
107
108
  failed: "执行失败",
108
109
  superseded: "任务已另行完成",
109
110
  abandoned: "已放弃",
@@ -1,67 +1,67 @@
1
- /** Copy-only recommended commands (Observe UI R5). */
2
- import { el } from "./dom.js";
3
-
4
- export async function copyText(text) {
5
- const value = String(text ?? "");
6
- if (!value) return false;
7
- try {
8
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
9
- await navigator.clipboard.writeText(value);
10
- return true;
11
- }
12
- } catch {
13
- // fall through
14
- }
15
- try {
16
- const ta = document.createElement("textarea");
17
- ta.value = value;
18
- ta.setAttribute("readonly", "");
19
- ta.style.position = "fixed";
20
- ta.style.left = "-9999px";
21
- document.body.appendChild(ta);
22
- ta.select();
23
- const ok = document.execCommand("copy");
24
- document.body.removeChild(ta);
25
- return ok;
26
- } catch {
27
- return false;
28
- }
29
- }
30
-
31
- /** Advisory recommended command: copy-only, never executes. */
32
- export function renderRecommendedCommand(action) {
33
- const block = el("div", "recommended-command");
34
- if (!action) {
35
- block.appendChild(el("span", "muted", "缺失"));
36
- return block;
37
- }
38
- const head = el("div", "recommended-command-head");
39
- if (action.kind) {
40
- head.appendChild(el("span", "recommended-command-kind", action.kind));
41
- }
42
- head.appendChild(
43
- el("span", "recommended-command-label", action.label ?? "—"),
44
- );
45
- block.appendChild(head);
46
- if (action.command) {
47
- const body = el("div", "recommended-command-body");
48
- const code = el("code", "recommended-command-text", action.command);
49
- body.appendChild(code);
50
- const btn = el("button", "copy-command", "复制");
51
- btn.type = "button";
52
- btn.addEventListener("click", (e) => {
53
- e.preventDefault();
54
- e.stopPropagation();
55
- void copyText(action.command).then((ok) => {
56
- btn.textContent = ok ? "已复制" : "复制失败";
57
- setTimeout(() => {
58
- btn.textContent = "复制";
59
- }, 1500);
60
- });
61
- });
62
- body.appendChild(btn);
63
- block.appendChild(body);
64
- }
65
- return block;
66
- }
67
-
1
+ /** Copy-only recommended commands (Observe UI R5). */
2
+ import { el } from "./dom.js";
3
+
4
+ export async function copyText(text) {
5
+ const value = String(text ?? "");
6
+ if (!value) return false;
7
+ try {
8
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
9
+ await navigator.clipboard.writeText(value);
10
+ return true;
11
+ }
12
+ } catch {
13
+ // fall through
14
+ }
15
+ try {
16
+ const ta = document.createElement("textarea");
17
+ ta.value = value;
18
+ ta.setAttribute("readonly", "");
19
+ ta.style.position = "fixed";
20
+ ta.style.left = "-9999px";
21
+ document.body.appendChild(ta);
22
+ ta.select();
23
+ const ok = document.execCommand("copy");
24
+ document.body.removeChild(ta);
25
+ return ok;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /** Advisory recommended command: copy-only, never executes. */
32
+ export function renderRecommendedCommand(action) {
33
+ const block = el("div", "recommended-command");
34
+ if (!action) {
35
+ block.appendChild(el("span", "muted", "缺失"));
36
+ return block;
37
+ }
38
+ const head = el("div", "recommended-command-head");
39
+ if (action.kind) {
40
+ head.appendChild(el("span", "recommended-command-kind", action.kind));
41
+ }
42
+ head.appendChild(
43
+ el("span", "recommended-command-label", action.label ?? "—"),
44
+ );
45
+ block.appendChild(head);
46
+ if (action.command) {
47
+ const body = el("div", "recommended-command-body");
48
+ const code = el("code", "recommended-command-text", action.command);
49
+ body.appendChild(code);
50
+ const btn = el("button", "copy-command", "复制");
51
+ btn.type = "button";
52
+ btn.addEventListener("click", (e) => {
53
+ e.preventDefault();
54
+ e.stopPropagation();
55
+ void copyText(action.command).then((ok) => {
56
+ btn.textContent = ok ? "已复制" : "复制失败";
57
+ setTimeout(() => {
58
+ btn.textContent = "复制";
59
+ }, 1500);
60
+ });
61
+ });
62
+ body.appendChild(btn);
63
+ block.appendChild(body);
64
+ }
65
+ return block;
66
+ }
67
+