@tea-agent/loop-agent 0.35.1-beta.0 → 0.35.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +110 -108
- package/CHANGELOG.md +24 -26
- package/README.md +165 -165
- package/bin/agent-worker.js +0 -0
- package/bin/loop-agent.js +57 -21
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/build-stamp.json +6 -0
- package/dist/cli/program.js +2 -2
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +44 -0
- package/dist/shared/operator/capabilities.js +1 -38
- package/dist/shared/package-metadata.js +42 -0
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/worker/console/chat/pi-runtime.js +25 -41
- package/dist/worker/console/chat/routes.js +4 -27
- package/dist/worker/console/operation-runner.js +0 -24
- package/dist/worker/console/operator-actions.js +0 -58
- package/dist/worker/console/static/assets/index-CvsQgALl.js +56 -0
- package/dist/worker/console/static/assets/{index-Dups4sSM.css → index-hJqCPs_g.css} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -13
- package/dist/worker/console/static-src/operator-chat/useComposer.js +7 -30
- package/dist/worker/loop-agent/loop-agent-client.js +17 -3
- package/dist/worker/observability/read-model.js +20 -0
- package/dist/worker/observe/static/copy.js +67 -67
- package/dist/worker/observe/static/dag-layout.d.ts +36 -36
- package/dist/worker/observe/static/dom.js +220 -220
- package/dist/worker/observe/static/relations.js +133 -133
- package/dist/worker/observe/static/run-processing.js +148 -148
- package/dist/worker/observe/static/views/batch.js +227 -227
- package/dist/worker/observe/static/views/failures.js +143 -143
- package/dist/worker/observe/static/views/feature.js +492 -492
- package/dist/worker/observe/static/views/run.js +453 -453
- package/dist/worker/observe/static/views/shell.js +7 -7
- package/dist/worker/observe/static/views/timeline.js +163 -163
- package/dist/worker/preflight.js +2 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +33 -23
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/contract-output-registry.js +14 -0
- package/dist/workflows/dag/contract-validator-registrations.js +8 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +9 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +233 -39
- package/dist/workflows/dag/frontend-prewrite-gate.js +364 -61
- package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
- package/dist/workflows/dag/frontend-recovery-run.js +539 -0
- package/dist/workflows/dag/frontend-repair.js +219 -18
- package/dist/workflows/dag/frontend-verification-trace.js +47 -32
- package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
- package/dist/workflows/dag/init-hybrid.js +41 -24
- package/dist/workflows/dag/node-execution.js +89 -0
- package/dist/workflows/dag/recovery-recommendation.js +58 -0
- package/dist/workflows/dag/runner.js +245 -11
- package/dist/workflows/dag/scheduler.js +257 -3
- package/dist/workflows/dag/types.js +130 -2
- package/docs/architecture/evolution.md +73 -73
- package/docs/architecture/system-overview.md +100 -100
- package/docs/architecture/worker-and-feature.md +122 -122
- package/docs/skills/README.md +7 -7
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +473 -473
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/backend-test-result.schema.json +99 -99
- package/docs/templates/evaluation/agents-map-slim-v1.md +87 -87
- package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -153
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/frontend-design-contract.md +42 -42
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
- package/docs/templates/frontend-eval/metrics.md +138 -138
- package/docs/templates/frontend-eval/smoke-targets.md +53 -53
- package/docs/templates/frontend-task-constraints.md +35 -35
- package/docs/templates/frontend-task-requirement.md +70 -70
- package/docs/templates/init-evolution-review.md +35 -35
- package/docs/templates/init-managed-agents.md +154 -156
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
- package/docs/templates/knowledge-sync-dag.json +178 -178
- package/docs/templates/knowledge-sync-draft.schema.json +71 -71
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +80 -80
- package/docs/templates/worker-dogfood-setup.md +68 -68
- package/harness.json +2 -5
- package/package.json +2 -2
- package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
- package/scripts/kb-graph-incremental-prepare.mjs +0 -0
- package/scripts/kb-graph-materialize.mjs +105 -105
- package/scripts/kb-graph-promote.mjs +164 -164
- package/scripts/kb-query.mjs +554 -554
- package/skills/agent-worker/SKILL.md +48 -48
- package/skills/agent-worker/references/agent-worker-operator.md +159 -159
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +0 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +0 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +0 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +0 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +0 -0
- package/skills/browser-tools/browser-content.js +103 -103
- package/skills/browser-tools/browser-cookies.js +35 -35
- package/skills/browser-tools/browser-eval.js +53 -53
- package/skills/browser-tools/browser-hn-scraper.js +108 -108
- package/skills/browser-tools/browser-nav.js +44 -44
- package/skills/browser-tools/browser-pick.js +162 -162
- package/skills/browser-tools/browser-screenshot.js +34 -34
- package/skills/browser-tools/browser-start.js +86 -86
- package/skills/browser-tools/package-lock.json +2556 -2556
- package/skills/browser-tools/package.json +19 -19
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +0 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +0 -0
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +639 -641
- package/skills/loop-agent/references/docs-converge.md +126 -126
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
- package/skills/playwright-cli/references/element-attributes.md +23 -23
- package/skills/playwright-cli/references/playwright-tests.md +39 -39
- package/skills/playwright-cli/references/request-mocking.md +87 -87
- package/skills/playwright-cli/references/running-code.md +241 -241
- package/skills/playwright-cli/references/session-management.md +225 -225
- package/skills/playwright-cli/references/storage-state.md +275 -275
- package/skills/playwright-cli/references/test-generation.md +433 -433
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/using-git-worktrees/SKILL.md +215 -215
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
- package/dist/worker/console/operation-wait.js +0 -241
- package/dist/worker/console/static/assets/index-SjjjZnV3.js +0 -56
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
|
@@ -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) {
|
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
export const DEFAULT_RUN_DAG_PROGRESS_INTERVAL_MS = 30_000;
|
|
2
|
-
/**
|
|
3
|
-
* Contract floor for periodic progress output. Values below this are rejected
|
|
4
|
-
* at CLI parse time (aligned with `dag execute`'s parseProgressIntervalMs).
|
|
5
|
-
* Lives here (not src/application/dag/args.ts) because task-advance shares it
|
|
6
|
-
* and src/application/dag/args.ts is outside the task-advance write boundary.
|
|
7
|
-
*/
|
|
8
|
-
export const MIN_RUN_DAG_PROGRESS_INTERVAL_MS = 1_000;
|
|
9
|
-
/** Validate a progress interval; throws with the dag execute error contract. */
|
|
10
|
-
export function validateRunDagProgressIntervalMs(value) {
|
|
11
|
-
if (!Number.isInteger(value) || value < MIN_RUN_DAG_PROGRESS_INTERVAL_MS) {
|
|
12
|
-
throw new Error("progress-interval-ms must be an integer >= 1000");
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
2
|
function formatDuration(durationMs) {
|
|
17
3
|
const totalSeconds = Math.max(0, Math.floor(durationMs / 1_000));
|
|
18
4
|
const hours = Math.floor(totalSeconds / 3_600);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { advanceTaskLifecycle, } from "../application/task-lifecycle/index.js";
|
|
2
2
|
import { buildOperatorResult, operatorFailed, processExitCodeForOutcome, writeOperatorJson, } from "../shared/operator/index.js";
|
|
3
|
-
import { createRunDagProgressObserver, validateRunDagProgressIntervalMs, } from "./run-dag-progress.js";
|
|
4
3
|
const COMMAND = "task advance";
|
|
5
4
|
const USAGE = `usage:
|
|
6
5
|
task advance <task-id> [title]
|
|
@@ -25,8 +24,6 @@ const USAGE = `usage:
|
|
|
25
24
|
[--dag-output <path>]
|
|
26
25
|
[--skip-finalize]
|
|
27
26
|
[--no-strict-models]
|
|
28
|
-
[--quiet]
|
|
29
|
-
[--progress-interval-ms <ms>]
|
|
30
27
|
[--dry-run]
|
|
31
28
|
[--json]`;
|
|
32
29
|
function pushList(target, value) {
|
|
@@ -173,19 +170,6 @@ export function parseTaskAdvanceArgs(args) {
|
|
|
173
170
|
options.strictModels = false;
|
|
174
171
|
continue;
|
|
175
172
|
}
|
|
176
|
-
if (token === "--quiet") {
|
|
177
|
-
options.quiet = true;
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (token === "--progress-interval-ms") {
|
|
181
|
-
const raw = next();
|
|
182
|
-
const parsed = Number(raw);
|
|
183
|
-
if (!Number.isFinite(parsed)) {
|
|
184
|
-
throw new Error(`progress-interval-ms must be an integer >= 1000\n${USAGE}`);
|
|
185
|
-
}
|
|
186
|
-
options.progressIntervalMs = validateRunDagProgressIntervalMs(parsed);
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
173
|
if (token === "--help" || token === "-h") {
|
|
190
174
|
throw new Error(USAGE);
|
|
191
175
|
}
|
|
@@ -224,7 +208,7 @@ function mapOutcome(result) {
|
|
|
224
208
|
return "blocked";
|
|
225
209
|
return "succeeded";
|
|
226
210
|
}
|
|
227
|
-
|
|
211
|
+
function toUseCaseInput(repoRoot, taskId, options) {
|
|
228
212
|
const timeouts = new Map((options.verifyTimeout ?? []).map((entry) => {
|
|
229
213
|
const parsed = parseVerifyTimeout(entry);
|
|
230
214
|
return [parsed.label, parsed.timeoutMs];
|
|
@@ -286,8 +270,7 @@ export function toUseCaseInput(repoRoot, taskId, options, observer) {
|
|
|
286
270
|
dagOutputPath: options.dagOutputPath,
|
|
287
271
|
skipFinalize: options.skipFinalize,
|
|
288
272
|
strictModels: options.strictModels,
|
|
289
|
-
|
|
290
|
-
onProgress: options.quiet
|
|
273
|
+
onProgress: options.json
|
|
291
274
|
? undefined
|
|
292
275
|
: (message) => {
|
|
293
276
|
process.stderr.write(`[task advance] ${message}\n`);
|
|
@@ -312,18 +295,8 @@ export async function runTaskAdvance(repoRoot, args) {
|
|
|
312
295
|
process.exitCode = processExitCodeForOutcome(envelope.outcome);
|
|
313
296
|
return;
|
|
314
297
|
}
|
|
315
|
-
let progress;
|
|
316
298
|
try {
|
|
317
|
-
|
|
318
|
-
// the single final OperatorCommandResultV1 JSON). The observer is created
|
|
319
|
-
// ONLY for the approve-gate execution path and disposed on every exit;
|
|
320
|
-
// its timer starts only after onRunStart (double guard, no stray timer).
|
|
321
|
-
if (options.approveGate && !options.dryRun && !options.quiet) {
|
|
322
|
-
progress = createRunDagProgressObserver({
|
|
323
|
-
intervalMs: options.progressIntervalMs,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
const result = await advanceTaskLifecycle(toUseCaseInput(repoRoot, taskId, options, progress?.observer));
|
|
299
|
+
const result = await advanceTaskLifecycle(toUseCaseInput(repoRoot, taskId, options));
|
|
327
300
|
const outcome = mapOutcome(result);
|
|
328
301
|
const gateStop = result.lifecycleState === "awaiting-write-set-approval" &&
|
|
329
302
|
result.blockers.length === 0;
|
|
@@ -359,7 +332,4 @@ export async function runTaskAdvance(repoRoot, args) {
|
|
|
359
332
|
writeOperatorJson(envelope);
|
|
360
333
|
process.exitCode = processExitCodeForOutcome(envelope.outcome);
|
|
361
334
|
}
|
|
362
|
-
finally {
|
|
363
|
-
progress?.dispose();
|
|
364
|
-
}
|
|
365
335
|
}
|
|
@@ -520,6 +520,33 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
520
520
|
}
|
|
521
521
|
let result;
|
|
522
522
|
try {
|
|
523
|
+
// Phase 5 (P1-9): capture the writeSet baseline BEFORE the writer provider
|
|
524
|
+
// call so a transient partial write can be rolled back and recovered via a
|
|
525
|
+
// staged child. Only frontend-implementation writers; a failed capture is a
|
|
526
|
+
// hard fail (no baseline → no rollback → no recovery). Dynamic import avoids
|
|
527
|
+
// an executor ↔ scheduler static cycle.
|
|
528
|
+
if (isWriteTask &&
|
|
529
|
+
(input.task.id === "frontend-implement-pi" ||
|
|
530
|
+
input.task.id === "frontend-repair-pi") &&
|
|
531
|
+
(input.task.writeSet?.length ?? 0) > 0) {
|
|
532
|
+
try {
|
|
533
|
+
const { captureFrontendWriterAttemptIntent } = await import("../workflows/dag/frontend-writer-recovery.js");
|
|
534
|
+
await captureFrontendWriterAttemptIntent({
|
|
535
|
+
cwd: input.cwd,
|
|
536
|
+
runDir: meta.runDir,
|
|
537
|
+
writeSet: input.task.writeSet,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
return {
|
|
542
|
+
ok: false,
|
|
543
|
+
stdout: "",
|
|
544
|
+
stderr: `frontend writer attempt baseline capture failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
545
|
+
failureCategory: "write-guard",
|
|
546
|
+
durationMs: Date.now() - started,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
523
550
|
result = await piStepFn({
|
|
524
551
|
attachedFiles: [],
|
|
525
552
|
modelConfig: resolveDagPiModelConfig(input.model, input.thinking ? { thinking: input.thinking } : undefined),
|
|
@@ -691,6 +718,23 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
691
718
|
const changedFiles = pathsChangedDuringRun(snapshotGitStatusPorcelain(beforeStatus), afterSnapshot, beforePathFingerprints, afterPathFingerprints);
|
|
692
719
|
changeManifestAfterStatus = afterStatus;
|
|
693
720
|
changeManifestChangedFiles = changedFiles;
|
|
721
|
+
// Phase 5: record the attempt changed paths/hashes into the rollback
|
|
722
|
+
// intent so a transient partial write can be CAS-restored.
|
|
723
|
+
if ((input.task.id === "frontend-implement-pi" ||
|
|
724
|
+
input.task.id === "frontend-repair-pi") &&
|
|
725
|
+
(input.task.writeSet?.length ?? 0) > 0) {
|
|
726
|
+
try {
|
|
727
|
+
const { recordFrontendWriterAttempt } = await import("../workflows/dag/frontend-writer-recovery.js");
|
|
728
|
+
await recordFrontendWriterAttempt({
|
|
729
|
+
cwd: input.cwd,
|
|
730
|
+
runDir: meta.runDir,
|
|
731
|
+
changedPaths: changedFiles,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
// best-effort: a missing intent (no baseline captured) is a no-op
|
|
736
|
+
}
|
|
737
|
+
}
|
|
694
738
|
const guard = validateShellWriteGuardFromDiff({
|
|
695
739
|
changedFiles,
|
|
696
740
|
task: input.task,
|
|
@@ -445,43 +445,6 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
445
445
|
modelCallable: "always",
|
|
446
446
|
humanConfirmation: "none",
|
|
447
447
|
},
|
|
448
|
-
{
|
|
449
|
-
action: "operationWait",
|
|
450
|
-
cli: "console canonical operation wait (read-only event-driven long poll)",
|
|
451
|
-
kind: "read",
|
|
452
|
-
inputSchemaVersion: 1,
|
|
453
|
-
resultSchemaVersion: 1,
|
|
454
|
-
resultPolicy: { readOnly: true, bounded: true, redacted: true },
|
|
455
|
-
envelopeSchemaVersion: 1,
|
|
456
|
-
requiredErrorCodes: [
|
|
457
|
-
"NOT_FOUND",
|
|
458
|
-
"INVALID_INPUT",
|
|
459
|
-
"EVENT_CURSOR_EXPIRED",
|
|
460
|
-
],
|
|
461
|
-
description: "Read-only event-driven wait on the canonical operation event ring. Returns immediately on existing events, terminal state or needs-reconcile; otherwise resolves on the first new event/state change or after maxWaitMs with timedOut:true (a success summary, not a command failure).",
|
|
462
|
-
inputParams: [
|
|
463
|
-
{
|
|
464
|
-
name: "operationId",
|
|
465
|
-
type: "string",
|
|
466
|
-
required: true,
|
|
467
|
-
description: "canonical operation id",
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
name: "afterSeq",
|
|
471
|
-
type: "number",
|
|
472
|
-
required: false,
|
|
473
|
-
description: "event cursor; only events with seq > afterSeq count (default 0, >= 0)",
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
name: "maxWaitMs",
|
|
477
|
-
type: "number",
|
|
478
|
-
required: false,
|
|
479
|
-
description: "bounded wait budget; clamped server-side (default 180000, floor 60000)",
|
|
480
|
-
},
|
|
481
|
-
],
|
|
482
|
-
modelCallable: "always",
|
|
483
|
-
humanConfirmation: "none",
|
|
484
|
-
},
|
|
485
448
|
{
|
|
486
449
|
action: "contractShow",
|
|
487
450
|
cli: "loop-agent task status <taskId> --json",
|
|
@@ -1002,7 +965,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1002
965
|
"CONTROLLER_MISMATCH",
|
|
1003
966
|
"INVALID_INPUT",
|
|
1004
967
|
],
|
|
1005
|
-
description: "Consume a single-use execution receipt and execute the reviewed DAG (prefer task advance --approve-gate when gate token present). accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor
|
|
968
|
+
description: "Consume a single-use execution receipt and execute the reviewed DAG (prefer task advance --approve-gate when gate token present). accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor.",
|
|
1006
969
|
inputParams: [
|
|
1007
970
|
{
|
|
1008
971
|
name: "executionId",
|
|
@@ -45,6 +45,48 @@ export function readPackageName(packageDir) {
|
|
|
45
45
|
}
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the build stamp (git SHA + build time) written by the build pipeline
|
|
50
|
+
* into `<packageDir>/dist/build-stamp.json`. `undefined` when absent (e.g.
|
|
51
|
+
* running from source via tsx, or a hand-assembled package), which callers
|
|
52
|
+
* treat as "no stamp available".
|
|
53
|
+
*/
|
|
54
|
+
export function readBuildStamp(packageDir) {
|
|
55
|
+
try {
|
|
56
|
+
const raw = readFileSync(path.join(packageDir, "dist", "build-stamp.json"), "utf-8");
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
if (parsed.schemaVersion !== 1 ||
|
|
59
|
+
typeof parsed.version !== "string" ||
|
|
60
|
+
typeof parsed.builtAt !== "string")
|
|
61
|
+
return undefined;
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Human-readable CLI version label: plain version, enriched with the build
|
|
70
|
+
* stamp (short git SHA + build date) when available so a stale dist/ that was
|
|
71
|
+
* built from an old commit is immediately recognizable in `--version`.
|
|
72
|
+
*/
|
|
73
|
+
export function readCliVersionLabel(packageDir) {
|
|
74
|
+
const version = readPackageVersion(packageDir) ?? "0.0.0";
|
|
75
|
+
const stamp = readBuildStamp(packageDir);
|
|
76
|
+
if (!stamp?.gitSha)
|
|
77
|
+
return version;
|
|
78
|
+
return `${version} (build ${stamp.gitSha.slice(0, 8)} ${stamp.builtAt.slice(0, 10)})`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Strip the build-stamp suffix from a CLI `--version` label, returning the bare
|
|
82
|
+
* SemVer. Controller-identity/preflight comparisons must use this rather than
|
|
83
|
+
* the raw label so a build-stamped `0.35.0 (build 7f8c5b5b …)` still matches
|
|
84
|
+
* `packageVersion === "0.35.0"`.
|
|
85
|
+
*/
|
|
86
|
+
export function normalizeCliVersionLabel(label) {
|
|
87
|
+
const match = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/.exec(label.trim());
|
|
88
|
+
return match ? match[0] : label.trim();
|
|
89
|
+
}
|
|
48
90
|
/**
|
|
49
91
|
* Compute the SHA-256 hash of a file on disk.
|
|
50
92
|
*
|
|
@@ -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:`,
|
|
@@ -58,11 +58,11 @@ import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.
|
|
|
58
58
|
*/
|
|
59
59
|
export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
|
|
60
60
|
"You are the General Operator Chat for loop-agent / agent-worker.",
|
|
61
|
-
"Operate through operator_* tools first. Pi read/write/edit/bash/grep/find/ls are available
|
|
62
|
-
"If a repository has no loop-agent harness and the user requests initialization, run `loop-agent init instructions --repo-root .` then `loop-agent init --repo-root . --profile full --merge`; finish
|
|
63
|
-
"
|
|
64
|
-
"On failure read primaryFailure, primaryRecovery, doctor evidence. If meaningful progress exists, wait.
|
|
65
|
-
"Continue until success, user stop, or no safe eligible recovery remains
|
|
61
|
+
"Operate through operator_* tools first. Pi read/write/edit/bash/grep/find/ls are available, but obey the loaded repository AGENTS.md. apply_patch, full-tools, shell, and coding-chat are denied alternate runtimes. Prefer safe-read/safe-grep for sensitive probes.",
|
|
62
|
+
"If a repository has no loop-agent harness and the user requests initialization, run `loop-agent init instructions --repo-root .` then `loop-agent init --repo-root . --profile full --merge`; finish the generated setup, init doctor, inspect, docs audit, and quick verification. For updates use `loop-agent init upgrade --repo-root . --json` until stable; use init check-update only for an explicitly read-only request.",
|
|
63
|
+
"After a DAG is started or accepted, do not end on accepted/queued/running or an operationId. Supervise it to a terminal outcome. Poll operationGet, task status, dagReport, and dagDoctor after about 15 seconds on start/change, every 30 seconds during progress, and every 60 seconds after 3 minutes unchanged. Report only meaningful node/rank changes, review/verify/closeout, recovery, liveness concerns, and terminal outcomes.",
|
|
64
|
+
"On failure read primaryFailure, primaryRecovery, and doctor evidence. If meaningful progress exists, wait. Otherwise use a fresh eligible dagRerunPlan and rerun its safe node; when ineligible follow AGENTS.md/runtime recovery for same-task rerun/advance, resume, or Worker retry. Auto-approve only a bounded writeSet inside allowedPaths, outside forbiddenPaths, without broad/destructive risk, and with structured verification.",
|
|
65
|
+
"Continue until success, user stop, or no safe eligible recovery remains because limits, bindings, auth/quota recovery, or required external authorization are exhausted. Never create a new task for a transient failure or replace repair-pi with direct edits.",
|
|
66
66
|
].join("\n");
|
|
67
67
|
/** Compose the inspectable system prompt actually injected into Operator Chat. */
|
|
68
68
|
export function composeOperatorChatSystemPrompt(input) {
|
|
@@ -1280,7 +1280,6 @@ export class ConsolePiRuntime {
|
|
|
1280
1280
|
* session is not active — callers fail closed instead of fabricating data.
|
|
1281
1281
|
*/
|
|
1282
1282
|
/**
|
|
1283
|
-
* ConsolePiRuntime.listSlashCommands
|
|
1284
1283
|
* Browser-safe slash command projection for UI-11.
|
|
1285
1284
|
* Sources: current Session extension commands, prompt templates, skills.
|
|
1286
1285
|
* Never returns prompt/skill file bodies.
|
|
@@ -1293,36 +1292,29 @@ export class ConsolePiRuntime {
|
|
|
1293
1292
|
.replace(/[A-Za-z]:\\Users\\[^\s]+/gi, "~")
|
|
1294
1293
|
.slice(0, 240);
|
|
1295
1294
|
};
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1295
|
+
try {
|
|
1296
|
+
await this.ensureSessionReady(sessionId);
|
|
1297
|
+
}
|
|
1298
|
+
catch {
|
|
1299
|
+
return [];
|
|
1300
|
+
}
|
|
1299
1301
|
const session = this.sessions.get(sessionId);
|
|
1300
1302
|
if (!session)
|
|
1301
|
-
return
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
const SOURCE_RANK = {
|
|
1305
|
-
extension: 0,
|
|
1306
|
-
skill: 1,
|
|
1307
|
-
prompt: 2,
|
|
1308
|
-
};
|
|
1309
|
-
const byKey = new Map();
|
|
1310
|
-
const sourceWarnings = [];
|
|
1303
|
+
return [];
|
|
1304
|
+
const out = [];
|
|
1305
|
+
const seen = new Set();
|
|
1311
1306
|
const push = (command, label, description, source) => {
|
|
1312
1307
|
const name = command.startsWith("/") ? command : `/${command}`;
|
|
1313
1308
|
const key = name.toLowerCase();
|
|
1314
|
-
if (!key || key === "/")
|
|
1309
|
+
if (!key || key === "/" || seen.has(key))
|
|
1315
1310
|
return;
|
|
1316
|
-
|
|
1311
|
+
seen.add(key);
|
|
1312
|
+
out.push({
|
|
1317
1313
|
command: name,
|
|
1318
1314
|
label: (label || name).slice(0, 80),
|
|
1319
1315
|
description: scrub(description),
|
|
1320
1316
|
source,
|
|
1321
|
-
};
|
|
1322
|
-
const existing = byKey.get(key);
|
|
1323
|
-
if (!existing || SOURCE_RANK[source] < SOURCE_RANK[existing.source]) {
|
|
1324
|
-
byKey.set(key, next);
|
|
1325
|
-
}
|
|
1317
|
+
});
|
|
1326
1318
|
};
|
|
1327
1319
|
try {
|
|
1328
1320
|
const cmds = session.extensionRunner?.getRegisteredCommands?.() ?? [];
|
|
@@ -1333,20 +1325,18 @@ export class ConsolePiRuntime {
|
|
|
1333
1325
|
push(String(name), String(cmd.name || name), String(cmd.description || ""), "extension");
|
|
1334
1326
|
}
|
|
1335
1327
|
}
|
|
1336
|
-
catch
|
|
1337
|
-
//
|
|
1338
|
-
sourceWarnings.push(`extension: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1328
|
+
catch {
|
|
1329
|
+
// non-blocking
|
|
1339
1330
|
}
|
|
1340
1331
|
try {
|
|
1341
|
-
// Enumerate prompts before skills on purpose: priority must still let skill win.
|
|
1342
1332
|
for (const tpl of session.promptTemplates ?? []) {
|
|
1343
1333
|
if (!tpl?.name)
|
|
1344
1334
|
continue;
|
|
1345
1335
|
push(String(tpl.name), String(tpl.name), String(tpl.description || ""), "prompt");
|
|
1346
1336
|
}
|
|
1347
1337
|
}
|
|
1348
|
-
catch
|
|
1349
|
-
|
|
1338
|
+
catch {
|
|
1339
|
+
// non-blocking
|
|
1350
1340
|
}
|
|
1351
1341
|
try {
|
|
1352
1342
|
const services = this.serviceScopes.get(sessionId);
|
|
@@ -1361,16 +1351,10 @@ export class ConsolePiRuntime {
|
|
|
1361
1351
|
push(command, skill.name, String(skill.description || ""), "skill");
|
|
1362
1352
|
}
|
|
1363
1353
|
}
|
|
1364
|
-
catch
|
|
1365
|
-
|
|
1354
|
+
catch {
|
|
1355
|
+
// non-blocking
|
|
1366
1356
|
}
|
|
1367
|
-
|
|
1368
|
-
? sourceWarnings.join("; ").slice(0, 160)
|
|
1369
|
-
: undefined;
|
|
1370
|
-
return {
|
|
1371
|
-
commands: [...byKey.values()],
|
|
1372
|
-
...(warning ? { warning } : {}),
|
|
1373
|
-
};
|
|
1357
|
+
return out;
|
|
1374
1358
|
}
|
|
1375
1359
|
async getRuntimeSnapshot(sessionId, options) {
|
|
1376
1360
|
try {
|
|
@@ -1645,24 +1645,14 @@ async function handleChatFiles(res, deps, sessionId, query) {
|
|
|
1645
1645
|
}
|
|
1646
1646
|
export async function handleChatCommands(res, deps, sessionId) {
|
|
1647
1647
|
try {
|
|
1648
|
-
const
|
|
1648
|
+
const commands = typeof deps.runtime.listSlashCommands === "function"
|
|
1649
1649
|
? await deps.runtime.listSlashCommands(sessionId)
|
|
1650
|
-
:
|
|
1651
|
-
// Partial single-source failures keep successful commands + compact warning (AC-3).
|
|
1652
|
-
const commands = listed.commands ?? [];
|
|
1653
|
-
const warning = typeof listed.warning === "string" && listed.warning.trim()
|
|
1654
|
-
? listed.warning.trim().slice(0, 160)
|
|
1655
|
-
: undefined;
|
|
1650
|
+
: [];
|
|
1656
1651
|
res.statusCode = 200;
|
|
1657
1652
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1658
|
-
res.end(JSON.stringify({
|
|
1659
|
-
ok: true,
|
|
1660
|
-
commands,
|
|
1661
|
-
...(warning ? { warning } : {}),
|
|
1662
|
-
}));
|
|
1653
|
+
res.end(JSON.stringify({ ok: true, commands }));
|
|
1663
1654
|
}
|
|
1664
1655
|
catch (error) {
|
|
1665
|
-
// Whole-list / readiness failures stay fail-soft with empty commands + warning.
|
|
1666
1656
|
res.statusCode = 200;
|
|
1667
1657
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1668
1658
|
res.end(JSON.stringify({
|
|
@@ -2809,21 +2799,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
2809
2799
|
return;
|
|
2810
2800
|
}
|
|
2811
2801
|
}
|
|
2812
|
-
let body = {};
|
|
2813
|
-
try {
|
|
2814
|
-
body = await readJsonBody(req);
|
|
2815
|
-
}
|
|
2816
|
-
catch {
|
|
2817
|
-
sendJson(res, 400, {
|
|
2818
|
-
ok: false,
|
|
2819
|
-
error: { code: "INVALID_INPUT", message: "invalid json body" },
|
|
2820
|
-
});
|
|
2821
|
-
return;
|
|
2822
|
-
}
|
|
2823
|
-
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
2824
|
-
const customInstructions = rawInstructions?.trim() || undefined;
|
|
2825
2802
|
try {
|
|
2826
|
-
const snapshot = await deps.runtime.compact(sessionId
|
|
2803
|
+
const snapshot = await deps.runtime.compact(sessionId);
|
|
2827
2804
|
const event = deps.events.append(sessionId, deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`, { kind: "compact", data: snapshot });
|
|
2828
2805
|
sendJson(res, 200, { ok: true, snapshot, eventId: event.eventId });
|
|
2829
2806
|
}
|
|
@@ -74,30 +74,6 @@ export async function runOperation(operationId, deps) {
|
|
|
74
74
|
message: chunk,
|
|
75
75
|
});
|
|
76
76
|
},
|
|
77
|
-
onHeartbeat: (info) => {
|
|
78
|
-
// P1 (2026-08-13): project sibling CLI heartbeats into canonical
|
|
79
|
-
// operation events so operationWait/operationEventSummary can consume
|
|
80
|
-
// them. The projection is best-effort: a failed append must never
|
|
81
|
-
// terminate the sibling CLI execution (AC-002). The injected test
|
|
82
|
-
// runCommand path has no LoopAgentClient callback guard, so the
|
|
83
|
-
// runner protects itself here.
|
|
84
|
-
try {
|
|
85
|
-
deps.events.append(operationId, {
|
|
86
|
-
at: info.at,
|
|
87
|
-
kind: "heartbeat",
|
|
88
|
-
message: `heartbeat elapsedMs=${info.elapsedMs}`,
|
|
89
|
-
data: {
|
|
90
|
-
elapsedMs: info.elapsedMs,
|
|
91
|
-
action: op.action,
|
|
92
|
-
...(op.taskId ? { taskId: op.taskId } : {}),
|
|
93
|
-
...(op.dagRunId ? { dagRunId: op.dagRunId } : {}),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// Heartbeat is derived telemetry; ignore projection failures.
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
77
|
});
|
|
102
78
|
await spawnUpdate;
|
|
103
79
|
finished = await finalizeFromWorkerResult(operationId, result, deps);
|
|
@@ -19,7 +19,6 @@ import { deriveTaskIdentityFromPrd, nextTaskIdRevision, } from "./prd-identity.j
|
|
|
19
19
|
import { appendEngineeringCliArgs, buildDraftFromTaskIntake, parseEngineeringBoundaryFromParams, } from "./prd-intake-bridge.js";
|
|
20
20
|
import { resolveSiblingAgentWorkerBin } from "./sibling-controller.js";
|
|
21
21
|
import { projectOperationEventSummary, projectOperationForChat, } from "./chat/chat-event-store.js";
|
|
22
|
-
import { OperationWaitError, waitForOperationChange, } from "./operation-wait.js";
|
|
23
22
|
import { MutationGateReceiptStore } from "./mutation-gate-receipt-store.js";
|
|
24
23
|
import { assessAutonomousExecutionEligibility, deriveEligibilityFactsFromDagSpec, } from "./dag-execution-receipt.js";
|
|
25
24
|
import { issueHumanGateToken, mutationGatePayloadHash, verifyHumanGateToken, } from "./human-gate-token.js";
|
|
@@ -517,63 +516,6 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
517
516
|
}),
|
|
518
517
|
};
|
|
519
518
|
}
|
|
520
|
-
case "operationWait": {
|
|
521
|
-
const operationId = str(p.operationId);
|
|
522
|
-
if (!operationId)
|
|
523
|
-
return invalid(action, "operationId is required");
|
|
524
|
-
const rawAfterSeq = p.afterSeq ?? 0;
|
|
525
|
-
const afterSeq = typeof rawAfterSeq === "number" ? rawAfterSeq : Number(rawAfterSeq);
|
|
526
|
-
const rawMaxWaitMs = p.maxWaitMs;
|
|
527
|
-
const maxWaitMs = typeof rawMaxWaitMs === "number"
|
|
528
|
-
? rawMaxWaitMs
|
|
529
|
-
: rawMaxWaitMs === undefined
|
|
530
|
-
? undefined
|
|
531
|
-
: Number(rawMaxWaitMs);
|
|
532
|
-
try {
|
|
533
|
-
const result = await waitForOperationChange({
|
|
534
|
-
operations: ctx.operations,
|
|
535
|
-
events: ctx.events,
|
|
536
|
-
operationId,
|
|
537
|
-
afterSeq,
|
|
538
|
-
maxWaitMs,
|
|
539
|
-
});
|
|
540
|
-
return {
|
|
541
|
-
kind: "sync",
|
|
542
|
-
status: 200,
|
|
543
|
-
body: operatorSucceeded(action, result),
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
catch (error) {
|
|
547
|
-
if (error instanceof OperationWaitError) {
|
|
548
|
-
if (error.code === "NOT_FOUND") {
|
|
549
|
-
return {
|
|
550
|
-
kind: "error",
|
|
551
|
-
status: 404,
|
|
552
|
-
body: operatorFailed({
|
|
553
|
-
command: action,
|
|
554
|
-
outcome: "not-found",
|
|
555
|
-
code: "NOT_FOUND",
|
|
556
|
-
message: error.message,
|
|
557
|
-
}),
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
if (error.code === "EVENT_CURSOR_EXPIRED") {
|
|
561
|
-
return {
|
|
562
|
-
kind: "error",
|
|
563
|
-
status: 410,
|
|
564
|
-
body: operatorFailed({
|
|
565
|
-
command: action,
|
|
566
|
-
outcome: "blocked",
|
|
567
|
-
code: "EVENT_CURSOR_EXPIRED",
|
|
568
|
-
message: error.message,
|
|
569
|
-
}),
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
return invalid(action, error.message);
|
|
573
|
-
}
|
|
574
|
-
throw error;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
519
|
case "inspect": {
|
|
578
520
|
const body = await runReadCli(ctx, ["inspect", "--json"], "inspect");
|
|
579
521
|
return { kind: "sync", status: body.ok ? 200 : 400, body };
|