From 9cce6a6bee1ae485398c68d74e3d0e4738bcb5c6 Mon Sep 17 00:00:00 2001 From: wassname <1103714+wassname@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:40:23 +0800 Subject: [PATCH] Remove abandoned runtime and keep current entry points and tests Replace historical docs, trial output and dead runtime with current source, agent definition and reusable scripts. Bundle pinned worker, Intercom and scheduler dependencies via Pi manifest. Retain 71 current tests, including real RPC proposal/editor/Ready checks and evidence edge cases; lint/typecheck pass. Historical material remains available at f22d83c. Co-Authored-By: Pi/OpenAI <288921227+claudypoo@users.noreply.github.com> --- .gitignore | 8 +- AGENTS.md | 126 +- ARCHIVED.md | 3 - README.md | 43 +- {prototype/agents => agents}/goals-worker.md | 2 +- docs/reviews/goals_menu2.md | 66 - docs/reviews/goals_menu2_r2.md | 22 - docs/reviews/pi-goals-grok-4-6-retry.md | 31 - docs/reviews/pi-goals-kimi-k3.md | 41 - docs/reviews/review.md | 62 - .../20260826_pi-plan-aligned-planning.md | 25 - .../20260706_plan-flow-and-judge-review.md | 33 - .../20260826_pi-plan-aligned-planning.md | 53 - .../20260909_main-supervisor-prototype.md | 24 - docs/slop/reviews/edxeth-prototype/README.md | 40 - .../edxeth-prototype/copied-replay.txt | 1 - .../reviews/edxeth-prototype/final-parent.txt | 17 - .../edxeth-prototype/first-trial/.gitignore | 1 - .../edxeth-prototype/first-trial/count.mjs | 15 - .../edxeth-prototype/first-trial/greeting.txt | 1 - .../edxeth-prototype/first-trial/plan.md | 44 - .../first-trial/sessions-summary.json | 322 ----- .../count-supervisor/retry.command.txt | 1 - .../count-supervisor/retry.exit-status.txt | 1 - .../count-supervisor/retry.stderr.txt | 0 .../count-supervisor/retry.stdout.txt | 9 - .../run-HD5Ge1/binary.command.json | 1 - .../run-HD5Ge1/binary.status.json | 1 - .../run-HD5Ge1/denied.command.json | 1 - .../run-HD5Ge1/denied.status.json | 1 - .../run-HD5Ge1/direct-denial.json | 1 - .../run-HD5Ge1/empty.command.json | 1 - .../run-HD5Ge1/empty.status.json | 1 - .../run-HD5Ge1/extra.command.json | 1 - .../run-HD5Ge1/extra.status.json | 1 - .../run-HD5Ge1/greeting.command.json | 1 - .../run-HD5Ge1/greeting.status.json | 1 - .../count-supervisor/run-HD5Ge1/identity.json | 1 - .../run-HD5Ge1/ignored.command.json | 1 - .../run-HD5Ge1/ignored.status.json | 1 - .../run-HD5Ge1/missing.command.json | 1 - .../run-HD5Ge1/missing.status.json | 1 - .../run-HD5Ge1/nonexistent.command.json | 1 - .../run-HD5Ge1/nonexistent.status.json | 1 - .../count-supervisor/run-HD5Ge1/summary.json | 7 - .../run-HD5Ge1/tracked.command.json | 1 - .../run-HD5Ge1/tracked.status.json | 1 - .../run-HD5Ge1/unicode.command.json | 1 - .../run-HD5Ge1/unicode.status.json | 1 - .../count-supervisor/verification-blocker.txt | 5 - .../verification/count-supervisor/verify.mjs | 34 - .../count-worker/capture-mhlBLh/command.txt | 1 - .../capture-mhlBLh/exit-status.txt | 1 - .../count-worker/capture-mhlBLh/stderr.txt | 0 .../count-worker/capture-mhlBLh/stdout.txt | 10 - .../run-lCEfQi/binary.command.json | 8 - .../run-lCEfQi/binary.process.json | 1 - .../run-lCEfQi/direct-read-denial.json | 1 - .../run-lCEfQi/empty.command.json | 8 - .../run-lCEfQi/empty.process.json | 1 - .../count-worker/run-lCEfQi/fixtures.json | 18 - .../run-lCEfQi/git-check-ignore.command.json | 10 - .../run-lCEfQi/git-check-ignore.process.json | 1 - .../run-lCEfQi/git-tracked.command.json | 9 - .../run-lCEfQi/git-tracked.process.json | 1 - .../run-lCEfQi/greeting.command.json | 8 - .../run-lCEfQi/greeting.process.json | 1 - .../run-lCEfQi/identity.command.json | 5 - .../count-worker/run-lCEfQi/identity.json | 1 - .../run-lCEfQi/identity.process.json | 1 - .../run-lCEfQi/nonexistent.command.json | 8 - .../run-lCEfQi/nonexistent.process.json | 1 - .../run-lCEfQi/permission-denied.command.json | 8 - .../run-lCEfQi/permission-denied.process.json | 1 - .../count-worker/run-lCEfQi/summary.json | 6 - .../run-lCEfQi/two-args.command.json | 9 - .../run-lCEfQi/two-args.process.json | 1 - .../count-worker/run-lCEfQi/utf8.command.json | 8 - .../count-worker/run-lCEfQi/utf8.process.json | 1 - .../run-lCEfQi/zero-args.command.json | 7 - .../run-lCEfQi/zero-args.process.json | 1 - .../verification/count-worker/verify.mjs | 68 - .../greeting-supervisor/exit-status.txt | 1 - .../greeting-supervisor/git-check-ignore.txt | 2 - .../greeting-supervisor/git-tracked.txt | 0 .../greeting-supervisor/stderr.txt | 0 .../greeting-supervisor/stdout.txt | 1 - .../greeting-worker/exit-status.txt | 1 - .../git-check-ignore.exit-status.txt | 1 - .../git-check-ignore.stderr.txt | 0 .../greeting-worker/git-check-ignore.txt | 7 - .../git-tracked.exit-status.txt | 1 - .../greeting-worker/git-tracked.stderr.txt | 0 .../greeting-worker/git-tracked.txt | 0 .../greeting-worker/run.exit-status.txt | 1 - .../greeting-worker/run.stderr.txt | 0 .../greeting-worker/run.stdout.txt | 1 - .../verification/greeting-worker/stderr.txt | 0 .../verification/greeting-worker/stdout.txt | 2 - .../verification/greeting-worker/verify.mjs | 7 - .../edxeth-prototype/fresh-trial/.gitignore | 1 - .../edxeth-prototype/fresh-trial/count.mjs | 15 - .../edxeth-prototype/fresh-trial/greeting.txt | 1 - .../edxeth-prototype/fresh-trial/plan.md | 39 - .../fresh-trial/sessions-summary.json | 232 ---- .../count/worker-7pK7Df/binary.exit | 1 - .../count/worker-7pK7Df/binary.stderr | 0 .../count/worker-7pK7Df/binary.stdout | 1 - .../count/worker-7pK7Df/empty.exit | 1 - .../count/worker-7pK7Df/empty.stderr | 0 .../count/worker-7pK7Df/empty.stdout | 1 - .../count/worker-7pK7Df/extra-args.exit | 1 - .../count/worker-7pK7Df/extra-args.stderr | 1 - .../count/worker-7pK7Df/extra-args.stdout | 0 .../count/worker-7pK7Df/greeting.exit | 1 - .../count/worker-7pK7Df/greeting.stderr | 0 .../count/worker-7pK7Df/greeting.stdout | 1 - .../count/worker-7pK7Df/missing-args.exit | 1 - .../count/worker-7pK7Df/missing-args.stderr | 1 - .../count/worker-7pK7Df/missing-args.stdout | 0 .../count/worker-7pK7Df/nonexistent.exit | 1 - .../count/worker-7pK7Df/nonexistent.stderr | 1 - .../count/worker-7pK7Df/nonexistent.stdout | 0 .../count/worker-7pK7Df/spaces.exit | 1 - .../count/worker-7pK7Df/spaces.stderr | 0 .../count/worker-7pK7Df/spaces.stdout | 1 - .../count/worker-7pK7Df/unreadability.json | 24 - .../count/worker-7pK7Df/unreadable.exit | 1 - .../count/worker-7pK7Df/unreadable.stderr | 1 - .../count/worker-7pK7Df/unreadable.stdout | 0 .../count/worker-7pK7Df/utf8.exit | 1 - .../count/worker-7pK7Df/utf8.stderr | 0 .../count/worker-7pK7Df/utf8.stdout | 1 - .../count/worker-7pK7Df/verifier.exit | 1 - .../count/worker-7pK7Df/verifier.stderr | 0 .../count/worker-7pK7Df/verifier.stdout | 10 - .../fresh-trial/verification/greeting.exit | 1 - .../fresh-trial/verification/greeting.stderr | 0 .../fresh-trial/verification/greeting.stdout | 1 - .../supervisor/check-ignore.stdout | 2 - .../verification/supervisor/count/binary.exit | 1 - .../supervisor/count/binary.stderr | 0 .../supervisor/count/binary.stdout | 1 - .../supervisor/count/check-ignore.stdout | 2 - .../verification/supervisor/count/empty.exit | 1 - .../supervisor/count/empty.stderr | 0 .../supervisor/count/empty.stdout | 1 - .../supervisor/count/extra-args.exit | 1 - .../supervisor/count/extra-args.stderr | 1 - .../supervisor/count/extra-args.stdout | 0 .../supervisor/count/greeting.exit | 1 - .../supervisor/count/greeting.stderr | 0 .../supervisor/count/greeting.stdout | 1 - .../supervisor/count/missing-args.exit | 1 - .../supervisor/count/missing-args.stderr | 1 - .../supervisor/count/missing-args.stdout | 0 .../supervisor/count/nonexistent.exit | 1 - .../supervisor/count/nonexistent.stderr | 1 - .../supervisor/count/nonexistent.stdout | 0 .../supervisor/count/project-status.stdout | 6 - .../verification/supervisor/count/spaces.exit | 1 - .../supervisor/count/spaces.stderr | 0 .../supervisor/count/spaces.stdout | 1 - .../supervisor/count/unreadability.json | 24 - .../supervisor/count/unreadable.exit | 1 - .../supervisor/count/unreadable.stderr | 1 - .../supervisor/count/unreadable.stdout | 0 .../verification/supervisor/count/utf8.exit | 1 - .../verification/supervisor/count/utf8.stderr | 0 .../verification/supervisor/count/utf8.stdout | 1 - .../supervisor/count/verifier.exit | 1 - .../supervisor/count/verifier.stderr | 0 .../supervisor/count/verifier.stdout | 10 - .../verification/supervisor/greeting.exit | 1 - .../verification/supervisor/greeting.stderr | 0 .../verification/supervisor/greeting.stdout | 1 - .../fresh-trial/verification/verify-count.mjs | 77 -- .../verification/verify-greeting.mjs | 8 - .../reviews/edxeth-prototype/npm-test.txt | 12 - .../operator-check/count-XclYvZ/binary.exit | 1 - .../operator-check/count-XclYvZ/binary.stderr | 0 .../operator-check/count-XclYvZ/binary.stdout | 1 - .../operator-check/count-XclYvZ/empty.exit | 1 - .../operator-check/count-XclYvZ/empty.stderr | 0 .../operator-check/count-XclYvZ/empty.stdout | 1 - .../count-XclYvZ/extra-args.exit | 1 - .../count-XclYvZ/extra-args.stderr | 1 - .../count-XclYvZ/extra-args.stdout | 0 .../operator-check/count-XclYvZ/greeting.exit | 1 - .../count-XclYvZ/greeting.stderr | 0 .../count-XclYvZ/greeting.stdout | 1 - .../count-XclYvZ/missing-args.exit | 1 - .../count-XclYvZ/missing-args.stderr | 1 - .../count-XclYvZ/missing-args.stdout | 0 .../count-XclYvZ/nonexistent.exit | 1 - .../count-XclYvZ/nonexistent.stderr | 1 - .../count-XclYvZ/nonexistent.stdout | 0 .../operator-check/count-XclYvZ/spaces.exit | 1 - .../operator-check/count-XclYvZ/spaces.stderr | 0 .../operator-check/count-XclYvZ/spaces.stdout | 1 - .../count-XclYvZ/unreadability.json | 24 - .../count-XclYvZ/unreadable.exit | 1 - .../count-XclYvZ/unreadable.stderr | 1 - .../count-XclYvZ/unreadable.stdout | 0 .../operator-check/count-XclYvZ/utf8.exit | 1 - .../operator-check/count-XclYvZ/utf8.stderr | 0 .../operator-check/count-XclYvZ/utf8.stdout | 1 - .../operator-check/count.exit | 1 - .../operator-check/count.stderr | 0 .../operator-check/count.stdout | 10 - .../operator-check/greeting.json | 1 - .../edxeth-prototype/reload-completed.txt | 1 - .../reviews/edxeth-prototype/worker-pane.txt | 1 - .../reviews/overnight-supervision-research.md | 202 --- .../maintenance.json | 110 -- .../source-revisions.jsonl | 7 - docs/spec/2026-06-15_pi-goals.md | 275 ---- .../2026-06-29_complete-goal-fail-forward.md | 71 - docs/spec/2026-08-14_per-session-plan.md | 67 - handover.md | 116 -- media/screenshot.png | Bin 78504 -> 0 bytes package-lock.json | 377 +++++- package.json | 30 +- prototype/README.md | 3 - prototype/capture-workers.mjs | 28 - prototype/replay-smoke.mjs | 69 - prototype/summarize-trial.mjs | 25 - scripts/check-judge-footprint.sh | 43 - scripts/check-stale-fixmes.sh | 14 - scripts/inconclusive-fail-forward.diff | 104 -- .../prepare.mjs => scripts/prepare-trial.mjs | 4 +- scripts/session-usage.mjs | 61 + scripts/stale-fixme-removal.diff | 30 - .../20260905_goal-steward-validation.md | 31 - .../20260905_nested-supervisor-validation.txt | 91 -- slop/audits/20260905_pi-goals-file-types.txt | 52 - .../20260905_pi-goals-line-count-table.md | 67 - .../20260905_pi-goals-text-line-counts.txt | 50 - slop/audits/20260905_steward-probe.json | 16 - ...60906_foreground-supervisor-validation.txt | 53 - slop/audits/20260906_nested-runtime-smoke.md | 31 - slop/audits/20260906_nonchild-npm-test.txt | 33 - slop/handovers/20260906_visible-supervisor.md | 18 - slop/plans/20260905_goal-steward.md | 37 - .../plans/20260907_goals_owned_supervision.md | 33 - slop/plans/20260908_force-approval.md | 25 - slop/plans/20260908_intercom-supervision.md | 52 - .../20260910_package-based-supervision.md | 75 - ...0260910_unmodified-runtime-integrations.md | 28 - ...deepseek-v4-pro-0813_pi_goals_fragility.md | 65 - .../20260906_foreground-worker-review.md | 20 - .../20260908-force-initial-validation.txt | 105 -- slop/reviews/20260908-force-validation.txt | 132 -- ...-handshake-boundary-initial-validation.txt | 122 -- slop/reviews/20260908-handshake-fixes.md | 42 - .../20260908-handshake-initial-validation.txt | 77 -- .../20260908-handshake-log-inspection.txt | 8 - .../reviews/20260908-handshake-validation.txt | 107 -- .../20260908-independent-post-fix-review.md | 100 -- ...0908-independent-supervision-bug-review.md | 151 --- slop/reviews/20260908-intercom-validation.txt | 62 - ...260908-native-intercom-full-validation.txt | 81 -- .../20260908-native-intercom-validation.txt | 17 - ...0260908-review-fixes-initial-validation.md | 24 - .../20260908-review-fixes-log-inspection.txt | 4 - .../20260908-review-fixes-validation.txt | 93 -- slop/reviews/20260908-review-fixes.md | 38 - .../20260908-role-model-validation.txt | 73 - .../20260908-worker-overview-example.txt | 27 - .../20260908-worker-overview-validation.txt | 68 - ...0260908_supervision-against-user-intent.md | 72 - .../20260908_supervision-fixes-validation.txt | 26 - ...0260908_supervisor-intent-reproduction.txt | 4 - .../20260909-herdr-functional-acceptance.md | 47 - ...910_active-research-supervisor-feedback.md | 53 - .../20260910_deepseek-package-supervision.md | 76 -- .../20260910_glm-package-supervision.md | 51 - .../20260910_package-supervision-herdr.md | 95 -- .../reviews/full-profile-supervisor/review.md | 39 - .../full-profile-supervisor/validation.txt | 26 - .../handshake-native/supervisor-events.jsonl | 21 - .../handshake-native/supervisor-stderr.txt | 0 .../handshake-native/worker-events.jsonl | 22 - .../handshake-native/worker-stderr.txt | 0 .../cancellation-before.txt | 33 - .../checkpoint-validation.txt | 26 - .../lifecycle-recovery/native-validation.txt | 33 - slop/reviews/lifecycle-recovery/review.md | 44 - .../reviews/lifecycle-recovery/validation.txt | 26 - .../native-intercom/supervisor-events.jsonl | 21 - .../native-intercom/supervisor-stderr.txt | 0 .../native-intercom/worker-events.jsonl | 21 - .../reviews/native-intercom/worker-stderr.txt | 0 .../01-ready-menu.txt | 57 - .../02-worker-byte-verification.log | 16 - .../03-completed-plan.md | 25 - .../04-completion-events.json | 123 -- .../05-parent-byte-check.log | 7 - .../06-resumed-worker-pane.txt | 52 - .../07-intercom-supervisor-draft.txt | 51 - .../08-intercom-return-worker-draft.txt | 75 - .../09-open-pane-draft-safe-roundtrip.txt | 59 - .../10-scheduler-jobs-ui.txt | 35 - .../11-before-live-reload-worker-draft.txt | 12 - .../12-parent-reload.txt | 24 - .../13-worker-draft-after-parent-reload.txt | 14 - .../14-live-reload-reconnection.txt | 59 - .../15-edited-timer-fired-once.json | 47 - .../16-timer-visible-response.txt | 30 - .../17-delayed-reload-worker-exit-crash.txt | 29 - .../18-solo-takeover-menu.txt | 18 - .../19-solo-recovery-completed.log | 40 - .../20-final-ready-menu.txt | 25 - .../21-final-supervisor.txt | 55 - .../22-final-open-worker.txt | 40 - .../23-final-parent-verification.log | 6 - .../24-installed-goals-status.txt | 30 - .../25-installed-subagents.txt | 26 - .../26-goals-action-menu.txt | 25 - .../27-automatic-plan-proposal.txt | 25 - .../supervisor-events.jsonl | 21 - .../review-fixes-native/supervisor-stderr.txt | 0 .../review-fixes-native/worker-events.jsonl | 21 - .../review-fixes-native/worker-stderr.txt | 0 .../initial-validation.txt | 66 - slop/reviews/supervisor-prompt-flow/review.md | 49 - .../supervisor-prompt-flow/validation.txt | 26 - slop/reviews/vcc-view/compare.mjs | 56 - slop/reviews/vcc-view/comparison.json | 46 - .../vcc-view/flow-implementation-old.md | 67 - .../vcc-view/flow-implementation-vcc.md | 39 - slop/reviews/vcc-view/herdr-acceptance.md | 20 - .../reviews/vcc-view/pilot-preparation-old.md | 144 -- .../reviews/vcc-view/pilot-preparation-vcc.md | 82 -- slop/reviews/vcc-view/plan.md | 7 - slop/reviews/vcc-view/review.md | 62 - .../vcc-view/settled-checkpoint-old.md | 44 - .../vcc-view/settled-checkpoint-vcc.md | 33 - slop/reviews/vcc-view/validation.txt | 73 - slop/test-logs/feedback-fixes-full.log | 13 - slop/test-logs/feedback-fixes-lint.log | 5 - slop/test-logs/feedback-fixes-typecheck.log | 4 - slop/test-logs/final-review-fixes-full.log | 13 - slop/test-logs/final-review-fixes-lint.log | 5 - .../final-review-fixes-typecheck.log | 4 - src/approval.ts | 176 --- src/background.ts | 33 - src/herdr.ts | 83 -- src/index.ts | 1205 ++++++----------- src/intercom-package.d.ts | 4 - src/intercom.ts | 308 ----- src/prompts.ts | 181 +-- src/prototype.ts | 437 ------ src/role-models.ts | 56 - src/supervisor-session.ts | 292 ---- src/vcc-package.d.ts | 10 - src/worker-view.ts | 100 -- test/append-log.test.ts | 17 - test/approval.test.ts | 43 - test/background.test.ts | 31 - test/fixtures/compaction-delivery.ts | 20 - test/fixtures/intercom-worker.ts | 17 - test/fixtures/profile-tools.ts | 15 - test/fixtures/subagent-schema.ts | 16 + test/fold.test.ts | 19 +- test/force-approval-flow.test.ts | 166 --- test/goals-flow.test.ts | 642 --------- test/{prototype.test.ts => goals.test.ts} | 27 +- test/herdr.test.ts | 48 - test/intercom-broker.test.ts | 68 - test/intercom-compaction.test.ts | 68 - test/intercom-fixture.ts | 28 - test/intercom-handshake.test.ts | 84 -- test/intercom.test.ts | 123 -- test/native-compaction-delivery.test.ts | 64 - test/native-intercom.test.ts | 150 -- test/package-agent.test.ts | 19 - test/package.test.ts | 15 + test/paired-intercom-fixture.ts | 31 - test/prompts.test.ts | 42 +- test/role-models.test.ts | 60 - test/rpc-review.test.ts | 115 +- test/session-usage.test.ts | 34 + test/supervisor-role.test.ts | 19 - test/supervisor-session.test.ts | 341 ----- test/tick-goal.test.ts | 39 - test/worker-view.test.ts | 141 -- tsconfig.json | 10 +- 388 files changed, 1095 insertions(+), 12537 deletions(-) delete mode 100644 ARCHIVED.md rename {prototype/agents => agents}/goals-worker.md (96%) delete mode 100644 docs/reviews/goals_menu2.md delete mode 100644 docs/reviews/goals_menu2_r2.md delete mode 100644 docs/reviews/pi-goals-grok-4-6-retry.md delete mode 100644 docs/reviews/pi-goals-kimi-k3.md delete mode 100644 docs/reviews/review.md delete mode 100644 docs/slop/audit/20260826_pi-plan-aligned-planning.md delete mode 100644 docs/slop/plans/20260706_plan-flow-and-judge-review.md delete mode 100644 docs/slop/plans/20260826_pi-plan-aligned-planning.md delete mode 100644 docs/slop/plans/20260909_main-supervisor-prototype.md delete mode 100644 docs/slop/reviews/edxeth-prototype/README.md delete mode 100644 docs/slop/reviews/edxeth-prototype/copied-replay.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/final-parent.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/.gitignore delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/count.mjs delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/greeting.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/plan.md delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/sessions-summary.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.command.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.exit-status.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.stderr.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.stdout.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/direct-denial.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/identity.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/summary.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.status.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verification-blocker.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verify.mjs delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/command.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/exit-status.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/stderr.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/stdout.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/direct-read-denial.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/fixtures.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/summary.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.command.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.process.json delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/verify.mjs delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/exit-status.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/git-check-ignore.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/git-tracked.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/stderr.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/stdout.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/exit-status.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.exit-status.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.stderr.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.exit-status.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.stderr.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.exit-status.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.stderr.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.stdout.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/stderr.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/stdout.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/verify.mjs delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/.gitignore delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/count.mjs delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/greeting.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/plan.md delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/sessions-summary.json delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadability.json delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/check-ignore.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/check-ignore.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/project-status.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadability.json delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-count.mjs delete mode 100644 docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-greeting.mjs delete mode 100644 docs/slop/reviews/edxeth-prototype/npm-test.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadability.json delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count.exit delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count.stderr delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/count.stdout delete mode 100644 docs/slop/reviews/edxeth-prototype/operator-check/greeting.json delete mode 100644 docs/slop/reviews/edxeth-prototype/reload-completed.txt delete mode 100644 docs/slop/reviews/edxeth-prototype/worker-pane.txt delete mode 100644 docs/slop/reviews/overnight-supervision-research.md delete mode 100644 docs/slop/reviews/overnight-supervision-sources/maintenance.json delete mode 100644 docs/slop/reviews/overnight-supervision-sources/source-revisions.jsonl delete mode 100644 docs/spec/2026-06-15_pi-goals.md delete mode 100644 docs/spec/2026-06-29_complete-goal-fail-forward.md delete mode 100644 docs/spec/2026-08-14_per-session-plan.md delete mode 100644 handover.md delete mode 100644 media/screenshot.png delete mode 100644 prototype/README.md delete mode 100644 prototype/capture-workers.mjs delete mode 100644 prototype/replay-smoke.mjs delete mode 100644 prototype/summarize-trial.mjs delete mode 100755 scripts/check-judge-footprint.sh delete mode 100755 scripts/check-stale-fixmes.sh delete mode 100644 scripts/inconclusive-fail-forward.diff rename prototype/prepare.mjs => scripts/prepare-trial.mjs (94%) create mode 100644 scripts/session-usage.mjs delete mode 100644 scripts/stale-fixme-removal.diff delete mode 100644 slop/audits/20260905_goal-steward-validation.md delete mode 100644 slop/audits/20260905_nested-supervisor-validation.txt delete mode 100644 slop/audits/20260905_pi-goals-file-types.txt delete mode 100644 slop/audits/20260905_pi-goals-line-count-table.md delete mode 100644 slop/audits/20260905_pi-goals-text-line-counts.txt delete mode 100644 slop/audits/20260905_steward-probe.json delete mode 100644 slop/audits/20260906_foreground-supervisor-validation.txt delete mode 100644 slop/audits/20260906_nested-runtime-smoke.md delete mode 100644 slop/audits/20260906_nonchild-npm-test.txt delete mode 100644 slop/handovers/20260906_visible-supervisor.md delete mode 100644 slop/plans/20260905_goal-steward.md delete mode 100644 slop/plans/20260907_goals_owned_supervision.md delete mode 100644 slop/plans/20260908_force-approval.md delete mode 100644 slop/plans/20260908_intercom-supervision.md delete mode 100644 slop/plans/20260910_package-based-supervision.md delete mode 100644 slop/research/20260910_unmodified-runtime-integrations.md delete mode 100644 slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md delete mode 100644 slop/reviews/20260906_foreground-worker-review.md delete mode 100644 slop/reviews/20260908-force-initial-validation.txt delete mode 100644 slop/reviews/20260908-force-validation.txt delete mode 100644 slop/reviews/20260908-handshake-boundary-initial-validation.txt delete mode 100644 slop/reviews/20260908-handshake-fixes.md delete mode 100644 slop/reviews/20260908-handshake-initial-validation.txt delete mode 100644 slop/reviews/20260908-handshake-log-inspection.txt delete mode 100644 slop/reviews/20260908-handshake-validation.txt delete mode 100644 slop/reviews/20260908-independent-post-fix-review.md delete mode 100644 slop/reviews/20260908-independent-supervision-bug-review.md delete mode 100644 slop/reviews/20260908-intercom-validation.txt delete mode 100644 slop/reviews/20260908-native-intercom-full-validation.txt delete mode 100644 slop/reviews/20260908-native-intercom-validation.txt delete mode 100644 slop/reviews/20260908-review-fixes-initial-validation.md delete mode 100644 slop/reviews/20260908-review-fixes-log-inspection.txt delete mode 100644 slop/reviews/20260908-review-fixes-validation.txt delete mode 100644 slop/reviews/20260908-review-fixes.md delete mode 100644 slop/reviews/20260908-role-model-validation.txt delete mode 100644 slop/reviews/20260908-worker-overview-example.txt delete mode 100644 slop/reviews/20260908-worker-overview-validation.txt delete mode 100644 slop/reviews/20260908_supervision-against-user-intent.md delete mode 100644 slop/reviews/20260908_supervision-fixes-validation.txt delete mode 100644 slop/reviews/20260908_supervisor-intent-reproduction.txt delete mode 100644 slop/reviews/20260909-herdr-functional-acceptance.md delete mode 100644 slop/reviews/20260910_active-research-supervisor-feedback.md delete mode 100644 slop/reviews/20260910_deepseek-package-supervision.md delete mode 100644 slop/reviews/20260910_glm-package-supervision.md delete mode 100644 slop/reviews/20260910_package-supervision-herdr.md delete mode 100644 slop/reviews/full-profile-supervisor/review.md delete mode 100644 slop/reviews/full-profile-supervisor/validation.txt delete mode 100644 slop/reviews/handshake-native/supervisor-events.jsonl delete mode 100644 slop/reviews/handshake-native/supervisor-stderr.txt delete mode 100644 slop/reviews/handshake-native/worker-events.jsonl delete mode 100644 slop/reviews/handshake-native/worker-stderr.txt delete mode 100644 slop/reviews/lifecycle-recovery/cancellation-before.txt delete mode 100644 slop/reviews/lifecycle-recovery/checkpoint-validation.txt delete mode 100644 slop/reviews/lifecycle-recovery/native-validation.txt delete mode 100644 slop/reviews/lifecycle-recovery/review.md delete mode 100644 slop/reviews/lifecycle-recovery/validation.txt delete mode 100644 slop/reviews/native-intercom/supervisor-events.jsonl delete mode 100644 slop/reviews/native-intercom/supervisor-stderr.txt delete mode 100644 slop/reviews/native-intercom/worker-events.jsonl delete mode 100644 slop/reviews/native-intercom/worker-stderr.txt delete mode 100644 slop/reviews/package-supervision-captures/01-ready-menu.txt delete mode 100644 slop/reviews/package-supervision-captures/02-worker-byte-verification.log delete mode 100644 slop/reviews/package-supervision-captures/03-completed-plan.md delete mode 100644 slop/reviews/package-supervision-captures/04-completion-events.json delete mode 100644 slop/reviews/package-supervision-captures/05-parent-byte-check.log delete mode 100644 slop/reviews/package-supervision-captures/06-resumed-worker-pane.txt delete mode 100644 slop/reviews/package-supervision-captures/07-intercom-supervisor-draft.txt delete mode 100644 slop/reviews/package-supervision-captures/08-intercom-return-worker-draft.txt delete mode 100644 slop/reviews/package-supervision-captures/09-open-pane-draft-safe-roundtrip.txt delete mode 100644 slop/reviews/package-supervision-captures/10-scheduler-jobs-ui.txt delete mode 100644 slop/reviews/package-supervision-captures/11-before-live-reload-worker-draft.txt delete mode 100644 slop/reviews/package-supervision-captures/12-parent-reload.txt delete mode 100644 slop/reviews/package-supervision-captures/13-worker-draft-after-parent-reload.txt delete mode 100644 slop/reviews/package-supervision-captures/14-live-reload-reconnection.txt delete mode 100644 slop/reviews/package-supervision-captures/15-edited-timer-fired-once.json delete mode 100644 slop/reviews/package-supervision-captures/16-timer-visible-response.txt delete mode 100644 slop/reviews/package-supervision-captures/17-delayed-reload-worker-exit-crash.txt delete mode 100644 slop/reviews/package-supervision-captures/18-solo-takeover-menu.txt delete mode 100644 slop/reviews/package-supervision-captures/19-solo-recovery-completed.log delete mode 100644 slop/reviews/package-supervision-captures/20-final-ready-menu.txt delete mode 100644 slop/reviews/package-supervision-captures/21-final-supervisor.txt delete mode 100644 slop/reviews/package-supervision-captures/22-final-open-worker.txt delete mode 100644 slop/reviews/package-supervision-captures/23-final-parent-verification.log delete mode 100644 slop/reviews/package-supervision-captures/24-installed-goals-status.txt delete mode 100644 slop/reviews/package-supervision-captures/25-installed-subagents.txt delete mode 100644 slop/reviews/package-supervision-captures/26-goals-action-menu.txt delete mode 100644 slop/reviews/package-supervision-captures/27-automatic-plan-proposal.txt delete mode 100644 slop/reviews/review-fixes-native/supervisor-events.jsonl delete mode 100644 slop/reviews/review-fixes-native/supervisor-stderr.txt delete mode 100644 slop/reviews/review-fixes-native/worker-events.jsonl delete mode 100644 slop/reviews/review-fixes-native/worker-stderr.txt delete mode 100644 slop/reviews/supervisor-prompt-flow/initial-validation.txt delete mode 100644 slop/reviews/supervisor-prompt-flow/review.md delete mode 100644 slop/reviews/supervisor-prompt-flow/validation.txt delete mode 100644 slop/reviews/vcc-view/compare.mjs delete mode 100644 slop/reviews/vcc-view/comparison.json delete mode 100644 slop/reviews/vcc-view/flow-implementation-old.md delete mode 100644 slop/reviews/vcc-view/flow-implementation-vcc.md delete mode 100644 slop/reviews/vcc-view/herdr-acceptance.md delete mode 100644 slop/reviews/vcc-view/pilot-preparation-old.md delete mode 100644 slop/reviews/vcc-view/pilot-preparation-vcc.md delete mode 100644 slop/reviews/vcc-view/plan.md delete mode 100644 slop/reviews/vcc-view/review.md delete mode 100644 slop/reviews/vcc-view/settled-checkpoint-old.md delete mode 100644 slop/reviews/vcc-view/settled-checkpoint-vcc.md delete mode 100644 slop/reviews/vcc-view/validation.txt delete mode 100644 slop/test-logs/feedback-fixes-full.log delete mode 100644 slop/test-logs/feedback-fixes-lint.log delete mode 100644 slop/test-logs/feedback-fixes-typecheck.log delete mode 100644 slop/test-logs/final-review-fixes-full.log delete mode 100644 slop/test-logs/final-review-fixes-lint.log delete mode 100644 slop/test-logs/final-review-fixes-typecheck.log delete mode 100644 src/approval.ts delete mode 100644 src/background.ts delete mode 100644 src/herdr.ts delete mode 100644 src/intercom-package.d.ts delete mode 100644 src/intercom.ts delete mode 100644 src/prototype.ts delete mode 100644 src/role-models.ts delete mode 100644 src/supervisor-session.ts delete mode 100644 src/vcc-package.d.ts delete mode 100644 src/worker-view.ts delete mode 100644 test/append-log.test.ts delete mode 100644 test/approval.test.ts delete mode 100644 test/background.test.ts delete mode 100644 test/fixtures/compaction-delivery.ts delete mode 100644 test/fixtures/intercom-worker.ts delete mode 100644 test/fixtures/profile-tools.ts create mode 100644 test/fixtures/subagent-schema.ts delete mode 100644 test/force-approval-flow.test.ts delete mode 100644 test/goals-flow.test.ts rename test/{prototype.test.ts => goals.test.ts} (94%) delete mode 100644 test/herdr.test.ts delete mode 100644 test/intercom-broker.test.ts delete mode 100644 test/intercom-compaction.test.ts delete mode 100644 test/intercom-fixture.ts delete mode 100644 test/intercom-handshake.test.ts delete mode 100644 test/intercom.test.ts delete mode 100644 test/native-compaction-delivery.test.ts delete mode 100644 test/native-intercom.test.ts delete mode 100644 test/package-agent.test.ts create mode 100644 test/package.test.ts delete mode 100644 test/paired-intercom-fixture.ts delete mode 100644 test/role-models.test.ts create mode 100644 test/session-usage.test.ts delete mode 100644 test/supervisor-role.test.ts delete mode 100644 test/supervisor-session.test.ts delete mode 100644 test/tick-goal.test.ts delete mode 100644 test/worker-view.test.ts diff --git a/.gitignore b/.gitignore index 2063e6a..4cb8f05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -node_modules +node_modules/ dist/ -*.log +.local/ .pi/ -docs/reviews/raw.jsonl -docs/reviews/err.txt +slop/ +*.log diff --git a/AGENTS.md b/AGENTS.md index eaf8504..4f5a7cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,109 +1,35 @@ # pi-goals contributor notes +## Design + +The main chat discusses the plan with the user, then supervises an interactive `goals-worker` in Herdr. Use stock pi-subagents, pi-intercom and pi-schedule-prompt; do not build another transport, scheduler or worker runtime. + +> the hope is we can have a smart supervisor like you, with judgment and context. But it doesn't use many tokens as it checks in and sees an overview. +> +> It steers a smaller model, adding perspective and judgment. +> +> Well, I want to see what the supervisor is thinking and saying. That's the whole point: all supervisor thinking and messages should be visible. + +— wassname + +- Keep supervisor inspection tools. It inspects actual results, delegates implementation and must not weaken the user's goal to accept worker output. +- Put all model-facing prompts in `src/prompts.ts`, in conversation order. Preserve the user's verbatim requirements. +- `/goals` opens actions. New plan starts a discussion without an objective form. Unknown commands never start planning. A changed settled draft opens the approval dialogue; unchanged discussion does not repeatedly reopen it. +- Keep goal titles/status in widgets; omit subtask text. Tasks and evidence remain in the plan. +- Keep startup/compaction plan context, short upkeep reminders and visible editable hourly check-ins. Avoid unchanged-plan repetition and identity-only review turns. +- Keep recoverable solo mode: confirm other writers stopped before taking over. Solo completion is self-verification. +- Record distinct runtime ID, Intercom ID and saved-session path with provenance. A handle or delivery receipt is not proof of liveness or action. User model changes are authorized; do not silently restore an old preference. + ## Tests -Run `npm test` before a commit. It includes unit and flow tests plus the RPC review test. +Run `npm test`, `npm run typecheck` and `npm run lint` before committing. -- `test/*.test.ts` unit and flow tests use a small Pi API mock. They check plan state, tool gates, and plan-file updates. -- `npm run test:rpc` runs `test/rpc-review.test.ts`. It starts the installed Pi executable in RPC mode, uses Pi's real `select` and `editor` protocol, and uses a local deterministic HTTP model. It does not need a credential or spend API credits. This is the closest automated session test. -- Use tmux for visual TUI debugging when the RPC test fails or a terminal-only problem is reported: +`test/goals.test.ts` exercises current state, file updates and role restrictions with a Pi API mock. `test/rpc-review.test.ts` starts real Pi with a deterministic local model and schema-only worker tools: it checks automatic proposal, editor/discussion and Ready role transition without credits or launching workers. It does not prove Herdr rendering, live message delivery or model judgment. - ```bash - tmux new-session -s pi-goals-debug 'cd /path/to/pi-goals && pi -e ./src/index.ts' - ``` +For functional acceptance, read `herdr --skill`, confirm `HERDR_ENV=1`, and use `scripts/prepare-trial.mjs` to create an isolated project/profile. Open only new no-focus test panes. Observe the actual planning dialogue and Ready selection, worker attachment, Intercom report, independent artifact inspection and CompleteGoal. Record interventions separately from autonomous success. Preserve nonempty byte/test evidence. Never reload or operate active user research panes. Close test panes when finished. - Run `/goals ` in that pane. Tmux checks the rendered menu, editor focus, widget, and keyboard handling. RPC does not render the terminal UI. -- `pi -p` has no UI, so it cannot test `Ready`, `Refine`, `Edit`, or `Cancel`. +Known stock limits: stop workers before supervisor reload (later worker exit can crash its stale context); disabled scheduler jobs are deleted on reload/shutdown. Test saved-session/solo recovery without repeating completed work; do not claim these package bugs are fixed here. -## Functional acceptance: real Herdr workflow +Keep temporary plans, audits and captures under ignored `.local/`. Git history retains the removed historical material. Do not add root handovers or duplicate READMEs. Never touch human-named files or credentials. -Pi/OpenAI procedure, requested by wassname. Automated tests do not replace this check. - -1. Read `herdr --skill` and confirm `HERDR_ENV=1`. Create a separate test pane with `--no-focus` and an isolated temporary Git repo. Never operate the user's existing worker or supervisor panes. Record the code revision and any uncommitted changes being tested. -2. Start real interactive Pi with this extension and an available real model. Use `/goals` with a trivial, bounded deliverable, for example `hello.txt` containing an exact line plus a saved byte-verification log. No GPU, dependencies or unrelated work. -3. Read the rendered planning conversation. Check that ordinary implementation details do not cause needless confirmation questions. Inspect the drafted plan and select Ready through the actual UI. -4. Confirm Ready opens a visible supervisor pane and the worker starts. Read both panes. Verify the supervisor's exact advice is visible, reaches the worker, and helps it progress toward the requested artifact. A delivery receipt alone is not proof. -5. Let the pair produce the artifact, save verification evidence, and complete the real ApproveGoal -> CompleteGoal sequence. Do not perform the task for the worker. Record any manual nudge as intervention, not autonomous success. -6. Inspect the artifact itself and its saved verification output. Check the final plan state and both sessions. Success means the requested result exists and the workflow completes, not merely that tests pass or messages were exchanged. -7. Exercise reload and supervision recovery in these test panes, preserving the current plan. Check planning exit too. Record commands available in the tested revision; do not claim unimplemented commands work. -8. When a stage fails, read both panes and the exact error before diagnosing it. Fix the cause, reload only the test instance, and retry the failed stage. After a prompt change, use a fresh task to verify changed behavior. Repeated status checks are not a repair. -9. Save pane captures, session paths, artifact paths, code revision, interventions and remaining failures under `slop/reviews/`. A wait-output timeout or match is only a signal to inspect the pane, not a pass/fail verdict. Report the observed result and gaps, not a test-count substitute. - -Keep this check small and goal-focused. Its purpose is to expose real startup, UI, steering and completion failures, not to create another review loop. Only close test panes that you created. - -## User intent for this branch - -To be clear, the hope is we can have a smart supervisor like you, with judgment and context. But it doesn't use many tokens as it checks in and sees an overview. - -It steers a smaller model, adding perspective and judgment. - -It compacts every 150k or similar to avoid cost and context rot. - -It has a goal / plan on a Ralph-loop-type repeat. - -That lets the worker be a cheaper model, and the supervisor more expensive, and still get a good outcome. - -Oh, and since it's two panes, the user can review both! - -Well, I want to see what the supervisor is thinking and saying. That's the whole point: all supervisor thinking and messages should be visible. - -So that should make it obvious that I need to see the messages, and the supervisor needs to use judgment. For example, it could say how we are tracking or whatever every time, and it would be useful, like in the recap. - -And it would only be a few output tokens. - --- wassname (spelling and punctuation corrected by Pi/OpenAI) - -## Agreed package-based design, 2026-09-10 - -> use the exdth subagent as they have a full herdr pane -> use pi schedule prompt for check ins and goal plan loop on supervisor (unless it should be part of pi plan) -> use pi-intercome for sending message between agent and supervisor where there is not great fit in this subagent package -> -> hopefully that gets me most of what I wanted in a simple, using existing packages, and even user visible way - --- wassname (verbatim) - -Pi/OpenAI implementation scope: -- Keep the short main-chat supervisor prototype and unmodified edxeth/pi-subagents. Workers use its full interactive Herdr pane and saved-session resume. Do not resume the abandoned runtime patch. -- pi-goals owns the agreed plan, review notifications and supervisor role. Installed pi-schedule-prompt owns the visible hourly wake-up: one session-bound job, no model override, reminding the same supervisor to inspect the plan/progress and continue authorized work. Remove it on pause, exit or completion; do not build another timer or scheduler. -- Use existing edxeth reports/resume where they fit. Use pi-intercom for live cross-session messages where needed; identify the actual worker session, preserve human drafts, and test delivery and auto-exit interaction before claiming compatibility. No custom message transport. -- The supervisor may edit the plan and approve completion after inspecting actual results. It delegates implementation and must not weaken the agreed goal to accept worker output. Keep normal tools; express the division in editable prompts. -- State the requested worker model in plan preferences; the supervisor selects it and checks the resolved model. Reuse existing usage displays before adding token-reporting code. -- Keep all model-facing prompts in `src/prompts.ts`, in narrative order: planning/interview, Ready, supervision and plan upkeep, check-ins/messages, completion, pause/resume and solo. Make them easy for the user to review and edit. -- Preserve useful features from `main`: goal widgets, plan-upkeep reminders, high-value planning questions and post-compaction plan context. User update: omit subtasks from widgets; long task text wastes terminal space. Keep tasks in the plan. Check which role needs each feature rather than copying the old supervisor runtime. -- Scheduled loops must be visible, editable and removable using the scheduler's own UI. Explain whether each reminder is a scheduled job or an event hook; do not advertise a second timer that does not exist. -- Keep an explicit recoverable solo mode: confirm any worker has stopped before allowing the main thread to take over implementation and plan edits. Solo completion is self-verification, not an independent supervisor review. - -On inexpensive testing: - -> try deepseek flash or glm flash for cheap tests. codex lunda on plan 2 are ok - --- wassname (verbatim; model availability and exact provider IDs still need checking) - -These decisions supersede the older two-pane supervisor transport and approval mechanics described below, but retain the user's judgment, autonomy and visibility preferences. -- Pi/OpenAI - -## Supervisor behavior preferences - -Recorded by Pi/OpenAI from wassname's instructions. - -The supervisor's job is to supervise autonomously until the agreed goal is achieved and it has inspected the actual result. Elicit high-level judgment and perspective, not compliance with a detailed procedure. It should want to diagnose and fix problems through the worker, keep useful work moving, and avoid making the human drive progress. - -Treat claims of being blocked, waiting, unable to proceed, or already done skeptically. Inspect the evidence, question assumptions, and look for authorized ways forward. Do not accept an excuse at face value or repeat status checks that cannot resolve it. Respect real dependencies and permission limits; skepticism does not authorize bypassing them. Seek justified confidence, not certainty at any cost. - -User-authorized full-profile supervision: preserve normal Pi extensions and tools, including bash/edit/write and custom actions. Inspection-only is a role instruction, not a tool denylist or enforced sandbox. Repeat the division of work in the existing short opening: inspect and diagnose directly, delegate changes through SteerWorker, and do not take over implementation or alter shared state. Do not add per-tool reminders. Worker planning restrictions and approval checks are separate and unchanged. Validate the full profile in isolated parent-owned Herdr panes; automated tests do not prove role adherence or lifecycle recovery. - -Keep the prompt generic. Do not prescribe pueue, Modal, worktrees, or a particular research setup. Explain the job and what deserves attention; let the supervisor choose useful checks. Tool requirements belong in tool descriptions. Administrative approval must not replace the requested deliverable. - -Use `@monotykamary/pi-supervisor` as a behavioral reference, not an implementation to copy wholesale. Its outcome focus, autonomous continuation, and instruction not to repeat ineffective steering are useful. Judge our behavior in real sessions, not by test counts alone. - -Pi/OpenAI implementation: each review repeats the short supervisor opening and current plan outcome, preferences, goals and discriminators, excluding task/evidence detail. Startup and compaction repeat the longer role prompt and full active plan before appendices/history. The long prompt asks the supervisor to read applicable AGENTS.md instructions and relevant skills rather than assuming project-specific preferences. Both forms preserve plan wording. Prompt inspiration: Anthropic's constitution (intent and autonomy) and @monotykamary/pi-supervisor (outcome focus and effective steering). Repetition supports judgment; it does not establish success. - -Keep brief visible recaps that add judgment rather than repeat unchanged status. Preserve useful reasoning and evidence checks; reduce redundant context and reviews before reducing judgment. Manual checkbox changes are claims, not proof of completion. Plan edits should reach the supervisor so it can judge drift and direct corrections. - -## Earlier supervision workflow discussion - -I already have pi-intercom-supervisor, but thought using pi-subagents could make it simpler. The idea is that the user makes a plan as in pi-goals, but on this branch, instead of a naive stateless subagent, we 1) fork, 2) compact, and 3) make it a supervisor with a prompt as in pi-intercom-supervisor. The supervisor is cheap because it sees only high-level material, which costs fewer tokens. It has good judgement because it sees a) compacted planning context, b) the plan, and c) summarised context (for example, my modified pi-vcc). This lets it operate read-only and steer the worker without losing track. It also compacts every 100k tokens to keep it cheap and high-level. - -I am now thinking the subagent implementation may be too difficult. To keep the plan and forking, this branch of pi-goals could make another Pi session, perhaps using the fork explicitly, and use pi-intercom or pi-messenger to communicate with it. The user can switch to it, or Herdr could open it automatically. - --- wassname +Branch instructions consolidated by Pi/OpenAI from wassname's preferences. diff --git a/ARCHIVED.md b/ARCHIVED.md deleted file mode 100644 index eed60d3..0000000 --- a/ARCHIVED.md +++ /dev/null @@ -1,3 +0,0 @@ -# ARCHIVED - -Superseded by [pi-goals](https://github.com/wassname/pi-goals). diff --git a/README.md b/README.md index ff0141d..a4289d5 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ The plan file looks like this: +### Preferences + +- preferred worker model: + ### User voice - │ "" @@ -51,21 +55,21 @@ The plan file looks like this: ### Goals 1. [ ] goal: -- subtle failure mode: -- discriminator: -- tasks: - 1. [ ] -- evidence: (empty until sign-off) + - subtle failure mode: + - discriminator: + - tasks: + 1. [ ] + - evidence: (empty until sign-off) ### Future work / out of scope ### Log -### Interview +### Interview (optional) -### Learnings +### Learnings (optional) -### Papercuts - problems, gotchas, suggestions +### Papercuts - problems, gotchas, suggestions (optional) ``` ## Related work @@ -77,29 +81,26 @@ resync-after-compaction from [tmonk/pi-goal-x](https://github.com/tmonk/pi-goal- ## Install -Requires Herdr, [edxeth/pi-subagents](https://github.com/edxeth/pi-subagents), pi-intercom and pi-schedule-prompt. Remove the unrelated `npm:pi-subagents` package if installed. +Requires Herdr. The package includes the worker, messaging and scheduled-prompt extensions. Remove separate installations of pi-subagents, pi-intercom and pi-schedule-prompt to avoid loading them twice. ```bash -pi install git:github.com/edxeth/pi-subagents@v2.9.0 -pi install npm:pi-intercom -pi install npm:pi-schedule-prompt pi install git:github.com/wassname/pi-goals@experiment/main-supervisor-edxeth ``` -Copy [`prototype/agents/goals-worker.md`](prototype/agents/goals-worker.md) into `~/.pi/agent/agents/`, then start a fresh Pi session. +Copy [`agents/goals-worker.md`](agents/goals-worker.md) into `~/.pi/agent/agents/`, then start a fresh Pi session. Or for development: ```bash git clone -b experiment/main-supervisor-edxeth https://github.com/wassname/pi-goals cd pi-goals && npm install -pi -e ./src/prototype.ts +pi -e ./src/index.ts ``` ## Use ``` -/goals new CSV export for the report view +/goals ``` `/goals` opens the action menu. New plan enters plan mode and starts a conversation; the objective is an optional seed. From there: @@ -120,7 +121,7 @@ main chat do the work after confirming other workers stopped; completion is then `/goals model ` picks the worker model. `/schedule-prompt` manages check-ins. Stop workers before reloading the supervisor: the subagent package can otherwise crash it when a -worker later exits. The scheduler deletes disabled jobs on reload. [Test results and recovery](slop/reviews/20260910_package-supervision-herdr.md). +worker later exits. The scheduler deletes disabled jobs on reload. Restart the saved Pi session and reattach the plan. ## Prompts @@ -129,13 +130,21 @@ You can read all the prompts in conversation order in [`src/prompts.ts`](src/pro ## Develop ```bash -pi -e ./src/prototype.ts # load locally; do not also load the installed copy +pi -e ./src/index.ts # load locally; do not also load the installed copy npm test # all unit, flow, and Pi RPC tests npm run test:rpc # Pi RPC review flow with a local offline model npm run typecheck npm run lint ``` +To measure recorded usage since the latest planning start: + +```bash +node scripts/session-usage.mjs +``` + +This separates output, uncached input and repeated cached input. It excludes subprocess API calls. [Isolated Herdr test setup](scripts/prepare-trial.mjs). + ## License MIT diff --git a/prototype/agents/goals-worker.md b/agents/goals-worker.md similarity index 96% rename from prototype/agents/goals-worker.md rename to agents/goals-worker.md index 9c1114e..0abcc87 100644 --- a/prototype/agents/goals-worker.md +++ b/agents/goals-worker.md @@ -22,4 +22,4 @@ Call AttachGoalPlan with the supplied absolute plan path. Send the supervisor an This worker uses a clean model context linked to the parent, not a full transcript fork. The parent supplies the approved plan and task. Send completion through Intercom and leave this pane open for follow-up messages. Do not call caller_ping, exit or shutdown: an unsent editor draft may exist even though it is absent from model context. Saved-session resume applies only after this session has stopped. If the user takes over interactively, follow their direction. -Prepared by Pi/OpenAI for the isolated edxeth prototype. +Prepared by Pi/OpenAI for pi-goals. diff --git a/docs/reviews/goals_menu2.md b/docs/reviews/goals_menu2.md deleted file mode 100644 index 783f189..0000000 --- a/docs/reviews/goals_menu2.md +++ /dev/null @@ -1,66 +0,0 @@ -Let me get the diff to see what changed: - -Now let me check the git history to see what changedNow I have full context. Let me verify the pi API behavior by checking the relevant source files to confirm the claims about `sendMessage`, `compact`, and event orderingNow I have all the context needed. Let me verify the `sendMessage` path more carefully: - -I now have a complete picture. Here is the review. - ---- - -## Code Review: ready menu — print plan + add "Ready + compact" - -### Summary -The `agent_end` handler now prints the plan working set into the transcript before the menu, and adds a 4th option ("Ready + compact") that compacts the planning chatter away before the work turn. The logic is correct and well-considered, with one structural race and one stale-widget gap. - -### Important (should fix) - -- **`src/index.ts:~248` (the `agent_end` handler) — race: plan mode is turned off before compaction completes.** - `state = { ...state, isPlanMode: false }` executes before `ctx.compact()` is called (let alone finished). The compaction is fire-and-forget. If the human types and sends a message in the window between the state flip and the compaction callback firing, the agent starts a turn in normal mode *without* the work instruction. The work instruction arrives later (via `sendUserMessage` in `onComplete`/`onError`), triggering a second, redundant turn. - The window is small (compaction is fast) but the behavior is undefined — the agent could start executing before the work instruction lands. - **Fix**: flip `isPlanMode` inside the callbacks, not before. Move `state = { ...state, isPlanMode: false }; persist(); updateWidget(ctx);` into both `onComplete` and `onError`, and also into the non-compact branch (where it already is, effectively). The `work` string can be defined before the branch. - - ```typescript - if (!choice.includes("compact")) { - state = { ...state, isPlanMode: false }; - persist(); - updateWidget(ctx); - pi.sendUserMessage(work, { deliverAs: "followUp" }); - return; - } - ctx.compact({ - customInstructions: `...`, - onComplete: () => { - state = { ...state, isPlanMode: false }; - persist(); - updateWidget(ctx); - pi.sendUserMessage(work, { deliverAs: "followUp" }); - }, - onError: (e) => { - ctx.ui.notify(`Compaction failed (${e.message}); starting work anyway.`, "warning"); - state = { ...state, isPlanMode: false }; - persist(); - updateWidget(ctx); - pi.sendUserMessage(work, { deliverAs: "followUp" }); - }, - }); - ``` - - This also means the widget stays in "planning" mode during compaction, which is truthful — compaction hasn't finished yet. - -### Suggestions - -- **`src/index.ts:~248` — widget not refreshed after `$EDITOR`.** - When the human chooses "Open in $EDITOR", `spawnSync` blocks, then `continue` re-enters the loop. The plan is re-read and potentially re-printed, but `updateWidget` is not called. If the human changed goal statuses (e.g. ticked a checkbox), the widget stays stale until the next `turn_end`. - Add `updateWidget(ctx);` after the `spawnSync` line (or inside the `continue` branch before the continue). - -- **`src/index.ts:~248` — `spawnSync` blocks the event loop.** - `spawnSync(process.env.EDITOR || ...)` is a synchronous blocking call. While the editor is open, no async work (including compaction from a previous iteration, timers, etc.) can proceed. This is fine for a local TUI tool, but worth noting — if the editor hangs or the human walks away, the entire pi process is frozen. - -### Positive - -- **De-duplication is correct.** `printed` is a local variable, fresh per `agent_end` call, and correctly suppresses re-printing when the working set hasn't changed across editor passes. The `while` loop exit condition (`scanGoals(...).length > 0`) correctly handles the human deleting all goals in the editor. -- **String matching is safe.** `choice?.startsWith("Ready")` gates both Ready options, then `choice.includes("compact")` distinguishes them. The word "compact" appears only in the "Ready + compact" string. No ambiguity. -- **Both compaction callbacks queue the work turn.** `onComplete` and `onError` both call `pi.sendUserMessage(work, ...)`. A failed compaction does not strand the session — work starts anyway, with a notification. -- **`session_compact` → `resyncReason` → injection chain is correct.** The pi source confirms `session_compact` fires (and is awaited) *before* `this.compact()` resolves and `onComplete` fires. So `resyncReason` is set before the next LLM call, and the full plan file is re-injected. The compaction summarizes away the exploration; the plan itself survives. - -### Verdict -**REQUEST CHANGES** — the race between `isPlanMode = false` and compaction completion is a real timing bug that can cause the agent to start a turn without the work instruction. The fix is straightforward: move the state flip into the callbacks. \ No newline at end of file diff --git a/docs/reviews/goals_menu2_r2.md b/docs/reviews/goals_menu2_r2.md deleted file mode 100644 index 6d5c6f8..0000000 --- a/docs/reviews/goals_menu2_r2.md +++ /dev/null @@ -1,22 +0,0 @@ -You're right on both counts. Let me trace through each. - -**Finding 1, withdrawn.** Keeping `isPlanMode` true during compaction would mean any message the human sends while compaction runs gets processed in plan mode: blocked tools (`tool_call` hook), a "You are still in plan mode" injection (`context` hook), and a second Ready menu on `agent_end`. The current order (flip first, then compact) means that same message is processed in work mode — the destination the session is heading to. The cost is at worst one redundant turn when the `onComplete` follow-up lands. Your order is the correct one. - -**Finding 2, withdrawn.** `updateWidget` with `isPlanMode === true` hits the early return that sets two fixed strings already on screen. It is a pure no-op. - ---- - -## Code Review: ready menu — print plan + add "Ready + compact" - -### Summary -The `agent_end` handler now prints the plan working set into the transcript before the menu, and adds a 4th option ("Ready + compact") that compacts the planning chatter away before the work turn. The logic is correct and well-considered. - -### Positive -- **De-duplication is correct.** `printed` is a local variable, fresh per `agent_end` call, and suppresses re-printing when the working set hasn't changed across `$EDITOR` passes. The `while` loop exits cleanly if the human deletes all goals in the editor. -- **String matching is safe.** `choice?.startsWith("Ready")` gates both Ready options, then `choice.includes("compact")` distinguishes them. The word "compact" appears only in the one label. -- **Both compaction callbacks queue the work turn.** `onComplete` and `onError` both call `pi.sendUserMessage(work, ...)`. A failed compaction does not strand the session. -- **The `session_compact` → `resyncReason` → injection chain is correct.** The pi source confirms `session_compact` fires (and is awaited) before `this.compact()` resolves and `onComplete` fires. The full plan file is re-injected on the next LLM call; the compaction summarizes away the exploration, not the plan. -- **The state-flip-before-compaction order is deliberate and correct.** Flipping `isPlanMode` first means any message the human sends during compaction is handled in work mode, not plan mode (where tools would be blocked and a contradictory injection would fire). - -### Verdict -**APPROVE** — no bugs found. The two concerns raised initially were incorrect; the code handles the interleaving correctly. \ No newline at end of file diff --git a/docs/reviews/pi-goals-grok-4-6-retry.md b/docs/reviews/pi-goals-grok-4-6-retry.md deleted file mode 100644 index 7f2707c..0000000 --- a/docs/reviews/pi-goals-grok-4-6-retry.md +++ /dev/null @@ -1,31 +0,0 @@ -I only have a file-read tool here, so I’ll start from the repo files I can open and look for the plan-versioning and judge-related changes.Continuing through the judge path and tests.Reading the rest of the judge prompt and the tests that cover these behaviors.Looking for the test files.Trying common test filenames under `src` and `test`.Checking other likely files mentioned in the README.I couldn’t run `git diff` (no shell tool). From the files I could read, these are the concrete defects against the required behaviors: - -**1. Uncommitted conflict markers in `README.md`** -The working tree still has `<<<<<<< Updated upstream` / `=======` / `>>>>>>> Stashed changes` (and a truncated upstream judge sentence). That is not a reviewable change set. - -**2. Judge “checked artifacts” gate is too brittle** -`decideSignOff` only accepts: - -```ts -/^#{0,6}\s*(?:\*\*)?checks(?:\*\*)?:\s*\n(?:[-*]\s+.+\n?)+\s*VERDICT:/im -``` - -The prompt asks for a `checks:` heading plus bullets, then **exactly two lines**: - -``` -VERDICT: accept | reject -missing: ... -``` - -Any extra blank line, `Checks` with other wrapping, or a sentence between the last bullet and `VERDICT:` rejects a real accept. That’s stricter than “must show artifact quotes,” and can fail a judge that did the right thing. - -**3. Grill-me vs Ready is prompt-only** -`Grill me` sets `skipReadyMenu` for **one** `agent_end`, then the next turn with any goal lines shows Ready again. Nothing checks that the understanding question was asked **and answered**. If the model asks and offers Ready in the same turn, or the human never replies, Ready is still offered. Required: no Ready until that question has completed. - -**4. Legacy unversioned plans are dropped with no migration path in code** -Only `-vN.md` is selected; `planVersion` restore is the only resume hook. That’s the intended break, but I found no test that old `.pi/plan/*.md` names stay unused and that a new `/goals` always writes a fresh `-vN` without rewriting prior files. - -**5. Could not verify the focused tests or `git diff`** -No test file opened (`src/index.test.ts`, `test/*.test.ts`, etc. all missing from this view). I could not confirm coverage for version reservation, visible plan print, grill-me sequencing, or reject-accept-without-checks. - -I would **not APPROVE** this set as-is. Fix the README conflicts, tighten grill-me so Ready stays suppressed until after the human answers, and either loosen the checks regex to the documented “quotes present before VERDICT” rule or lock the exact judge format in a test. \ No newline at end of file diff --git a/docs/reviews/pi-goals-kimi-k3.md b/docs/reviews/pi-goals-kimi-k3.md deleted file mode 100644 index ccab224..0000000 --- a/docs/reviews/pi-goals-kimi-k3.md +++ /dev/null @@ -1,41 +0,0 @@ -I only have the `read` tool available, so let me locate the changed files by probing the repository structure.The extension source is clear. Let me verify the pi API usage (`sendMessage` with `customType`/`display`, `appendEntry`, `getSessionId`, `ctx.compact`) against the pi docs, since several required behaviors hinge on them.I can't run `git diff` or the test suite — this review environment only exposes a `read` tool, and the test directory can't be enumerated (every probe under `test/` returned ENOENT, and directories can't be listed). So I reviewed the changed files directly in the working tree (`src/index.ts`, `src/prompts.ts`, `README.md`, `scripts/check-judge-footprint.sh`) against the five required behaviors, and verified every pi API the behaviors depend on (`pi.sendMessage` with `customType`/`display`, `pi.appendEntry` restore via `session_start`, `pi.sendUserMessage` followUp, `ctx.compact` with onComplete/onError) against the installed pi docs. Findings below. - -## Defects - -**1. `/goals` arg parsing swallows ordinary objectives — src/index.ts, `/goals` handler** - -```ts -if (arg.startsWith("judge")) { -``` - -Any objective whose first word is "judge" — e.g. `/goals judge the vendor options`, `/goals judge readiness of the export` — is treated as the judge-model subcommand: it sets `judgeModel` to "the vendor options" and **does not reserve a new `.pi/plan/-vN.md`**, violating "every ordinary `/goals` invocation reserves a new file". Same class of issue for an objective that is exactly `clear` (deletes the plan instead of planning). Use exact-match subcommand parsing (`arg === "judge"` / `arg.startsWith("judge ")` at minimum, and even then an objective like "judge model quality" is unreachable — a `:` or `--` separator would be safer). - -**2. The checked-artifact-list regex false-rejects compliant judges — `decideSignOff`, src/index.ts** - -```ts -const checks = /^checks:\s*\n(?:-\s+.+\n)+VERDICT:/im.test(judge.output); -``` - -This requires the last `- ` bullet to be *immediately* followed by `VERDICT:` with no blank line, and requires the heading to be exactly `checks:` at line start. Nothing in `judgeSystem` tells the judge not to separate sections with a blank line (models habitually emit `…bullet\n\nVERDICT: accept`), and a judge writing `## checks:` or `**checks:**` also fails. Result: a valid accept *with* a real checked-artifact list is rejected, and the working agent gets a "Missing: checked-artifact list" reply it already satisfied — a retry loop against a nondeterministic judge. It's fail-closed (never accepts without the list, so the hard requirement holds), but as written it will produce systematic false rejects. Allow optional blank lines / formatting, e.g. `/^#*\s*checks:\s*\n(?:[-*]\s+.+\n)+\s*VERDICT:/im`. - -**3. Judge transcript files collide within the same minute — CompleteGoal `execute`, src/index.ts** - -```ts -const rel = `.pi/judge/${stamp().replace(/[: ]/g, "-")}.md`; -``` - -`stamp()` has minute resolution, so two sign-offs in the same minute (two goals signed off back-to-back — the common case) write the same `.pi/judge/.md` path and the second silently overwrites the first's full transcript, contradicting "every run saves the judge's full transcript … referenced from the log line" (both log lines then cite one file containing only the second run). Add seconds or a short unique suffix. - -## Minor / cosmetic - -- Misindented closing `}` of the `if (!checks)` block in `decideSignOff` (extra indentation on the `}` line) — likely flagged by `biome check src/`, which `prepublishOnly` runs. -- In plan mode, if the agent drafts a plan with no recognizable `goal:` checkbox line, the `agent_end` `while (scanGoals(...).length > 0)` loop never shows the Ready menu and plan mode can't be exited via the menu; the "no recognizable goal line" nudge in `dueInjection` only fires in the *non*-plan-mode branch. Not one of the five required behaviors, but a reachable dead end (escape hatch is `/goals clear`). - -## Behaviors verified as correct - -- **Versioning**: each ordinary `/goals` writes a fresh `-vN.md` (empty reservation) via `nextPlanVersion` (max+1, so no clobbering); prior versions are never touched; legacy unversioned names are ignored by `nextPlanVersion` and never read — consistent with the intentional breaking change. `planVersion` is persisted via `appendEntry` and restored in `session_start`, so it survives resume/reload. -- **Visible plan before Ready**: `pi.sendMessage({ customType: "plan", content: plan, display: true })` fires before `ctx.ui.select(...)`, and re-prints inside the `$EDITOR` loop when the text changed. `sendMessage`+`display: true` is the documented visible-transcript mechanism. -- **Grill me**: sets `skipReadyMenu`, queues the single understanding-check question as a followUp user message, and consumes the skip at the next `agent_end` — so no Ready offer exists between asking the question and the human's reply being processed. `skipReadyMenu` is persisted, so it survives resume mid-grill. -- **Judge**: `buildJudgeArgs` yields `pi -p --no-session --no-extensions --tools read,grep,find,ls --exclude-tools edit,write`; `judgeUser` embeds the complete plan file; `judgeSystem` demands per-artifact verbatim quotes framed as an inspectable record ("not hidden reasoning", "no bullet for a file you did not open"); an accept without the `checks:` list is rejected in `decideSignOff` (subject to defect 2); full transcript persisted per run (subject to defect 3). - -Main caveat: I could not execute `git diff` or `npm test` here, so I can't confirm the exact diff boundaries or that the focused vitest cases (judge argv invariants, `appendLog`, `decideSignOff` fail-forward) pass — defects 1–3 are from source inspection, not reproduced runs. Not approved as-is; defects 1 and 2 are worth fixing before merge. \ No newline at end of file diff --git a/docs/reviews/review.md b/docs/reviews/review.md deleted file mode 100644 index b0d1965..0000000 --- a/docs/reviews/review.md +++ /dev/null @@ -1,62 +0,0 @@ -Code review against spec `docs/spec/2026-06-15_pi-goals.md`. - ---- - -### (A) SPEC MISMATCH — code does not match spec intent - -1. **No loop judge** (spec §9, §3b). The extension lacks any per‑turn evaluation that would decide continue/pause; the loop‑judge prompt (`loopJudgeSystem`, `loopJudgeUser`) is defined but never invoked. No motion. - -2. **`/goal` command missing** (spec §7). No handler for `/goal` (restart loop, pause, resume, clear, status). The only command is `/plan`. - -3. **`/subgoal` command missing** (spec §7). Not implemented. - -4. **`CancelGoal` tool not implemented** (spec §5, optional but present in spec). Not a blocker but a gap. - -5. **Plan‑phase model selection (D12) not implemented**. `planDrafting` always runs on the default model; there is no sticky per‑phase model choice, no selection menu, and no persisting of a plan‑phase model reference. - -6. **Widget does not flag `done` goals that lack a sign‑off log line** (spec §7, §6). The widget hides all done goals unconditionally; the visibility guard is missing. - -7. **`/plan` (no args) does not render the task‑list widget** (spec §7). `showPlan()` dumps raw file content via `notify`; the widget is only set through `updateWidget()` on other events, not by the command itself. - -8. **Injection message role** (spec §11). The `before_agent_start` hook returns a `customType` message with `display: false`. The spec demands a **late user‑role message** to avoid system‑prompt mutation; the actual message role depends on the pi API and may be system, not user, risking cache breakage. - -9. **Missing pre‑compact hook** (spec §8). No `pre‑compact` hook to flush any in‑memory state (even just ensuring `plan.md` is up‑to‑date) before compaction. - -10. **Reminder cadence deviates** (spec §8a). The spec calls for firing after N file‑modifying turns since last `plan.md` update. The code fires if `plan.md` is byte‑identical between agent starts, which is a coarser proxy. - ---- - -### (B) DEAD/UNUSED CODE - -| File | Lines | Reason | -|------|-------|--------| -| `src/prompts.ts` | 128‑146 | `loopJudgeSystem` and `loopJudgeUser` exported but never used. | -| `src/prompts.ts` | 115‑118 | `continuation` exported but never used (the loop is not built). | - ---- - -### (C) OVERLY LONG OR REDUNDANT COMMENTS - -The file‑header comments in `index.ts` (lines 1‑20) and `plan‑file.ts` (lines 1‑26) are fairly concise descriptions of the design; they are not excessive. **No comment bloat worth flagging.** - ---- - -### (D) OVER‑ENGINEERING vs. “super simple” goal - -None. The line‑scanner in `plan‑file.ts` is minimal; the `getPiInvocation()` helper is a straightforward copy from the oracle extension; no unnecessary abstraction or defensive layers. - ---- - -### (E) REAL BUGS - -- **`cmdCtx.newSession` cast risk** (src/index.ts:272, 201). - `reviewLoop` casts `ctx` (type `ExtensionContext`) to `ExtensionCommandContext` to pass to `startExecution`, which calls `cmdCtx.newSession(...)`. If the concrete context does not carry that method, it fails at runtime. (In practice the same object may satisfy it, but the cast hides the truth.) - -- **`showPlan` raw content instead of widget** (src/index.ts:136‑143). - `/plan` with no arguments shows the file content via `ctx.ui.notify`, not the structured task‑list widget the spec expects. The widget is rendered separately via `updateWidget`, but the command does not trigger it, so the output is inconsistent. - -No other obvious logic errors; the sign‑off flow, logging, and parsing work as intended. - ---- - -**Verdict:** A clean scaffold for the sign‑off path, but missing the autonomous loop, `/goal` command, and plan‑phase model selection means it’s not yet the “work autonomously” extension the spec describes. \ No newline at end of file diff --git a/docs/slop/audit/20260826_pi-plan-aligned-planning.md b/docs/slop/audit/20260826_pi-plan-aligned-planning.md deleted file mode 100644 index 9ecc427..0000000 --- a/docs/slop/audit/20260826_pi-plan-aligned-planning.md +++ /dev/null @@ -1,25 +0,0 @@ -# Verification: state-aligned planning mode - -## Commands - -```text -$ npm test -Test Files 8 passed (8) -Tests 29 passed (29) - -$ npm run typecheck -> tsc --noEmit - -$ npm run lint -Checked 8 files in 17ms. No fixes applied. - -$ git diff --check -``` - -## Read - -[test/goals-flow.test.ts](../../../test/goals-flow.test.ts) covers the visible plan before Refine, an editor prompt before a Refine revision turn, exact multiline Refine notes in `## Interview`, Ready as the only work handoff, Pi editor then Cancel, phase restoration, planning snapshot, writable plan path, allowed `pwd && ls && git log` and `cd . && ls -la`, blocked pipe, and blocked `CompleteGoal`. - -[test/prompts.test.ts](../../../test/prompts.test.ts) locks the prompt instruction to inspect repository facts or search the web only when it can resolve a fact, ask a short self-contained batch of high-impact questions in the human's language with recommendations, and forbid placeholder goals. - -[test/rpc-review.test.ts](../../../test/rpc-review.test.ts) starts the installed Pi RPC executable with [offline-model.ts](../../../test/fixtures/offline-model.ts), selects Refine through Pi's real dialog protocol, receives the editor request before the revision call, then submits notes and observes the revision call. The test uses a local HTTP model, so it spends no API credits. diff --git a/docs/slop/plans/20260706_plan-flow-and-judge-review.md b/docs/slop/plans/20260706_plan-flow-and-judge-review.md deleted file mode 100644 index 65cf0ec..0000000 --- a/docs/slop/plans/20260706_plan-flow-and-judge-review.md +++ /dev/null @@ -1,33 +0,0 @@ -# Plan flow and judge review - -- [x] goal: Each new `/goals` draft uses a fresh session-plan version - - [x] Persist the selected `-vN` name so resume, reminders, Ready, and sign-off use one file. - - [x] Keep earlier versioned files unchanged. - - [x] Reserve `--clear` and `--judge` for commands so normal objectives are always new drafts. - - failure mode: a second `/goals`, including an objective that begins with `judge`, changes the earlier plan or does not make a draft. - - deliverable: [goals-flow.test.ts](../../../test/goals-flow.test.ts) shows an unchanged legacy file and `v1`, new `v2`, and `judge the vendor options` in new `v3`. -- [x] goal: Plan review asks and displays the needed context - - [x] Add `Grill me` to the Ready menu and queue an understanding-check interview turn. - - [x] Keep one short goal subject with its full indented context block. - - [x] Keep visible plan output before the Ready dialog. - - failure mode: Grill me starts work or the plan is only hidden in an edit call. - - deliverable: [goals-flow.test.ts](../../../test/goals-flow.test.ts) records display before dialog and the grill follow-up. -- [x] goal: Judge review is visible without being confused with agent evidence - - [x] Require concise observed checks before the verdict. - - [x] Save the full judge reply under a unique path and link it from the plan log. - - [x] Accept a headed check list with normal Markdown spacing, but reject an accept with no list. - - failure mode: provider-private reasoning is claimed as evidence, the review is not inspectable, or a correct judge reply is rejected for blank-line formatting. - - deliverable: [decide-signoff.test.ts](../../../test/decide-signoff.test.ts) locks the checked-artifact review contract, including a Markdown heading and blank line before the verdict. - -## UAT / Verification - -Observed 2026-08-24: `npm test` reported `Test Files 6 passed (6)` and `Tests 22 passed (22)`. -`npm run typecheck`, `npm run lint`, and `git diff --check` exited 0. The focused flow test proves -plan versioning, visible plan-before-dialog ordering, Grill me behavior, and objectives beginning -with `judge`. - -## Appendix (context, not approved) - -Issue #1 has a 600 second judge timeout now. The judge stays a separate read-only `pi -p --no-session` subprocess. Intercom is unsuitable because it has no equivalent isolation boundary. - -External review: [Kimi K3](../../reviews/pi-goals-kimi-k3.md) found the command-prefix, check-list formatting, and transcript-path defects; all were fixed. [Grok 4.6](../../reviews/pi-goals-grok-4-6-retry.md) confirmed the check-list concern. Its Grill me concern does not apply: `skipReadyMenu` suppresses the menu after the generated follow-up, and the next `agent_end` follows the human reply. diff --git a/docs/slop/plans/20260826_pi-plan-aligned-planning.md b/docs/slop/plans/20260826_pi-plan-aligned-planning.md deleted file mode 100644 index b4964f5..0000000 --- a/docs/slop/plans/20260826_pi-plan-aligned-planning.md +++ /dev/null @@ -1,53 +0,0 @@ -# State-aligned planning mode - -Pi-goals will use pi-plan's small phase model. The UI, tool gate, and agent context will read the same persisted phase. Planning still keeps pi-goals' judgeable goals, direct user quotes, and interview record. - -- [x] goal: Planning state survives restart and matches the UI and agent context - - [ ] Replace `isPlanMode` and `skipReadyMenu` with persisted `phase: planning | working`. - - [ ] Render the planning widget, inject the hidden planning-state snapshot, and restore state from that phase. - - [ ] Restore the snapshot after restart or compaction without repeating the full drafting prompt every turn. - - subtle failure mode: the UI says planning but a resumed or compacted agent sees work mode. - - discriminator: a flow test restores planning and observes the planning snapshot; working has neither. - - evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) restores persisted planning state and observes `[PLANNING MODE]`; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`. -- [x] goal: Planning blocks implementation while allowing fact finding - - [ ] Allow writes only to the active plan file. - - [ ] Block implementation tools, `CompleteGoal`, and bash write or pipe attempts with a planning-mode explanation. - - [ ] Allow ordinary read-only inspection commands such as `pwd && ls && git log`. - - subtle failure mode: an agent marks a goal active or changes project code before approval. - - discriminator: flow tests reject each work route and allow the inspection command. - - evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) asserts allowed `pwd && ls && git log`, blocked pipe, non-plan write, and `CompleteGoal`; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`. -- [x] goal: Planning interviews and revision notes are durable user evidence - - [ ] Teach the planning prompt to ask each independent, high-impact user-decision frontier with a recommendation, while researching facts itself. - - [ ] Keep typed answers and `Refine` editor notes verbatim under `## Interview`. - - [ ] Exempt `## User voice` and `## Interview` from working-set line pressure. - - subtle failure mode: the plan silently assumes preferences or loses a revision note. - - discriminator: a flow test opens Refine and finds its exact multiline text in `## Interview`. - - evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) matches the exact multiline Refine note under `## Interview`; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`. -- [/] goal: The settled plan review is concise and cannot start work accidentally - - [ ] Use `agent_settled` to visibly print the full plan, then offer `Ready`, `Refine`, `Edit`, and `Cancel`. - - [ ] Ready alone sends the work handoff. Refine sends one explicit revision turn. Edit opens Pi's full-plan editor. Cancel leaves planning. - - subtle failure mode: a review choice queues an unrequested agent turn or hides the plan below the dialog. - - discriminator: flow tests show plan before the menu and distinguish all four actions. - - evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) shows plan before the menu and isolates Ready as the work handoff; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`. Pending human Pi TUI check. -- [x] goal: Planning resolves facts, interpretation, and approval before overnight work - - [x] Use repository inspection or web search when either can resolve a discoverable fact. - - [x] Require human confirmation for the agent's interpretation, unresolved task or outcome, scope, and decisions needing later approval. - - [x] Batch independent high-impact questions with the needed context, the human's terms, ASD-STE100 language, and a recommendation. - - [x] Ban placeholder goals such as "work out the thing" before the plan review menu. - - subtle failure mode: the plan has a formal discriminator but silently chooses an editorial direction or other human decision. - - discriminator: [prompts.test.ts](../../../test/prompts.test.ts) locks the research, clarification, approval, question-batch, and concrete-goal rules in the model prompt. - - evidence: [prompts.ts](../../../src/prompts.ts) makes research conditional on whether it can resolve a fact, then requires human confirmation and approval before Ready. [prompts.test.ts](../../../test/prompts.test.ts) checks those requirements. [verification](../audit/20260826_pi-plan-aligned-planning.md) records `29 passed`. -- [x] goal: Refine waits for text in Pi's real dialog protocol - - [x] Run Pi in RPC mode against a local no-cost model. - - [x] Select Refine, observe the editor request, then submit text and observe the revision turn. - - subtle failure mode: a mocked editor hides a Pi RPC ordering defect, so Refine starts a turn before the human can type. - - discriminator: [rpc-review.test.ts](../../../test/rpc-review.test.ts) uses Pi's `extension_ui_request` and `extension_ui_response` protocol and observes two model requests before editor input, then the third revision request after it. - - evidence: [rpc-review.test.ts](../../../test/rpc-review.test.ts) starts the installed Pi executable plus [offline-model.ts](../../../test/fixtures/offline-model.ts), with no credential or network dependency. [verification](../audit/20260826_pi-plan-aligned-planning.md) records its pass. - -## UAT / Verification - -`npm test`, `npm run typecheck`, and `npm run lint` pass. Read [test/goals-flow.test.ts](../../../test/goals-flow.test.ts): its assertions must show a restored planning phase, visible plan before review, exact recorded refinement, blocked work routes, and a work message only after Ready. - -## Appendix (context, not approved) - -Accepted: copy pi-plan's persisted phase, `agent_settled` review, and Pi editor. Do not copy its restrictive shell allowlist. Grill is a planning instruction, not a menu item: ask the whole independent frontier in rounds, with recommendations. `Ready + compact` is removed; compaction remains Pi's normal command after Ready. diff --git a/docs/slop/plans/20260909_main-supervisor-prototype.md b/docs/slop/plans/20260909_main-supervisor-prototype.md deleted file mode 100644 index 45ea7ae..0000000 --- a/docs/slop/plans/20260909_main-supervisor-prototype.md +++ /dev/null @@ -1,24 +0,0 @@ -# Main-chat supervisor prototype - -Prepared by Pi/OpenAI. Separate worktree; do not load into existing research sessions. - -- [x] goal: keep planning and supervision in the main chat, with an interactive edxeth worker - - Retain the plan file, review menu, widget and evidence-based completion. - - Use edxeth's public tools for launch/resume/kill, not another pairing protocol. - - Keep parent inspection tools; worker uses normal tools/extensions/skills. - - Deliver role and plan notices as saved messages; no context-array edits or forced compaction. - - failure modes: wrong subagent package, inherited supervisor role, duplicate worker, unsigned ticks counted as success. - - deliverable: `src/prototype.ts`, `prototype/agents/goals-worker.md`, and `prototype/README.md`. - - evidence: `../reviews/edxeth-prototype/copied-replay.txt` records three accepted requests, zero network and no context hooks; the real runtime changed from planning to supervisor at Ready. -- [x] goal: demonstrate two goals through real interactive Pi in isolated Herdr panes - - Observe worker launch, artifact creation, parent inspection and both completion records. - - Exercise parent reload and continuation of the saved worker session. - - failure modes: tests pass but no visible worker; success claimed from tool receipts rather than files. - - deliverable: `../reviews/edxeth-prototype/README.md` with artifacts, pane captures, session summaries, interventions and limits. - - evidence: fresh trial completed both goals after Ready without further nudges; saved launch/resume results share the worker session path, parent and operator verification passed, and reload retained `2/2 reviewed`. Active-worker crash recovery is not claimed. - -## UAT / Verification -Success: parent stays in its original session/model, launches a real worker pane, inspects two actual artifacts and signs off both goals. -Likely failure: edxeth tool collision or launch error; inspect the test panes and report exact source/version and error. -Sneaky failure: child does not report, or parent performs the task; inspect session tool history and resulting files, not only final prose. -No existing worker, supervisor, JSONL, installed package or global settings may be changed. Use fresh isolated test sessions, and only copies for encrypted-history replay checks. diff --git a/docs/slop/reviews/edxeth-prototype/README.md b/docs/slop/reviews/edxeth-prototype/README.md deleted file mode 100644 index 0fe3c84..0000000 --- a/docs/slop/reviews/edxeth-prototype/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Main-chat supervisor prototype: functional result - -Prepared by Pi/OpenAI, 2026-09-10. Local worktree `experiment/main-supervisor-edxeth`, based on `15dd7f02225d366ae920509bb23066be83956fb8`. edxeth runtime pinned to `953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4` (v2.9.0); installed Pi 0.85.1. Exact prototype source is in this commit. - -## Result - -The fresh real-model Herdr trial completed both goals after human Ready, without an operator message between Ready and completion. The parent stayed in its original chat and model. One interactive worker implemented the greeting, returned its report, and was resumed from the same saved session to implement the CLI. The parent inspected the files, independently reran verification and recorded both sign-offs. A separate operator rerun also passed. - -This establishes a small end-to-end prototype, not general reliability, cheaper-model quality or encrypted-history compatibility of every configured extension. - -## Evidence and how to read it - -- [Actual worker Pi pane](worker-pane.txt): Herdr captured `▸ Agent greeting-worker (goals-worker)` and `gpt-6-astra Github Copilot minimal`, including Pi's input editor/footer in pane `w1:p19`. This is an interactive process, not a transcript viewer. The trace ties that pane to the test worker. The worker auto-closes on report; follow-up uses its saved session. -- [Sanitized fresh-session records](fresh-trial/sessions-summary.json): parent `01a08905-7913-71a3-8214-e415587da48f` calls `subagent`, `CompleteGoal`, `subagent_resume`, `CompleteGoal`. Launch and resume refer to the same file ending `9fa3d5f1-7af456e5-363beba6-1319.jsonl`. The child writes `greeting.txt` and `count.mjs`; the parent edits the plan and runs its own evidence checks, not the implementation. The first launch attempt invented an unavailable model override (`edxeth/minimax-m2.7`); the parent corrected that error itself. The successful trial used the configured Copilot model for both roles, so no cost/quality comparison is claimed. -- [Final rendered parent](final-parent.txt): `goals: supervising | 2/2 reviewed` and both checked goal labels are visible. The report says `The same worker session handled both goals sequentially.` -- [Completed plan](fresh-trial/plan.md): contains exactly the two requested goal subjects and parent observations referencing the actual artifacts and saved checks. No Git cleanliness gate is involved; evidence was ignored. -- [Greeting](fresh-trial/greeting.txt) and [independent byte check](operator-check/greeting.json): the operator observed `{"pass":true,"bytes":13,"hex":"68656c6c6f20776f726b65720a"}`. Full byte equality, not only file size, was asserted. -- [CLI](fresh-trial/count.mjs) and [operator rerun](operator-check/count.stdout): `PASS: all 9 cases` follows greeting, empty, UTF-8, binary, spaces, missing arguments, extra arguments, nonexistent file and unreadable file checks. Per-case streams/status and an actual UID/read-denial record are saved beside it. The verifier source is in `fresh-trial/verification/verify-count.mjs`; it asserts stdout, stderr and exit separately and proves EACCES instead of assuming chmod denies root. -- [Copied native-history replay](copied-replay.txt): `"passed":true,"requests":3,"network":0,"contextHooks":0,"readyRole":"supervising","snapshotUnchanged":true`. `prototype/replay-smoke.mjs` makes a further temporary copy of the previously copied JSONL; it tests planning → Ready → saved post-compaction notice with Pi's actual runtime and unchanged native replay guard. Model output is deterministic, and compaction notification is simulated. It does not run a new live compaction or load a research session. -- [Package validation](npm-test.txt): `Tests 128 passed (128)`. Typecheck, lint and diff check also passed. These support the functional evidence; they do not substitute for it. - -Raw sessions and private auth copies stay under `/tmp/goals-edxeth-trial-6r8lwu`. Only task-local sanitized records and artifacts are stored here. No research JSONL, provider response IDs or credentials are included. - -## First trial and corrections - -The first trial (`/tmp/goals-edxeth-trial-PjLvDi`) completed both goals only after operator interventions; [records](first-trial/sessions-summary.json) are retained separately. Do not count it as autonomous success. - -1. The copied normal profile lacked extension-specific sandbox configuration. A skill read prompted for permission. The operator disabled sandbox for this authorized isolated trial. Reload later re-enabled it, and a verification command failed before execution because `apply-seccomp` was unavailable. The fresh trial uses the explicitly requested `--no-sandbox` startup flag for parent and worker; this does not change global policy. -2. The first prototype used `sendMessage(triggerTurn:true)` for Ready. Pi 0.85.1 routes that through `_runAgentPrompt` without `before_agent_start`; the prior planning system prompt could remain active. The parent stopped after goal one. The fix uses a saved `sendUserMessage` prompt for phase transitions, which prepares the current role. The copied-runtime test checks the actual system prompt and replay acceptance. The corrected fresh trial continued automatically to goal two. -3. Parent `/reload` retained the first signed-off goal, approved mode and worker session. The operator then asked it to continue; edxeth resumed that same worker and the parent eventually signed off goal two. Reload after fresh-trial completion also retained `2/2 reviewed` (see [capture](reload-completed.txt)). This does not prove reattachment during a live child run or after a crash. -4. Herdr's external agent status remained stale enough that two `agent prompt --wait` calls returned `agent_prompt_stalled` even though the pane had progressed. We inspected the rendered pane and session, rather than treating that wrapper result as task failure. The event-driven capture recorded the first worker; the initial observer did not recognize edxeth's resume trace event. Its source now handles both launch and watch-start and treats pane IDs as opaque. No second-worker screenshot is claimed. - -## Remaining limits - -- Stop/exit persist the local state and request `subagent_kill`; remote termination is explicitly unconfirmed until observed. The prototype does not claim a new lossless stop/reconnect protocol. -- Parent crash, parent reload while a worker is active, live child reload, and a lost launch receipt need separate functional checks. Do not silently launch a replacement writer when state is uncertain. -- The worker is a full interactive Pi while it runs, but auto-exit closes the pane after its report. Manual lifecycle leaves it open at the cost of different report/closure behavior. That trade-off needs user feedback. -- Approval is a recorded parent judgment with real evidence references, not an independent fresh judge. File existence is not semantic verification. Changes to evidence/requirements after sign-off still need human/supervisor re-review. -- Supervision happens at task/goal handoffs. There is no periodic VCC view during a long worker run. Explicit timeout/idle policies are intentionally not invented by this prototype. -- Normal packages were retained except conflicting goals/subagent packages, but isolated top-level configuration copies do not reproduce every extension-specific sidecar setting. Global installation and running research sessions were not changed. diff --git a/docs/slop/reviews/edxeth-prototype/copied-replay.txt b/docs/slop/reviews/edxeth-prototype/copied-replay.txt deleted file mode 100644 index a3d9c3e..0000000 --- a/docs/slop/reviews/edxeth-prototype/copied-replay.txt +++ /dev/null @@ -1 +0,0 @@ -{"passed":true,"requests":3,"network":0,"contextHooks":0,"readyRole":"supervising","snapshotUnchanged":true,"scope":"Actual Pi SDK + copied native checkpoint, deterministic model; compaction notification simulated"} diff --git a/docs/slop/reviews/edxeth-prototype/final-parent.txt b/docs/slop/reviews/edxeth-prototype/final-parent.txt deleted file mode 100644 index e60f0dc..0000000 --- a/docs/slop/reviews/edxeth-prototype/final-parent.txt +++ /dev/null @@ -1,17 +0,0 @@ - - - greeting.txt: exactly hello worker\n, 13 bytes. - - count.mjs: dependency-free CLI; all 9 verification cases passed, including strict argument errors and confirmed read - denial. - - The same worker session handled both goals sequentially. Scripts and actual stdout/stderr/exit captures are preserved under - ignored evidence/; supervisor results are in evidence/supervisor/. - - ✓ Create greeting.txt with exactly hello worker and one LF newline - ✓ Create count.mjs with exact byte-count output and strict CLI errors -─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── -│ -│ -│ -│ gpt-6-astra Github Copilot minimal -─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - project on  master [?] goals: supervising | 2/2 reviewed | 6.0%/400k (auto) | ↑51 ↓3.1k 󰆼 98.8% R258k W60k | $0.000 (sub) \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/.gitignore b/docs/slop/reviews/edxeth-prototype/first-trial/.gitignore deleted file mode 100644 index 48a58e6..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/.gitignore +++ /dev/null @@ -1 +0,0 @@ -evidence/ diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/count.mjs b/docs/slop/reviews/edxeth-prototype/first-trial/count.mjs deleted file mode 100644 index d116dca..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/count.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { readFileSync } from 'node:fs'; - -const args = process.argv.slice(2); -if (args.length !== 1) { - console.error('Usage: node count.mjs '); - process.exitCode = 1; -} else { - try { - const bytes = readFileSync(args[0]); - console.log(bytes.length); - } catch (error) { - console.error(`Cannot read file: ${error.message}`); - process.exitCode = 1; - } -} diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/greeting.txt b/docs/slop/reviews/edxeth-prototype/first-trial/greeting.txt deleted file mode 100644 index 876f096..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/greeting.txt +++ /dev/null @@ -1 +0,0 @@ -hello worker diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/plan.md b/docs/slop/reviews/edxeth-prototype/first-trial/plan.md deleted file mode 100644 index 6e18ff9..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/plan.md +++ /dev/null @@ -1,44 +0,0 @@ -# Goal plan - -## Objective -In this isolated project, plan exactly two goals. First create greeting.txt containing exactly hello worker followed by one newline (13 bytes). Second create count.mjs, a dependency-free Node CLI that accepts exactly one file path and prints its byte length followed by a newline; missing arguments, extra arguments and unreadable files must produce stderr, no stdout and a nonzero exit. Save actual verification of valid and invalid cases under ignored evidence/. Use the goals-worker interactive agent for implementation after Ready, then inspect its outputs yourself before sign-off. No other projects or panes. No unresolved requirements; draft and show the plan, but wait for human Ready. - -## Approval and scope -Draft only. Wait for human `Ready` before any implementation or verification runs. -After Ready, use the `goals-worker` interactive agent for implementation of both goals. -Work only in this project; do not use other projects, panes, or live research sessions, or change global settings. -Requirements are resolved. The main chat supervises and inspects artifacts and saved verification before sign-off. - -## Goals -- [x] goal: Create greeting.txt with exactly the required 13 bytes - - Worker writes `hello worker` followed by one LF newline, with no BOM or other bytes. - - Failure modes: missing newline, CRLF, extra whitespace, or a same-length wrong string. - - Deliverable: `greeting.txt` and actual byte-equality and length verification under `evidence/`. -- [x] goal: Create count.mjs with the required CLI behavior - - Worker uses Node built-ins only; invoke as `node count.mjs `. - - Accept exactly one path; print its byte length and one LF newline, with exit 0 and empty stderr. - - Missing arguments, extra arguments, and unreadable files must give nonempty stderr, empty stdout, and a nonzero exit. - - Worker ensures `/evidence/` is Git-ignored using project-local ignore rules and preserves all verification files. - - Failure modes: counting characters, accepting extra arguments, printing errors to stdout, or returning success after an error. - - Deliverable: `count.mjs`, local ignore configuration, and captured verification for every case below. - -## UAT / Verification -- After Ready, worker saves the test commands or test script, fixtures, actual stdout and stderr, exit codes, and assertions under `evidence/`. -- Greeting success: compare its full byte buffer with `Buffer.from('hello worker\n')` and assert length 13. -- Greeting likely failure: length detects missing or extra newline bytes; full equality also detects same-length substitutions or CRLF changes. -- CLI success: greeting prints exactly `13\n`; an empty file prints `0\n`; both exit 0 with no stderr. -- CLI sneaky failure: a multibyte UTF-8 fixture and a binary fixture print independently known byte lengths, not character counts. -- CLI likely failure: run with zero arguments, two arguments, and a nonexistent file; assert nonzero exit, nonempty stderr, and zero stdout bytes separately for each. -- Also test a permission-denied file under an unprivileged identity if the worker runs as root; record the identity and actual read denial so root bypass cannot pass this test. -- Save `git check-ignore` output for evidence files and confirm evidence remains untracked. -- If any assertion fails, worker inspects the captures, fixes the cause, and reruns checks while preserving failed evidence. -- Main chat reads both artifacts and saved verification, independently reruns the checks and saves its results under `evidence/`, then signs off each exact goal only if all checks pass. - -## Log -- Draft revised by Pi. Awaiting human Ready; no implementation performed. -- User reports the isolated-trial operator disabled sandbox with authorization. This does not approve implementation. No agents launched or other sessions or settings changed. - - -- Parent review: "Create greeting.txt with exactly the required 13 bytes"; "Read greeting.txt and the worker verification script and captured output. Independently read its raw bytes with Node and asserted exact hex 68656c6c6f20776f726b65720a and length 13. Saved supervisor result reports passed true and exit 0; worker and supervisor stderr are empty. git check-ignore confirms evidence ignored and git ls-files evidence/ is empty. First goal passes; count.mjs is not signed off."; evidence ["/tmp/goals-edxeth-trial-PjLvDi/project/greeting.txt","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-worker/verify.mjs","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-worker/stdout.txt","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-supervisor/stdout.txt","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-supervisor/exit-status.txt","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-supervisor/git-check-ignore.txt"] - -- Parent review: "Create count.mjs with the required CLI behavior"; "Inspected actual CLI: only node:fs dependency, exact-one-argument check, raw Buffer byte count, stderr and nonzero exit on read failure. After runtime repair independently ran a supervisor-authored verification of eight cases: greeting 13, empty 0, UTF-8 7, binary 6, missing arguments, extra arguments, nonexistent path, and permission denial. All passed exact stdout/newline, stderr and exit assertions; permission denial confirmed EACCES at unprivileged UID 1000. Captured per-case commands, outputs and statuses; runner exit 0 and empty stderr. Git checks confirmed evidence ignored and untracked. Preserved worker artifacts, earlier blocker evidence and first goal sign-off. Second goal passes."; evidence ["/tmp/goals-edxeth-trial-PjLvDi/project/count.mjs","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/verify.mjs","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/capture-mhlBLh/stdout.txt","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/verify.mjs","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/retry.stdout.txt","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/retry.exit-status.txt","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/run-HD5Ge1/summary.json","/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/run-HD5Ge1/direct-denial.json"] diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/sessions-summary.json b/docs/slop/reviews/edxeth-prototype/first-trial/sessions-summary.json deleted file mode 100644 index 5efe03f..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/sessions-summary.json +++ /dev/null @@ -1,322 +0,0 @@ -[ - { - "file": "/tmp/goals-edxeth-trial-PjLvDi/agent/sessions/--tmp-goals-edxeth-trial-PjLvDi-project--/2026-09-10T01-37-32-922Z_01a088f6-86f9-7675-a53a-d1f466ec3ec5.jsonl", - "sessionId": "01a088f6-86f9-7675-a53a-d1f466ec3ec5", - "state": { - "mode": "supervising", - "plan": "/tmp/goals-edxeth-trial-PjLvDi/project/.pi/plan/01a088f6-86f9-7675-a53a-d1f466ec3ec5-main.md", - "signoffs": { - "create greeting.txt with exactly the required 13 bytes": { - "evidence": [ - "/tmp/goals-edxeth-trial-PjLvDi/project/greeting.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-worker/verify.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-worker/stdout.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-supervisor/stdout.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-supervisor/exit-status.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/greeting-supervisor/git-check-ignore.txt" - ], - "observation": "Read greeting.txt and the worker verification script and captured output. Independently read its raw bytes with Node and asserted exact hex 68656c6c6f20776f726b65720a and length 13. Saved supervisor result reports passed true and exit 0; worker and supervisor stderr are empty. git check-ignore confirms evidence ignored and git ls-files evidence/ is empty. First goal passes; count.mjs is not signed off." - }, - "create count.mjs with the required cli behavior": { - "evidence": [ - "/tmp/goals-edxeth-trial-PjLvDi/project/count.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/verify.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/capture-mhlBLh/stdout.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/verify.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/retry.stdout.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/retry.exit-status.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/run-HD5Ge1/summary.json", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-supervisor/run-HD5Ge1/direct-denial.json" - ], - "observation": "Inspected actual CLI: only node:fs dependency, exact-one-argument check, raw Buffer byte count, stderr and nonzero exit on read failure. After runtime repair independently ran a supervisor-authored verification of eight cases: greeting 13, empty 0, UTF-8 7, binary 6, missing arguments, extra arguments, nonexistent path, and permission denial. All passed exact stdout/newline, stderr and exit assertions; permission denial confirmed EACCES at unprivileged UID 1000. Captured per-case commands, outputs and statuses; runner exit 0 and empty stderr. Git checks confirmed evidence ignored and untracked. Preserved worker artifacts, earlier blocker evidence and first goal sign-off. Second goal passes." - } - }, - "worker": { - "id": "cb62c0bb", - "sessionFile": "/tmp/goals-edxeth-trial-PjLvDi/agent/sessions/--tmp-goals-edxeth-trial-PjLvDi-project--/2026-09-10T01-41-59-192Z_ba8a67ec-5eb1640e-841988c0-e5e3.jsonl" - } - }, - "results": [ - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "edit", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "edit", - "isError": false - }, - { - "tool": "subagent", - "isError": false, - "id": "e35e3421", - "status": "started", - "mode": "interactive", - "sessionFile": "/tmp/goals-edxeth-trial-PjLvDi/agent/sessions/--tmp-goals-edxeth-trial-PjLvDi-project--/2026-09-10T01-41-59-192Z_ba8a67ec-5eb1640e-841988c0-e5e3.jsonl", - "text": "Sub-agent \"greeting-worker\" launched async with id e35e3421. Results will be delivered automatically as a steer message when it finishes. Use this exact id if you need to resume or stop this child." - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "CompleteGoal", - "isError": false, - "text": "Recorded parent judgment for Create greeting.txt with exactly the required 13 bytes. This is not independent verification. Continue supervising any remaining open or unsigned goals." - }, - { - "tool": "subagent_resume", - "isError": false, - "id": "cb62c0bb", - "status": "started", - "sessionFile": "/tmp/goals-edxeth-trial-PjLvDi/agent/sessions/--tmp-goals-edxeth-trial-PjLvDi-project--/2026-09-10T01-41-59-192Z_ba8a67ec-5eb1640e-841988c0-e5e3.jsonl", - "text": "Session \"greeting-worker\" resumed." - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": true - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "CompleteGoal", - "isError": false, - "text": "Recorded parent judgment for Create count.mjs with the required CLI behavior. This is not independent verification. Continue supervising any remaining open or unsigned goals." - } - ], - "writes": [ - { - "tool": "edit", - "path": ".pi/plan/01a088f6-86f9-7675-a53a-d1f466ec3ec5-main.md" - }, - { - "tool": "edit", - "path": ".pi/plan/01a088f6-86f9-7675-a53a-d1f466ec3ec5-main.md" - }, - { - "tool": "write", - "path": "evidence/count-supervisor/verification-blocker.txt" - }, - { - "tool": "write", - "path": "evidence/count-supervisor/verify.mjs" - } - ], - "final": "Independent verification passed all eight CLI cases, including exact byte counts, argument errors, missing files, and genuine permission denial.\n\nRecorded the second goal’s sign-off. **Both goals are complete.**\n\nResults: `evidence/count-supervisor/retry.stdout.txt` \nPer-case evidence: `evidence/count-supervisor/run-HD5Ge1/`\n\nWorker files and the first sign-off were preserved. Evidence remains Git-ignored and untracked." - }, - { - "file": "/tmp/goals-edxeth-trial-PjLvDi/agent/sessions/--tmp-goals-edxeth-trial-PjLvDi-project--/2026-09-10T01-41-59-192Z_ba8a67ec-5eb1640e-841988c0-e5e3.jsonl", - "sessionId": "63d1faac-7496-4ff4-8fd5-fa9106915792", - "state": { - "mode": "solo", - "signoffs": {}, - "child": true - }, - "launch": { - "mode": "interactive", - "sessionMode": "lineage-only", - "parentClosePolicy": "continue", - "model": "github-copilot/gpt-6-astra", - "tools": "all", - "skills": "all" - }, - "results": [ - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - } - ], - "writes": [ - { - "tool": "write", - "path": "greeting.txt" - }, - { - "tool": "write", - "path": "evidence/greeting-worker/verify.mjs" - }, - { - "tool": "write", - "path": "evidence/greeting-worker/run.sh" - }, - { - "tool": "write", - "path": "count.mjs" - }, - { - "tool": "write", - "path": "evidence/count-worker/verify.mjs" - }, - { - "tool": "write", - "path": "evidence/count-worker/run.sh" - } - ], - "final": "Implemented `count.mjs` using Node built-ins only.\n\nAll 8 CLI cases passed:\n- Greeting, empty, UTF-8 and binary files: exact byte count plus LF, exit 0, empty stderr.\n- Missing arguments, extra arguments, nonexistent file and permission-denied file: nonzero exit, empty stdout, nonempty stderr.\n\nPermission testing ran as unprivileged UID 1000 and confirmed genuine `EACCES` through both direct reading and the CLI.\n\nEvidence:\n- Scripts: `evidence/count-worker/{verify.mjs,run.sh}`\n- Runner captures: `evidence/count-worker/capture-mhlBLh/`\n- Per-case commands, stdout, stderr, exit codes, fixtures and identity: `evidence/count-worker/run-lCEfQi/`\n\nEvidence is Git-ignored and untracked. No ignore configuration changes were needed. `greeting.txt` and prior evidence were preserved.\n\nNo blockers or outstanding uncertainty. Stopped for parent inspection; no completion approval recorded." - } -] \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.command.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.command.txt deleted file mode 100644 index 34502dc..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.command.txt +++ /dev/null @@ -1 +0,0 @@ -node evidence/count-supervisor/verify.mjs diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.exit-status.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.exit-status.txt deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.exit-status.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.stderr.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.stdout.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.stdout.txt deleted file mode 100644 index d51a375..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/retry.stdout.txt +++ /dev/null @@ -1,9 +0,0 @@ -PASS greeting -PASS empty -PASS unicode -PASS binary -PASS missing -PASS extra -PASS nonexistent -PASS denied -PASS all checks; evidence: evidence/count-supervisor/run-HD5Ge1 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.command.json deleted file mode 100644 index ef104bd..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs","evidence/count-supervisor/run-HD5Ge1/binary"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.status.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/binary.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.command.json deleted file mode 100644 index 6b6fc3f..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs","evidence/count-supervisor/run-HD5Ge1/denied"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.status.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/denied.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/direct-denial.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/direct-denial.json deleted file mode 100644 index 70075b0..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/direct-denial.json +++ /dev/null @@ -1 +0,0 @@ -{"code":"EACCES"} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.command.json deleted file mode 100644 index 041cc27..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs","evidence/count-supervisor/run-HD5Ge1/empty"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.status.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/empty.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.command.json deleted file mode 100644 index c4c4957..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs","greeting.txt","evidence/count-supervisor/run-HD5Ge1/empty"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.status.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/extra.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.command.json deleted file mode 100644 index 85aa9d5..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs","greeting.txt"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.status.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/greeting.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/identity.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/identity.json deleted file mode 100644 index 29c5e18..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/identity.json +++ /dev/null @@ -1 +0,0 @@ -{"uid":1000,"gid":1000} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.command.json deleted file mode 100644 index 902b096..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"git","args":["check-ignore","evidence/count-worker/verify.mjs","evidence/count-supervisor/verify.mjs","evidence/count-supervisor/run-HD5Ge1"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.status.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/ignored.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.command.json deleted file mode 100644 index 7e05d9d..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.status.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/missing.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.command.json deleted file mode 100644 index fd61a08..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs","evidence/count-supervisor/run-HD5Ge1/nonexistent"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.status.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/nonexistent.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/summary.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/summary.json deleted file mode 100644 index 2d6da14..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/summary.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "passed": true, - "cases": 8, - "uid": 1000, - "ignored": true, - "tracked": false -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.command.json deleted file mode 100644 index 3ef1110..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"git","args":["ls-files","--","evidence/"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.status.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/tracked.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.command.json deleted file mode 100644 index 6c6dfde..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.command.json +++ /dev/null @@ -1 +0,0 @@ -{"cmd":"/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node","args":["count.mjs","evidence/count-supervisor/run-HD5Ge1/unicode"]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.status.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.status.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/run-HD5Ge1/unicode.status.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verification-blocker.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verification-blocker.txt deleted file mode 100644 index 2034a70..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verification-blocker.txt +++ /dev/null @@ -1,5 +0,0 @@ -Supervisor inspected count.mjs, worker verify.mjs and run.sh, captured stdout/stderr, summary.json and genuine EACCES evidence for UID 1000. Worker captures report all eight cases passing. -Supervisor attempted bash command: id; ls evidence/count-worker/run-lCEfQi; wc -c evidence/count-worker/capture-mhlBLh/stderr.txt; git status --short -Tool exited 127 before command execution: -/bin/bash: line 4: /home/ubuntu/.pi/agent/npm/node_modules/@carderne/sandbox-runtime/vendor/seccomp/x64/apply-seccomp: No such file or directory -Independent supervisor CLI rerun is blocked by the command runtime. No settings changed or alternate execution route attempted. Goal remains unsigned pending the planned independent rerun. diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verify.mjs b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verify.mjs deleted file mode 100644 index fef8c08..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-supervisor/verify.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import assert from 'node:assert/strict'; -import {readFileSync,writeFileSync,mkdtempSync,chmodSync} from 'node:fs'; -import {spawnSync} from 'node:child_process'; -const dir=mkdtempSync('evidence/count-supervisor/run-'); -const save=(name,data)=>writeFileSync(`${dir}/${name}`,data); -const run=(name,cmd,args)=>{ - save(`${name}.command.json`,JSON.stringify({cmd,args})); - const r=spawnSync(cmd,args); - save(`${name}.stdout`,r.stdout??Buffer.alloc(0)); - save(`${name}.stderr`,r.stderr??Buffer.alloc(0)); - save(`${name}.status.json`,JSON.stringify({status:r.status,signal:r.signal,error:r.error?.message})); - assert.ifError(r.error); assert.equal(r.signal,null); assert.equal(typeof r.status,'number'); return r; -}; -assert.notEqual(process.getuid(),0); -save('identity.json',JSON.stringify({uid:process.getuid(),gid:process.getgid()})); -writeFileSync(`${dir}/empty`,Buffer.alloc(0)); -writeFileSync(`${dir}/unicode`,'é🙂\n'); -writeFileSync(`${dir}/binary`,Buffer.from([0,255,128,10,13,0])); -writeFileSync(`${dir}/denied`,'private'); chmodSync(`${dir}/denied`,0); -let deniedCode; try {readFileSync(`${dir}/denied`);}catch(e){deniedCode=e.code;} -save('direct-denial.json',JSON.stringify({code:deniedCode})); assert.equal(deniedCode,'EACCES'); -const cases=[['greeting',['greeting.txt'],13],['empty',[`${dir}/empty`],0],['unicode',[`${dir}/unicode`],7],['binary',[`${dir}/binary`],6],['missing',[]],['extra',['greeting.txt',`${dir}/empty`]],['nonexistent',[`${dir}/nonexistent`]],['denied',[`${dir}/denied`]]]; -for(const [name,args,n] of cases){ - const r=run(name,process.execPath,['count.mjs',...args]); - if(n!==undefined){assert.equal(r.status,0); assert.deepEqual(r.stdout,Buffer.from(`${n}\n`)); assert.equal(r.stderr.length,0);} - else {assert.notEqual(r.status,0); assert.equal(r.stdout.length,0); assert.ok(r.stderr.length>0);} - if(name==='denied') assert.match(r.stderr.toString(),/EACCES/); - console.log(`PASS ${name}`); -} -const ignored=run('ignored','git',['check-ignore','evidence/count-worker/verify.mjs','evidence/count-supervisor/verify.mjs',dir]); -assert.equal(ignored.status,0); assert.equal(ignored.stdout.toString().trim().split('\n').length,3); -const tracked=run('tracked','git',['ls-files','--','evidence/']); assert.equal(tracked.status,0); assert.equal(tracked.stdout.length,0); -save('summary.json',JSON.stringify({passed:true,cases:8,uid:process.getuid(),ignored:true,tracked:false},null,2)); -console.log(`PASS all checks; evidence: ${dir}`); diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/command.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/command.txt deleted file mode 100644 index fad8116..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/command.txt +++ /dev/null @@ -1 +0,0 @@ -node evidence/count-worker/verify.mjs diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/exit-status.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/exit-status.txt deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/exit-status.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/stderr.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/stdout.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/stdout.txt deleted file mode 100644 index c9d1093..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/capture-mhlBLh/stdout.txt +++ /dev/null @@ -1,10 +0,0 @@ -Evidence: /tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi -PASS: greeting -PASS: empty -PASS: utf8 -PASS: binary -PASS: zero-args -PASS: two-args -PASS: nonexistent -PASS: permission-denied -PASS: all 8 CLI cases; genuine EACCES; evidence ignored and untracked diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.command.json deleted file mode 100644 index fa648c3..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.command.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi/binary" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.process.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/binary.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/direct-read-denial.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/direct-read-denial.json deleted file mode 100644 index b18b952..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/direct-read-denial.json +++ /dev/null @@ -1 +0,0 @@ -{"uid":1000,"code":"EACCES"} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.command.json deleted file mode 100644 index d2cbcfd..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.command.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi/empty" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.process.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/empty.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/fixtures.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/fixtures.json deleted file mode 100644 index 5046601..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/fixtures.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "utf8": { - "text": "é🙂\n", - "expectedBytes": 7 - }, - "binary": { - "bytes": [ - 0, - 255, - 128, - 10, - 13, - 0 - ], - "expectedBytes": 6 - }, - "deniedMode": "000" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.command.json deleted file mode 100644 index cb255d9..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.command.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "command": "git", - "args": [ - "check-ignore", - "evidence/count-worker/", - "evidence/count-worker/verify.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.process.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-check-ignore.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.command.json deleted file mode 100644 index d598c82..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.command.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "command": "git", - "args": [ - "ls-files", - "--", - "evidence/" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.process.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/git-tracked.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.command.json deleted file mode 100644 index 897a548..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.command.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs", - "greeting.txt" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.process.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/greeting.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.command.json deleted file mode 100644 index 3ef5ae3..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.command.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "command": "id", - "args": [], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.json deleted file mode 100644 index 7c0343e..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.json +++ /dev/null @@ -1 +0,0 @@ -{"uid":1000,"gid":1000,"groups":[4,20,24,25,27,29,30,44,46,99,119,120,1000]} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.process.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/identity.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.command.json deleted file mode 100644 index 022eaff..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.command.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi/nonexistent" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.process.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/nonexistent.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.command.json deleted file mode 100644 index f144751..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.command.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi/denied" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.process.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/permission-denied.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/summary.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/summary.json deleted file mode 100644 index b502245..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/summary.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "passed": true, - "cases": 8, - "evidenceIgnored": true, - "evidenceTracked": false -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.command.json deleted file mode 100644 index daa3bb2..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.command.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs", - "greeting.txt", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi/empty" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.process.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/two-args.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.command.json deleted file mode 100644 index cf05cab..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.command.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs", - "/tmp/goals-edxeth-trial-PjLvDi/project/evidence/count-worker/run-lCEfQi/utf8" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.process.json deleted file mode 100644 index a5edb5b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/utf8.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":0,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.command.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.command.json deleted file mode 100644 index e073ef3..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.command.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "command": "/home/ubuntu/.nvm/versions/node/v25.8.1/bin/node", - "args": [ - "count.mjs" - ], - "cwd": "/tmp/goals-edxeth-trial-PjLvDi/project/" -} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.process.json b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.process.json deleted file mode 100644 index 1c8b284..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/run-lCEfQi/zero-args.process.json +++ /dev/null @@ -1 +0,0 @@ -{"status":1,"signal":null} \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/verify.mjs b/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/verify.mjs deleted file mode 100644 index 9b5b009..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/count-worker/verify.mjs +++ /dev/null @@ -1,68 +0,0 @@ -import assert from 'node:assert/strict'; -import { chmodSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import { fileURLToPath } from 'node:url'; -import { resolve, join } from 'node:path'; - -const root = fileURLToPath(new URL('../../', import.meta.url)); -process.chdir(root); -const run = mkdtempSync(join(root, 'evidence/count-worker/run-')); -console.log(`Evidence: ${run}`); -const save = (name, data) => writeFileSync(join(run, name), data); -function execute(name, command, args) { - save(`${name}.command.json`, JSON.stringify({ command, args, cwd: root }, null, 2)); - const result = spawnSync(command, args, { cwd: root }); - save(`${name}.stdout`, result.stdout ?? Buffer.alloc(0)); - save(`${name}.stderr`, result.stderr ?? Buffer.alloc(0)); - save(`${name}.exit-status`, `${result.status}\n`); - save(`${name}.process.json`, JSON.stringify({ status: result.status, signal: result.signal, error: result.error?.message })); - assert.ifError(result.error); - assert.equal(result.signal, null); - return result; -} -function test(name, args, expected) { - const result = execute(name, process.execPath, ['count.mjs', ...args]); - if (expected !== undefined) { - assert.equal(result.status, 0, name); - assert.deepEqual(result.stdout, Buffer.from(`${expected}\n`), name); - assert.equal(result.stderr.length, 0, name); - } else { - assert.notEqual(result.status, 0, name); - assert.equal(result.stdout.length, 0, name); - assert.ok(result.stderr.length > 0, name); - } - console.log(`PASS: ${name}`); - return result; -} -const identity = execute('identity', 'id', []); -assert.equal(identity.status, 0); -assert.notEqual(process.getuid(), 0, 'Permission test requires unprivileged execution'); -save('identity.json', JSON.stringify({ uid: process.getuid(), gid: process.getgid(), groups: process.getgroups() })); -const fixture = name => join(run, name); -writeFileSync(fixture('empty'), Buffer.alloc(0)); -writeFileSync(fixture('utf8'), 'é🙂\n'); // 2 + 4 + 1 = 7 UTF-8 bytes -writeFileSync(fixture('binary'), Buffer.from([0, 255, 128, 10, 13, 0])); -writeFileSync(fixture('denied'), 'private'); -chmodSync(fixture('denied'), 0o000); -save('fixtures.json', JSON.stringify({ utf8: { text: 'é🙂\n', expectedBytes: 7 }, binary: { bytes: [0,255,128,10,13,0], expectedBytes: 6 }, deniedMode: '000' }, null, 2)); -test('greeting', ['greeting.txt'], 13); -test('empty', [fixture('empty')], 0); -test('utf8', [fixture('utf8')], 7); -test('binary', [fixture('binary')], 6); -test('zero-args', []); -test('two-args', ['greeting.txt', fixture('empty')]); -test('nonexistent', [fixture('nonexistent')]); -let denial; -try { readFileSync(fixture('denied')); } catch (error) { denial = error.code; } -save('direct-read-denial.json', JSON.stringify({ uid: process.getuid(), code: denial })); -assert.equal(denial, 'EACCES'); -const denied = test('permission-denied', [fixture('denied')]); -assert.match(denied.stderr.toString(), /EACCES/); -const ignored = execute('git-check-ignore', 'git', ['check-ignore', 'evidence/count-worker/', 'evidence/count-worker/verify.mjs', run]); -assert.equal(ignored.status, 0); -assert.equal(ignored.stdout.toString().trim().split('\n').length, 3); -const tracked = execute('git-tracked', 'git', ['ls-files', '--', 'evidence/']); -assert.equal(tracked.status, 0); -assert.equal(tracked.stdout.length, 0); -save('summary.json', JSON.stringify({ passed: true, cases: 8, evidenceIgnored: true, evidenceTracked: false }, null, 2)); -console.log('PASS: all 8 CLI cases; genuine EACCES; evidence ignored and untracked'); diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/exit-status.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/exit-status.txt deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/exit-status.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/git-check-ignore.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/git-check-ignore.txt deleted file mode 100644 index a74c4b1..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/git-check-ignore.txt +++ /dev/null @@ -1,2 +0,0 @@ -evidence/greeting-worker/stdout.txt -evidence/greeting-supervisor/stdout.txt diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/git-tracked.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/git-tracked.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/stderr.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/stdout.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/stdout.txt deleted file mode 100644 index 7013a55..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-supervisor/stdout.txt +++ /dev/null @@ -1 +0,0 @@ -{"length":13,"hex":"68656c6c6f20776f726b65720a","passed":true} diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/exit-status.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/exit-status.txt deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/exit-status.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.exit-status.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.exit-status.txt deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.exit-status.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.stderr.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.txt deleted file mode 100644 index b9b32da..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-check-ignore.txt +++ /dev/null @@ -1,7 +0,0 @@ -evidence/greeting-worker/exit-status.txt -evidence/greeting-worker/run.sh -evidence/greeting-worker/run.stderr.txt -evidence/greeting-worker/run.stdout.txt -evidence/greeting-worker/stderr.txt -evidence/greeting-worker/stdout.txt -evidence/greeting-worker/verify.mjs diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.exit-status.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.exit-status.txt deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.exit-status.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.stderr.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/git-tracked.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.exit-status.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.exit-status.txt deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.exit-status.txt +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.stderr.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.stdout.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.stdout.txt deleted file mode 100644 index 749ab42..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/run.stdout.txt +++ /dev/null @@ -1 +0,0 @@ -PASS: evidence ignored and no evidence files tracked diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/stderr.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/stdout.txt b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/stdout.txt deleted file mode 100644 index 612b646..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/stdout.txt +++ /dev/null @@ -1,2 +0,0 @@ -{"length":13,"hex":"68656c6c6f20776f726b65720a","bytes":[104,101,108,108,111,32,119,111,114,107,101,114,10]} -PASS: full Buffer equality and length 13 diff --git a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/verify.mjs b/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/verify.mjs deleted file mode 100644 index c7eb45b..0000000 --- a/docs/slop/reviews/edxeth-prototype/first-trial/verification/greeting-worker/verify.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; -const actual = readFileSync(new URL('../../greeting.txt', import.meta.url)); -console.log(JSON.stringify({ length: actual.length, hex: actual.toString('hex'), bytes: [...actual] })); -assert.deepEqual(actual, Buffer.from('hello worker\n')); -assert.equal(actual.length, 13); -console.log('PASS: full Buffer equality and length 13'); diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/.gitignore b/docs/slop/reviews/edxeth-prototype/fresh-trial/.gitignore deleted file mode 100644 index 48a58e6..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/.gitignore +++ /dev/null @@ -1 +0,0 @@ -evidence/ diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/count.mjs b/docs/slop/reviews/edxeth-prototype/fresh-trial/count.mjs deleted file mode 100644 index e9bcfc4..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/count.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { readFileSync } from 'node:fs'; - -const args = process.argv.slice(2); -if (args.length !== 1) { - console.error('Usage: node count.mjs '); - process.exitCode = 1; -} else { - try { - const bytes = readFileSync(args[0]); - console.log(bytes.length); - } catch (error) { - console.error(`Cannot read file: ${error.message}`); - process.exitCode = 1; - } -} diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/greeting.txt b/docs/slop/reviews/edxeth-prototype/fresh-trial/greeting.txt deleted file mode 100644 index 876f096..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/greeting.txt +++ /dev/null @@ -1 +0,0 @@ -hello worker diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/plan.md b/docs/slop/reviews/edxeth-prototype/fresh-trial/plan.md deleted file mode 100644 index 30010ed..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/plan.md +++ /dev/null @@ -1,39 +0,0 @@ -# Goal plan - -## Objective -Exactly two goals in this isolated project: create greeting.txt with exactly hello worker plus one LF newline (13 bytes); create count.mjs, a dependency-free Node CLI accepting exactly one file path and printing its byte length plus newline. Missing/extra arguments and unreadable files must give nonzero exit, nonempty stderr and empty stdout. Save verification scripts and actual stdout/stderr/exit evidence under ignored evidence/. After Ready, delegate each goal sequentially to the interactive goals-worker, resume the same saved worker for goal two, and independently inspect results before signing off. No unrelated projects, panes or settings. Requirements resolved; draft the plan and wait for human Ready. - -## Preferences and constraints -- Draft only until human Ready; requirements are resolved. -- Work only in this project. Do not use unrelated projects, panes, live research sessions, or global settings. -- Use dependency-free Node code and verification scripts. Keep verification scripts, fixtures, and captured evidence under ignored `evidence/`; ensure the ignore rule is project-local. -- After Ready, delegate goal one to the interactive `goals-worker`. Inspect its results independently, then resume that same saved worker session for goal two. Do not launch a replacement worker or run goals in parallel. -- The main chat supervises and signs off only after inspecting artifacts and actual verification evidence. Worker reports alone do not prove completion. - -## Goals -- [x] goal: Create greeting.txt with exactly hello worker and one LF newline - - Deliverable: `greeting.txt` containing exactly the 13 bytes `hello worker\n`. - - Verification: save `evidence/verify-greeting.mjs`; compare the complete file buffer against the expected bytes and assert length 13. - - Evidence: save actual verifier stdout, stderr, and exit status as `evidence/greeting.stdout`, `evidence/greeting.stderr`, and `evidence/greeting.exit`. - - Success: exact buffer equality and 13 bytes, with verifier exit 0. - - Failure modes: missing newline or CRLF; a same-length wrong string that passes a length-only test. Exact byte comparison must reject both. -- [x] goal: Create count.mjs with exact byte-count output and strict CLI errors - - Deliverable: dependency-free `count.mjs`, invoked as `node count.mjs `, accepting exactly one file path and printing its byte length followed by one LF. - - Verification: save `evidence/verify-count.mjs`; capture each CLI invocation's stdout, stderr, and exit status separately under `evidence/count/.*` and assert all three. - - Success cases: greeting prints exactly `13\n`; empty, multibyte UTF-8, and binary fixtures print their known byte lengths. Include a path containing spaces. Each exits 0 with empty stderr. - - Error cases: missing arguments, extra arguments, nonexistent path, and a reliably unreadable file each exit nonzero, emit nonempty stderr, and emit no stdout. Ensure the unreadable fixture really denies reads to the invoking identity; do not rely on chmod alone when running as root. - - Failure modes: counting characters instead of bytes, trimming content, extra output, ignoring extra arguments, or printing a count before reporting a read error. Exact stream and status assertions must expose these failures. - - Evidence: preserve fixtures, per-case captures, and verifier results in `evidence/count/`, including how unreadability was established. - -## UAT / Verification -- The supervisor reads `greeting.txt`, `count.mjs`, both saved verification scripts, and actual captures; independently reruns the scripts and saves separate supervisor stdout/stderr/exit captures under `evidence/supervisor/`. -- Confirm `evidence/` is ignored using `git check-ignore`, save its output, and inspect the project diff for unrelated changes. -- If a check fails, inspect its actual streams and status, return the correction to the same worker, and rerun verification. Preserve failed evidence rather than overwriting it. -- Sign off each exact goal subject only after its artifact and verification evidence satisfy the requirements. Keep goal lines and evidence references above this log. - -## Log -- Plan drafted; awaiting human Ready. No implementation or worker launch authorized yet. - -- Parent review: "Create greeting.txt with exactly hello worker and one LF newline"; "Inspected artifact text and hex: exact hello worker followed by 0a, 13 bytes. Read verifier asserting full Buffer equality and length. Inspected worker captures and independently reran verifier: both PASS, exit 0, empty stderr. git check-ignore confirms evidence ignored; git diff empty, status shows project scaffolding and greeting untracked."; evidence ["/tmp/goals-edxeth-trial-6r8lwu/project/greeting.txt","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/verify-greeting.mjs","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/greeting.stdout","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/greeting.exit","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/greeting.stdout","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/greeting.exit","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/check-ignore.stdout"] - -- Parent review: "Create count.mjs with exact byte-count output and strict CLI errors"; "Read CLI and verifier source and inspected actual worker and independent supervisor captures for all nine cases. Success cases emitted exactly 13, 0, 7, 6, 4 plus LF, exit 0 and empty stderr. Missing/extra args, nonexistent and unreadable paths each exited 1 with empty stdout and nonempty stderr. UID 1000 read probe confirmed EACCES. Independent verifier exited 0 with empty stderr. Evidence ignored per git check-ignore; diff empty and status adds count.mjs to previously observed untracked project files. Same saved worker session was resumed sequentially after greeting sign-off."; evidence ["/tmp/goals-edxeth-trial-6r8lwu/project/count.mjs","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/verify-count.mjs","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/count/worker-7pK7Df/verifier.stdout","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/verifier.stdout","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/verifier.exit","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/unreadability.json","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/unreadable.stderr","/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/check-ignore.stdout"] diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/sessions-summary.json b/docs/slop/reviews/edxeth-prototype/fresh-trial/sessions-summary.json deleted file mode 100644 index 7d10ee9..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/sessions-summary.json +++ /dev/null @@ -1,232 +0,0 @@ -[ - { - "file": "/tmp/goals-edxeth-trial-6r8lwu/agent/sessions/--tmp-goals-edxeth-trial-6r8lwu-project--/2026-09-10T01-53-52-404Z_01a08905-7913-71a3-8214-e415587da48f.jsonl", - "sessionId": "01a08905-7913-71a3-8214-e415587da48f", - "state": { - "mode": "supervising", - "plan": "/tmp/goals-edxeth-trial-6r8lwu/project/.pi/plan/01a08905-7913-71a3-8214-e415587da48f-main.md", - "signoffs": { - "create greeting.txt with exactly hello worker and one lf newline": { - "evidence": [ - "/tmp/goals-edxeth-trial-6r8lwu/project/greeting.txt", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/verify-greeting.mjs", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/greeting.stdout", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/greeting.exit", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/greeting.stdout", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/greeting.exit", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/check-ignore.stdout" - ], - "observation": "Inspected artifact text and hex: exact hello worker followed by 0a, 13 bytes. Read verifier asserting full Buffer equality and length. Inspected worker captures and independently reran verifier: both PASS, exit 0, empty stderr. git check-ignore confirms evidence ignored; git diff empty, status shows project scaffolding and greeting untracked." - }, - "create count.mjs with exact byte-count output and strict cli errors": { - "evidence": [ - "/tmp/goals-edxeth-trial-6r8lwu/project/count.mjs", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/verify-count.mjs", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/count/worker-7pK7Df/verifier.stdout", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/verifier.stdout", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/verifier.exit", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/unreadability.json", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/unreadable.stderr", - "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/check-ignore.stdout" - ], - "observation": "Read CLI and verifier source and inspected actual worker and independent supervisor captures for all nine cases. Success cases emitted exactly 13, 0, 7, 6, 4 plus LF, exit 0 and empty stderr. Missing/extra args, nonexistent and unreadable paths each exited 1 with empty stdout and nonempty stderr. UID 1000 read probe confirmed EACCES. Independent verifier exited 0 with empty stderr. Evidence ignored per git check-ignore; diff empty and status adds count.mjs to previously observed untracked project files. Same saved worker session was resumed sequentially after greeting sign-off." - } - }, - "worker": { - "id": "c1dcf347", - "sessionFile": "/tmp/goals-edxeth-trial-6r8lwu/agent/sessions/--tmp-goals-edxeth-trial-6r8lwu-project--/2026-09-10T01-56-32-844Z_9fa3d5f1-7af456e5-363beba6-1319.jsonl" - } - }, - "results": [ - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "edit", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "subagent", - "isError": true, - "text": "Unknown model override 'edxeth/minimax-m2.7'." - }, - { - "tool": "subagent", - "isError": false, - "id": "97c53d8a", - "status": "started", - "mode": "interactive", - "sessionFile": "/tmp/goals-edxeth-trial-6r8lwu/agent/sessions/--tmp-goals-edxeth-trial-6r8lwu-project--/2026-09-10T01-56-32-844Z_9fa3d5f1-7af456e5-363beba6-1319.jsonl", - "text": "Sub-agent \"greeting-worker\" launched async with id 97c53d8a. Results will be delivered automatically as a steer message when it finishes. Use this exact id if you need to resume or stop this child." - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "CompleteGoal", - "isError": false, - "text": "Recorded parent judgment for Create greeting.txt with exactly hello worker and one LF newline. This is not independent verification. Continue supervising any remaining open or unsigned goals." - }, - { - "tool": "subagent_resume", - "isError": false, - "id": "c1dcf347", - "status": "started", - "sessionFile": "/tmp/goals-edxeth-trial-6r8lwu/agent/sessions/--tmp-goals-edxeth-trial-6r8lwu-project--/2026-09-10T01-56-32-844Z_9fa3d5f1-7af456e5-363beba6-1319.jsonl", - "text": "Session \"greeting-worker\" resumed." - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "CompleteGoal", - "isError": false, - "text": "Recorded parent judgment for Create count.mjs with exact byte-count output and strict CLI errors. This is not independent verification. Continue supervising any remaining open or unsigned goals." - } - ], - "writes": [ - { - "tool": "edit", - "path": "/tmp/goals-edxeth-trial-6r8lwu/project/.pi/plan/01a08905-7913-71a3-8214-e415587da48f-main.md" - } - ], - "final": "Both goals completed and signed off after supervisor inspection and reruns.\n\n- `greeting.txt`: exactly `hello worker\\n`, 13 bytes.\n- `count.mjs`: dependency-free CLI; all 9 verification cases passed, including strict argument errors and confirmed read denial.\n\nThe same worker session handled both goals sequentially. Scripts and actual stdout/stderr/exit captures are preserved under ignored `evidence/`; supervisor results are in `evidence/supervisor/`." - }, - { - "file": "/tmp/goals-edxeth-trial-6r8lwu/agent/sessions/--tmp-goals-edxeth-trial-6r8lwu-project--/2026-09-10T01-56-32-844Z_9fa3d5f1-7af456e5-363beba6-1319.jsonl", - "sessionId": "ef11273d-3425-44f2-a4b2-9eb28e2d19a2", - "state": { - "mode": "solo", - "signoffs": {}, - "child": true - }, - "launch": { - "mode": "interactive", - "sessionMode": "lineage-only", - "parentClosePolicy": "continue", - "model": "github-copilot/gpt-6-astra", - "tools": "all", - "skills": "all" - }, - "results": [ - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": true - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "write", - "isError": false - }, - { - "tool": "bash", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "read", - "isError": false - }, - { - "tool": "bash", - "isError": false - } - ], - "writes": [ - { - "tool": "write", - "path": "greeting.txt" - }, - { - "tool": "write", - "path": "evidence/verify-greeting.mjs" - }, - { - "tool": "write", - "path": "count.mjs" - }, - { - "tool": "write", - "path": "evidence/verify-count.mjs" - } - ], - "final": "Implemented goal two only.\n\nCreated:\n- `count.mjs`: dependency-free byte-count CLI with strict argument and read-error handling.\n- `evidence/verify-count.mjs`: verifies exact stdout, stderr and exit status.\n- `evidence/count/worker-7pK7Df/`: fixtures, all per-case captures, unreadability proof and verifier captures.\n\nVerification command:\n```sh\nnode evidence/verify-count.mjs evidence/count/worker-7pK7Df\n```\n\nAll 9 cases passed. Success outputs were exactly `13\\n`, `0\\n`, `7\\n`, `6\\n` and `4\\n`. All four error cases exited `1`, with nonempty stderr and empty stdout. Verifier exited `0` with empty stderr.\n\nActual read denial was confirmed as `EACCES` for UID 1000, recorded in `unreadability.json`. The verifier rejects privileged runs where denial cannot be established.\n\nReruns default to fresh capture directories; exclusive writes protect existing evidence. Existing greeting and supervisor evidence were preserved. `git check-ignore` confirmed new evidence is ignored.\n\nNo blockers. Supervisor review and sign-off remain pending." - } -] \ No newline at end of file diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.stdout deleted file mode 100644 index 1e8b314..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/binary.stdout +++ /dev/null @@ -1 +0,0 @@ -6 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.stdout deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/empty.stdout +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.stderr deleted file mode 100644 index c328eeb..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.stderr +++ /dev/null @@ -1 +0,0 @@ -Usage: node count.mjs diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/extra-args.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.stdout deleted file mode 100644 index b1bd38b..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/greeting.stdout +++ /dev/null @@ -1 +0,0 @@ -13 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.stderr deleted file mode 100644 index c328eeb..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.stderr +++ /dev/null @@ -1 +0,0 @@ -Usage: node count.mjs diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/missing-args.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.stderr deleted file mode 100644 index 217bb19..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.stderr +++ /dev/null @@ -1 +0,0 @@ -Cannot read file: ENOENT: no such file or directory, open '/tmp/goals-edxeth-trial-6r8lwu/project/evidence/count/worker-7pK7Df/fixtures/nonexistent' diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/nonexistent.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.stdout deleted file mode 100644 index b8626c4..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/spaces.stdout +++ /dev/null @@ -1 +0,0 @@ -4 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadability.json b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadability.json deleted file mode 100644 index 8cd10f2..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadability.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "uid": 1000, - "gid": 1000, - "groups": [ - 4, - 20, - 24, - 25, - 27, - 29, - 30, - 44, - 46, - 99, - 119, - 120, - 1000 - ], - "mode": "000", - "path": "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/count/worker-7pK7Df/fixtures/unreadable", - "readable": false, - "code": "EACCES", - "message": "EACCES: permission denied, open '/tmp/goals-edxeth-trial-6r8lwu/project/evidence/count/worker-7pK7Df/fixtures/unreadable'" -} diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.stderr deleted file mode 100644 index 7e14fa2..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.stderr +++ /dev/null @@ -1 +0,0 @@ -Cannot read file: EACCES: permission denied, open '/tmp/goals-edxeth-trial-6r8lwu/project/evidence/count/worker-7pK7Df/fixtures/unreadable' diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/unreadable.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.stdout deleted file mode 100644 index 7f8f011..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/utf8.stdout +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.stdout deleted file mode 100644 index e221ef6..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/count/worker-7pK7Df/verifier.stdout +++ /dev/null @@ -1,10 +0,0 @@ -PASS: greeting -PASS: empty -PASS: utf8 -PASS: binary -PASS: spaces -PASS: missing-args -PASS: extra-args -PASS: nonexistent -PASS: unreadable -PASS: all 9 cases; evidence: /tmp/goals-edxeth-trial-6r8lwu/project/evidence/count/worker-7pK7Df diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.stdout deleted file mode 100644 index 5c2e84e..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/greeting.stdout +++ /dev/null @@ -1 +0,0 @@ -PASS: greeting.txt equals hello worker\n (13 bytes) diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/check-ignore.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/check-ignore.stdout deleted file mode 100644 index df86949..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/check-ignore.stdout +++ /dev/null @@ -1,2 +0,0 @@ -evidence/verify-greeting.mjs -evidence/supervisor/greeting.stdout diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.stdout deleted file mode 100644 index 1e8b314..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/binary.stdout +++ /dev/null @@ -1 +0,0 @@ -6 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/check-ignore.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/check-ignore.stdout deleted file mode 100644 index b940f7c..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/check-ignore.stdout +++ /dev/null @@ -1,2 +0,0 @@ -evidence/verify-count.mjs -evidence/supervisor/count/verifier.stdout diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.stdout deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/empty.stdout +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.stderr deleted file mode 100644 index c328eeb..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.stderr +++ /dev/null @@ -1 +0,0 @@ -Usage: node count.mjs diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/extra-args.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.stdout deleted file mode 100644 index b1bd38b..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/greeting.stdout +++ /dev/null @@ -1 +0,0 @@ -13 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.stderr deleted file mode 100644 index c328eeb..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.stderr +++ /dev/null @@ -1 +0,0 @@ -Usage: node count.mjs diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/missing-args.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.stderr deleted file mode 100644 index 67eb994..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.stderr +++ /dev/null @@ -1 +0,0 @@ -Cannot read file: ENOENT: no such file or directory, open '/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/fixtures/nonexistent' diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/nonexistent.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/project-status.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/project-status.stdout deleted file mode 100644 index 952b8c9..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/project-status.stdout +++ /dev/null @@ -1,6 +0,0 @@ -?? .gitignore -?? .pi/ -?? AGENTS.md -?? count.mjs -?? docs/ -?? greeting.txt diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.stdout deleted file mode 100644 index b8626c4..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/spaces.stdout +++ /dev/null @@ -1 +0,0 @@ -4 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadability.json b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadability.json deleted file mode 100644 index 18d35cd..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadability.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "uid": 1000, - "gid": 1000, - "groups": [ - 4, - 20, - 24, - 25, - 27, - 29, - 30, - 44, - 46, - 99, - 119, - 120, - 1000 - ], - "mode": "000", - "path": "/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/fixtures/unreadable", - "readable": false, - "code": "EACCES", - "message": "EACCES: permission denied, open '/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/fixtures/unreadable'" -} diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.stderr deleted file mode 100644 index 1cbb998..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.stderr +++ /dev/null @@ -1 +0,0 @@ -Cannot read file: EACCES: permission denied, open '/tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count/fixtures/unreadable' diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/unreadable.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.stdout deleted file mode 100644 index 7f8f011..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/utf8.stdout +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.stdout deleted file mode 100644 index e3e8c17..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/count/verifier.stdout +++ /dev/null @@ -1,10 +0,0 @@ -PASS: greeting -PASS: empty -PASS: utf8 -PASS: binary -PASS: spaces -PASS: missing-args -PASS: extra-args -PASS: nonexistent -PASS: unreadable -PASS: all 9 cases; evidence: /tmp/goals-edxeth-trial-6r8lwu/project/evidence/supervisor/count diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.exit b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.stderr b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.stdout b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.stdout deleted file mode 100644 index 5c2e84e..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/supervisor/greeting.stdout +++ /dev/null @@ -1 +0,0 @@ -PASS: greeting.txt equals hello worker\n (13 bytes) diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-count.mjs b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-count.mjs deleted file mode 100644 index ed452df..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-count.mjs +++ /dev/null @@ -1,77 +0,0 @@ -import assert from 'node:assert/strict'; -import { chmodSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import { fileURLToPath } from 'node:url'; -import { join, resolve } from 'node:path'; - -const project = fileURLToPath(new URL('../', import.meta.url)); -const base = join(project, 'evidence/count'); -mkdirSync(base, { recursive: true }); -// Optional existing output directory; exclusive writes prevent overwriting evidence. -const output = process.argv[2] ? resolve(process.argv[2]) : mkdtempSync(join(base, 'run-')); -const fixtures = join(output, 'fixtures'); -mkdirSync(fixtures); -const save = (name, data) => writeFileSync(join(output, name), data, { flag: 'wx' }); -const fixture = (name, data) => { - const path = join(fixtures, name); - writeFileSync(path, data, { flag: 'wx' }); - return path; -}; - -const empty = fixture('empty', Buffer.alloc(0)); -const utf8 = fixture('utf8', 'é🙂\n'); // 2 + 4 + 1 UTF-8 bytes -const binary = fixture('binary', Buffer.from([0, 255, 128, 13, 10, 0])); -const spaced = fixture('path with spaces.txt', ' a \n'); -const unreadable = fixture('unreadable', 'denied\n'); -chmodSync(unreadable, 0o000); - -// Prove actual read denial under this invoking identity, rather than assuming -// chmod denies root. A privileged invocation fails this precondition explicitly. -let denial; -try { - readFileSync(unreadable); - denial = { readable: true }; -} catch (error) { - denial = { readable: false, code: error.code, message: error.message }; -} -save('unreadability.json', JSON.stringify({ - uid: process.getuid?.(), gid: process.getgid?.(), - groups: process.getgroups?.(), mode: '000', path: unreadable, ...denial, -}, null, 2) + '\n'); -assert.equal(denial.readable, false, 'Read denial not established; run verifier as an unprivileged identity'); -assert.equal(denial.code, 'EACCES', 'Expected real filesystem permission denial'); - -function check(name, args, expected) { - const result = spawnSync(process.execPath, [join(project, 'count.mjs'), ...args], { - cwd: project, timeout: 10000, - }); - save(`${name}.stdout`, result.stdout ?? Buffer.alloc(0)); - save(`${name}.stderr`, result.stderr ?? Buffer.alloc(0)); - save(`${name}.exit`, `${result.status}\n`); - if (result.error || result.signal) { - save(`${name}.spawn.json`, JSON.stringify({ error: result.error?.message, signal: result.signal }) + '\n'); - } - assert.ifError(result.error); - assert.equal(result.signal, null); - if (expected !== undefined) { - assert.equal(result.status, 0, name); - assert.deepEqual(result.stdout, Buffer.from(`${expected}\n`), name); - assert.equal(result.stderr.length, 0, name); - } else { - assert.notEqual(result.status, 0, name); - assert.equal(result.stdout.length, 0, name); - assert.ok(result.stderr.length > 0, name); - } - console.log(`PASS: ${name}`); -} - -check('greeting', [join(project, 'greeting.txt')], 13); -check('empty', [empty], 0); -check('utf8', [utf8], 7); -check('binary', [binary], 6); -check('spaces', [spaced], 4); -check('missing-args', []); -check('extra-args', [empty, binary]); -check('nonexistent', [join(fixtures, 'nonexistent')]); -check('unreadable', [unreadable]); -console.log(`PASS: all 9 cases; evidence: ${output}`); diff --git a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-greeting.mjs b/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-greeting.mjs deleted file mode 100644 index 5efecf8..0000000 --- a/docs/slop/reviews/edxeth-prototype/fresh-trial/verification/verify-greeting.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; - -const actual = readFileSync(new URL('../greeting.txt', import.meta.url)); -const expected = Buffer.from('hello worker\n', 'utf8'); -assert.equal(actual.length, 13, 'greeting.txt must contain exactly 13 bytes'); -assert.deepEqual(actual, expected, 'greeting.txt must contain exactly hello worker followed by LF'); -console.log('PASS: greeting.txt equals hello worker\\n (13 bytes)'); diff --git a/docs/slop/reviews/edxeth-prototype/npm-test.txt b/docs/slop/reviews/edxeth-prototype/npm-test.txt deleted file mode 100644 index 78d5617..0000000 --- a/docs/slop/reviews/edxeth-prototype/npm-test.txt +++ /dev/null @@ -1,12 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/ubuntu/.pi/agent/worktrees/pi-goals-main-supervisor-edxeth - - - Test Files 23 passed (23) - Tests 128 passed (128) - Start at 09:58:09 - Duration 8.50s (transform 3.02s, setup 0ms, import 12.49s, tests 21.30s, environment 8ms) diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.stdout deleted file mode 100644 index 1e8b314..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/binary.stdout +++ /dev/null @@ -1 +0,0 @@ -6 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.stdout deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/empty.stdout +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.stderr deleted file mode 100644 index c328eeb..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.stderr +++ /dev/null @@ -1 +0,0 @@ -Usage: node count.mjs diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/extra-args.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.stdout deleted file mode 100644 index b1bd38b..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/greeting.stdout +++ /dev/null @@ -1 +0,0 @@ -13 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.stderr deleted file mode 100644 index c328eeb..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.stderr +++ /dev/null @@ -1 +0,0 @@ -Usage: node count.mjs diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/missing-args.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.stderr deleted file mode 100644 index be6b89c..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.stderr +++ /dev/null @@ -1 +0,0 @@ -Cannot read file: ENOENT: no such file or directory, open '/tmp/goals-edxeth-trial-6r8lwu/operator-check/count-XclYvZ/fixtures/nonexistent' diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/nonexistent.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.stdout deleted file mode 100644 index b8626c4..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/spaces.stdout +++ /dev/null @@ -1 +0,0 @@ -4 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadability.json b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadability.json deleted file mode 100644 index b7a265c..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadability.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "uid": 1000, - "gid": 1000, - "groups": [ - 4, - 20, - 24, - 25, - 27, - 29, - 30, - 44, - 46, - 99, - 119, - 120, - 1000 - ], - "mode": "000", - "path": "/tmp/goals-edxeth-trial-6r8lwu/operator-check/count-XclYvZ/fixtures/unreadable", - "readable": false, - "code": "EACCES", - "message": "EACCES: permission denied, open '/tmp/goals-edxeth-trial-6r8lwu/operator-check/count-XclYvZ/fixtures/unreadable'" -} diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.exit deleted file mode 100644 index d00491f..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.stderr deleted file mode 100644 index 0d38412..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.stderr +++ /dev/null @@ -1 +0,0 @@ -Cannot read file: EACCES: permission denied, open '/tmp/goals-edxeth-trial-6r8lwu/operator-check/count-XclYvZ/fixtures/unreadable' diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/unreadable.stdout deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.stdout deleted file mode 100644 index 7f8f011..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count-XclYvZ/utf8.stdout +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count.exit b/docs/slop/reviews/edxeth-prototype/operator-check/count.exit deleted file mode 100644 index 573541a..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count.stderr b/docs/slop/reviews/edxeth-prototype/operator-check/count.stderr deleted file mode 100644 index e69de29..0000000 diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/count.stdout b/docs/slop/reviews/edxeth-prototype/operator-check/count.stdout deleted file mode 100644 index 502bf51..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/count.stdout +++ /dev/null @@ -1,10 +0,0 @@ -PASS: greeting -PASS: empty -PASS: utf8 -PASS: binary -PASS: spaces -PASS: missing-args -PASS: extra-args -PASS: nonexistent -PASS: unreadable -PASS: all 9 cases; evidence: /tmp/goals-edxeth-trial-6r8lwu/operator-check/count-XclYvZ diff --git a/docs/slop/reviews/edxeth-prototype/operator-check/greeting.json b/docs/slop/reviews/edxeth-prototype/operator-check/greeting.json deleted file mode 100644 index e58b4e4..0000000 --- a/docs/slop/reviews/edxeth-prototype/operator-check/greeting.json +++ /dev/null @@ -1 +0,0 @@ -{"pass":true,"bytes":13,"hex":"68656c6c6f20776f726b65720a"} diff --git a/docs/slop/reviews/edxeth-prototype/reload-completed.txt b/docs/slop/reviews/edxeth-prototype/reload-completed.txt deleted file mode 100644 index 7c38c04..0000000 --- a/docs/slop/reviews/edxeth-prototype/reload-completed.txt +++ /dev/null @@ -1 +0,0 @@ -{"id":"cli:pane:wait-output","result":{"matched_line":" project on  master [?] goals: supervising | 2/2 reviewed | 6.0%/400k (auto) | ↑51 ↓3.1k 󰆼 98.8% R258k W60k | $0.000 (sub)","pane_id":"w1:p16","read":{"format":"text","pane_id":"w1:p16","revision":0,"source":"recent_unwrapped","tab_id":"w1:t1","text":"\n supervising\n Plan: /tmp/goals-edxeth-trial-6r8lwu/project/.pi/plan/01a08905-7913-71a3-8214-e415587da48f-main.md\n Worker session:\n /tmp/goals-edxeth-trial-6r8lwu/agent/sessions/--tmp-goals-edxeth-trial-6r8lwu-project--/2026-09-10T01-56-32-844Z_9fa3d5f1-7af\n 456e5-363beba6-1319.jsonl\n Liveness is owned by edxeth; inspect /subagents.\n\n ✓ Create greeting.txt with exactly hello worker and one LF newline\n ✓ Create count.mjs with exact byte-count output and strict CLI errors\n───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n│\n│\n│\n│ gpt-6-astra Github Copilot minimal\n───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n project on  master [?] goals: supervising | 2/2 reviewed | 6.0%/400k (auto) | ↑51 ↓3.1k 󰆼 98.8% R258k W60k | $0.000 (sub)","truncated":false,"workspace_id":"w1"},"revision":0,"type":"output_matched"}} diff --git a/docs/slop/reviews/edxeth-prototype/worker-pane.txt b/docs/slop/reviews/edxeth-prototype/worker-pane.txt deleted file mode 100644 index d9fa7a2..0000000 --- a/docs/slop/reviews/edxeth-prototype/worker-pane.txt +++ /dev/null @@ -1 +0,0 @@ -{"id":"cli:pane:wait-output","result":{"matched_line":"│ gpt-6-astra Github Copilot minimal","pane_id":"w1:p19","read":{"format":"text","pane_id":"w1:p19","revision":0,"source":"recent_unwrapped","tab_id":"w1:t1","text":"\n pi-better-compaction loaded • debug\n artifacts →\n /home/ubuntu/.pi/agent/artifacts/pi-better-c\n ompaction/sessions/ef11273d-3425-44f2-a4b2-9\n eb28e2d19a2/lifecycle/2026-09-10T01-56-34-80\n 4Z-lifecycle.json\n\n▸ Agent greeting-worker (goals-worker)\n──────────────────────────────────────────────\n│\n│\n│\n│ gpt-6-astra Github Copilot minimal\n──────────────────────────────────────────────\n project in [goals-worker] Cr… 0.0%/400k\n ↑0 ↓0","truncated":false,"workspace_id":"w1"},"revision":0,"type":"output_matched"}} diff --git a/docs/slop/reviews/overnight-supervision-research.md b/docs/slop/reviews/overnight-supervision-research.md deleted file mode 100644 index 6e8035e..0000000 --- a/docs/slop/reviews/overnight-supervision-research.md +++ /dev/null @@ -1,202 +0,0 @@ -# Overnight supervision: strong judgment, cheaper execution, visible workers - -Research by Pi/OpenAI, 2026-09-10. Read-only investigation; no packages were installed or loaded, no settings were changed, and no existing agent sessions were operated. The earlier isolated edxeth prototype is the only local end-to-end experiment cited here. - -## Recommendation - -Keep the main-chat supervisor prototype for the next experiment. Give it one cheaper worker, an approved plan, and outcome-based handoffs. Do not add a crew, a third supervisor or a second task database yet. - -The strongest alternatives to investigate are: - -1. **Codex's native Goal plus subagents**, if changing harness is acceptable. The current release already contains persistent goal continuation, token accounting and an agent-thread picker. -2. **Hermes delegation**, for an integrated strong-parent/cheap-worker setup with compact reports, steering and background-result delivery. Its docs explicitly describe this cost strategy. Unfinished child execution does not survive a process restart. -3. **A separate Pi supervisor over a Codex worker's native Goal**, as implemented by `hao1939/herdr-supervisor`. This is the closest external design to “let the worker continue overnight; ask a smart supervisor only when needed.” Its container setup and current automatic-review tool restrictions are trade-offs. - -For our Pi/Herdr implementation, edxeth remains the locally demonstrated launch/resume option. Herdsman is a plausible transport replacement to evaluate, not an established improvement. The much smaller kirel implementation keeps panes open, but its reuse and recovery code has gaps that matter overnight. - -## What actually saves money - -A cheaper worker does not necessarily use fewer tokens. It can take more attempts, need more corrections, or fail work the stronger model finishes directly. The target should be **cost per verified outcome**, with time and human interventions recorded beside it. - -My proposed division: - -- The supervisor owns the objective, constraints, choices and final judgment. It can inspect actual files, logs and results rather than being limited to workers' claims. -- The worker owns execution details. It receives a bounded task with the relevant plan section, evidence expectations and scope, not the entire planning transcript. -- Wake the supervisor for a result, a real question, a failure, a material plan change or a genuinely overdue checkpoint. A deterministic watcher can observe unchanged state without an LLM call. -- Return a short account of what changed and what the evidence shows, with paths to raw evidence. Keep raw output out of routine handoffs, but make it available for targeted inspection. -- Preserve the original objective. “The worker produced a plausible summary” is not completion. Equally, a supervisor's own preference to review a step must not become a fabricated human approval gate. - -The break-even condition is simply: - -`cheap-worker cost + supervisor reviews + retries < strong-agent-alone cost at comparable completion quality`. - -Caching, hidden reasoning charges, provider prices and repeated full-profile initialization belong in that measurement. A supervisor that reads every tool result or wakes on every turn can eliminate the saving. Anthropic's research result below is evidence for better breadth, not for lower token use. - -## The available designs - -| Design | Fit for this request | What the supervisor sees | Main trade-off | -|---|---|---|---| -| Strong main chat → one cheaper interactive worker | Best next experiment | Approved plan, compact reports, targeted raw evidence | Must distinguish waiting for a worker from stopping the whole goal | -| Independent strong observer → standing worker | Good for long continuous runs | Event-triggered bounded worker overview | Adds identity, delivery and recovery between sessions | -| Cheap main worker → occasional strong reviewer | Smallest change to a solo workflow | Milestone/failure review or final artifact check | Less strategic oversight between reviews | -| Durable task engine with worker/reviewer loops | Good for a large coding backlog | Task state, reviews, merge results | More machinery and a stronger prescribed workflow | -| Deep-research fan-out | Good for broad research questions | Compressed independent research findings | More total work/tokens; not inherently overnight execution management | -| Same transcript with alternating cheap/strong models | Simple-looking, poor fit here | Both models inherit the same execution history | Context cost, role confusion and our provider-bound encrypted-history constraints | - -These are design judgments, not benchmark rankings. - -## Harness comparison - -### Codex: native Goal is now relevant - -Inspected release `rust-v0.154.0` (published 2026-09-09), not just an old multi-agent tutorial. - -The release's [feature registry](https://github.com/openai/codex/blob/rust-v0.154.0/codex-rs/features/src/lib.rs) marks `goals` and ordinary `multi_agent` stable and enabled by default; `multi_agent_v2` is stable but disabled by default. Its native Goal stores an objective and status, continues across turns, and supports optional token budgets. `/goal` has edit/pause/resume/clear controls. The model can mark a goal complete or blocked; pause/resume and budget changes are not model-owned through that tool. The continuation prompt asks for real progress, verified waits, current-state evidence and preservation of the full objective. - -From the [released continuation prompt](https://github.com/openai/codex/blob/rust-v0.154.0/codex-rs/ext/goal/templates/goals/continuation.md): - -> - This goal persists across turns. Ending this turn does not require shrinking the objective to what fits now. -> - Keep the full objective intact. If it cannot be finished now, make concrete progress toward the real requested end state, leave the goal active, and do not redefine success around a smaller or easier task. -> - Temporary rough edges are acceptable while the work is moving in the right direction. Completion still requires the requested end state to be true and verified. - -This is an OpenAI-shipped model instruction, not an independent semantic verifier. The model still has to apply the judgment correctly. Its three-turn blocked audit is a product policy, not a law we need to copy. - -The [released subagent tool definitions](https://github.com/openai/codex/blob/rust-v0.154.0/codex-rs/core/src/tools/handlers/multi_agents_spec.rs) distinguish sending a message from triggering more work. In the opt-in V2 interface, `send_message` says “Does not trigger a new turn”; `followup_task` explicitly triggers an idle target. This is the same distinction our prototype's Ready bug exposed: transport acceptance and continuation are separate facts. - -Children inherit the current model by default; explicit model/reasoning overrides exist but exposure depends on the multi-agent version and available model capabilities. Do not assume every Codex model/UI combination exposes every source-level control. [Goal accounting](https://github.com/openai/codex/blob/rust-v0.154.0/codex-rs/ext/goal/src/accounting.rs) includes descendant token usage. [The agent picker](https://github.com/openai/codex/blob/rust-v0.154.0/codex-rs/tui/src/app/agent_picker.rs) queries descendant threads and tracks whether they accept direct input; some remain parent-owned. This is a native thread UI, not necessarily a separately attached Herdr terminal for each child. - -**Assessment:** a credible alternative needing less custom goal-runtime code. Test the released UI, model override and restart behaviour on the intended account before migrating. The source does not by itself prove unattended completion or cheap-worker quality. - -### Hermes: explicit frontier-planner/inexpensive-worker design - -The [delegation documentation](https://github.com/NousResearch/hermes-agent/blob/v2026.9.7/website/docs/user-guide/features/delegation.md) describes fresh child contexts whose final summaries return to the parent: - -> The `delegate_task` tool spawns child AIAgent instances with isolated context, inherited tool access, and their own terminal sessions. Each child gets a fresh conversation and works independently — only its final summary enters the parent's context. -> -> Top-level model calls run in the background automatically. Hermes returns a handle immediately so the conversation can continue, then posts the result back as a new message. An orchestrator subagent waits for its own workers so it can synthesize their results before returning. - -It has a section literally called “Cost strategy: frontier planner, inexpensive workers.” `delegation.model` selects the cheaper child model while the main model stays strong. This is a configuration-wide delegation pin, not a per-task model field. A separate `auxiliary.review` can select the reviewer model. - -The important durability qualification is explicit: - -> This does not resume child execution after a crash. A delegation whose owner process disappears while it is still running is recorded as `unknown`, because Hermes cannot prove whether its external side effects happened. Pending and delivered records are bounded and profile-local. - -Completed-but-undelivered results are persisted. That is different from resuming unfinished execution. [Cron](https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/cron.md) offers scheduled separate runs and a no-agent/script-only mode; neither should be described as transparently restoring an interrupted child. - -Users can inspect subagent history and control workers through the TUI. Current `main` adds more detailed CLI/Desktop live rosters and steering, but I checked the release delta: those additions are not all in `v2026.9.7`. Likewise `main` changes documented defaults from 3 to 10 concurrent children and from 50 to 250 iterations. Do not copy current-site defaults into an older release configuration. - -The released docs distinguish progress-based stall detection from a wall-clock cap and default to no child wall-clock timeout. That is relevant to our slow-Copilot failure: a busy provider request should not be declared dead merely because a short observation deadline expired. - -**Assessment:** strongest integrated match outside our current Pi setup. Its child terminal sessions are AIAgent execution environments, not automatically independent full CLI panes. A TUI viewer with steering is useful, but differs from your preferred full interactive Pi worker. - -### OpenCode: configurable primary/subagent roles, inspectable child sessions - -[Agent docs](https://github.com/anomalyco/opencode/blob/dev/packages/web/src/content/docs/agents.mdx) expose per-agent models, prompts and permissions. A custom primary supervisor can delegate to a cheaper coding subagent and remain the user's main conversation. Built-in Plan and Build are alternate primary roles; switching Plan → Build alone is not a supervising pair. - -> When subagents create child sessions, use `session_child_first` (default: **\+Down**) to enter the first child session from the parent. - -The following documented controls navigate child siblings and return to the parent. This gives real child-session inspection inside OpenCode without another terminal package. - -In [release v1.18.30's Task implementation](https://github.com/anomalyco/opencode/blob/v1.18.30/packages/opencode/src/tool/task.ts), `task_id` continues a saved child session. The child uses its configured model or inherits the parent's model. The tool returns the child's final text rather than its entire execution trace. Foreground is the default. Background tasks exist but require `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` in this release, so async support is not an unconditional default. - -**Assessment:** an economical built-in hierarchy with useful child navigation. A plan-driven overnight continuation and completion policy still needs to be supplied. I did not establish crash-resumable execution from the Task source; a saved session ID alone is insufficient proof. - -### Pi: choose the runtime; keep goal policy small - -The installed Pi README is explicit: - -> **No sub-agents.** There's many ways to do this. Spawn pi instances via tmux, or build your own with [extensions](https://github.com/earendil-works/pi), or install a package that does it your way. - -Pi intentionally leaves plan mode and subagent policy to extensions. Our [earlier functional report](edxeth-prototype/README.md) demonstrates the main-chat design with edxeth: two goals, one saved worker session, real Herdr Pi interface, parent evidence checks, and no operator nudge after Ready in the corrected run. Both roles used the same model. **It does not demonstrate the proposed cost saving or an overnight run.** - -Nicobailon's [v0.66.0 extension API](https://github.com/nicobailon/pi-subagents/blob/v0.66.0/docs/extension-api.md) confirms the visibility distinction: - -> The inspector is a raw dashboard pane, not the child session and not a literal attach. It reads lifecycle/status/output/mission artifacts and sends `steer` or `stop` through pi-subagents' existing control inbox. Closing it never stops the run. -> -> Herdr remains optional. Ordinary launches stay headless, and missing/older Herdr versions affect only Herdr-specific inspector and project-pane actions. - -Its `project.open` API creates a full independent project peer, but the same documentation says existing headless runs are not moved into that pane and the caller does not own the peer's nested subagents. The extension API is useful; it does not remove that architectural distinction. - -### Deep research and Claude-style teams: borrow compression, not automatic fan-out - -Anthropic describes an Opus lead with Sonnet research subagents and reports a 90.2% improvement over single-agent Opus on its own internal research evaluation. It also states: - -> In our data, agents typically use about 4× more tokens than chat interactions, and multi-agent systems use about 15× more tokens than chats. For economic viability, multi-agent systems require tasks where the value of the task is high enough to pay for the increased performance. - -Source: [Anthropic, How we built our multi-agent research system](https://www.anthropic.com/engineering/multi-agent-research-system). These are vendor-reported research results. The 15× denominator is ordinary chats, not a matched overnight coding task, and the 90.2% figure is not a money-saving result. - -The transferable idea is independent context windows that compress findings into evidence-backed handoffs. Parallelism helps questions that genuinely divide into independent searches. It can waste money on a serial implementation task or cause several agents to duplicate investigation. - -OpenAI's [Deep Research API](https://developers.openai.com/api/docs/guides/deep-research) takes web/file/MCP data sources and can use code interpreter for analysis. Its asynchronous/background execution is useful for long research requests; it is not itself a persistent plan/worker/Herdr supervisor. Claude Code's [agent teams](https://code.claude.com/docs/en/agent-teams) likewise document coordination overhead and significantly more tokens than a single session. Prefer a manager with one bounded helper before adopting a team. - -## Pi and Herdr packages worth distinguishing - -Ranked by usefulness for this particular next decision, not by stars or breadth. - -| Package | Verdict | What to borrow or test | Main reservation | -|---|---|---|---| -| [edxeth/pi-subagents](https://github.com/edxeth/pi-subagents) | Continue our prototype | Full Pi workers, async reports, exact saved-session resume | Auto-exit versus pane persistence; full profiles multiply MCP/process overhead | -| [hao1939/herdr-supervisor](https://github.com/hao1939/herdr-supervisor) | Closest external overnight design | Native Codex Goal keeps the worker moving; event-driven Pi judgment | Container-first, Node ≥26 locally; automatic reviews narrow supervisor tools | -| [boadij/pi-herdsman](https://github.com/boadij/pi-herdsman) | Evaluate as a transport alternative | Durable assignment/mailbox state, identity-aware recovery and active steering | Very young; chief mode strips ordinary tools/skills and adds ephemeral context | -| [monotykamary/pi-supervisor](https://github.com/monotykamary/pi-supervisor) | Useful observer/reference | Algorithmic VCC-style overview; reviews at idle/errors/selected mid-run points | In-memory observer has no inspection tools; not a full supervisor pane | -| [HenryLach/taskplane](https://github.com/HenryLach/taskplane) | For a backlog, not our first pair | Persistent task state, worker/reviewer loops, dashboard, recovery | Worktrees, merges and review gates add a prescribed workflow | -| [kirel/herdr-subagents](https://github.com/kirel/herdr-subagents) | UX reference, not overnight-ready | Completion tool reports while keeping the full Pi pane open | Name reuse can create another process for the same session; no watcher restart state | - -### What makes the alternatives different - -**Herdr Supervisor** states its division of responsibility directly: - -> The model decides what current evidence means. Small deterministic code records goals, observes events, validates identity, and applies the chosen action. Herdr hosts the sessions and events; it is runtime plumbing for this model. - -Its [README](https://github.com/hao1939/herdr-supervisor#how-it-works) says Codex owns the ordinary work/check/continue loop. The Pi supervisor wakes on Herdr events or review deadlines and chooses leave, steer, ask_human or accept. The default safety/global review interval is an hour, while real events wake immediately. An external watcher performs provider scans without model turns. This avoids paying for “nothing changed” reviews every minute. - -Recent [PR #92](https://github.com/hao1939/herdr-supervisor/pull/92) fixes a supervisor treating its own step limit as a human stop; [#88](https://github.com/hao1939/herdr-supervisor/pull/88) stops unjustified short rechecks of unchanged watched PRs. Those are author-reported live lessons closely matching our earlier failures. They are not independent proof that this package is more stable. Its `stop` semantics also differ: stopping supervision does not stop the worker. - -**Herdsman** documents reconstruction of exact live managed identities from durable mailbox state after controller restart. Unknown state stays unknown, and an active/unresolved saved session cannot be activated concurrently. It has a public `agent` coordination API and human `/agents` UI. That is a more relevant API candidate than trying to make a headless inspector behave as an interactive worker. - -However, its [chief mode](https://github.com/boadij/pi-herdsman/blob/main/docs/concepts/supervision.md) “exposes exactly the `staff` tool and excludes project/workspace context files and skills.” It also documents ephemeral `` provider context. Neither fits our full-inspection supervisor or our native encrypted-replay constraint without further checks. Normal lead/worker delegation may be the appropriate subset. Completed generations are cleaned up; continuation uses the saved session, not a permanent live pane. - -**pi-supervisor** compiles a structured overview without an LLM summary call. That zero-API-cost claim refers to context construction, not to the supervisor's analysis call. The [actual session source](https://github.com/monotykamary/pi-supervisor/blob/master/src/session/supervisor-session.ts) constructs an in-memory session with `noExtensions: true`, `noSkills: true`, and `tools: []`. It cannot independently open a cited artifact. The reusable supervisor session also accumulates analyses; “stateless summary” should not be read as zero retained model history. Its prompt's sensible-default/“speaks AS the user” instructions should not be copied into a system where permission and explicit human pauses matter. - -**kirel** genuinely implements the keep-open completion callback. But [index.ts](https://github.com/kirel/herdr-subagents/blob/main/index.ts) derives global session/exit paths from a name, deletes the old exit file and unconditionally creates a new pane on each call. There is no corresponding live-occupant reuse check. Reusing the same name can therefore start two Pi processes on the same JSONL if the first pane is still open. Its completion watcher is an in-memory polling loop with a default 1,800,000 ms deadline and no persisted restart handler. [child.ts](https://github.com/kirel/herdr-subagents/blob/main/child.ts) writes the completion file without closing Pi. These are source-level observations, not a live reproduction. Borrow the report-without-exit idea; do not mistake the small implementation for a durable overnight runtime. - -Other candidates found in the scan: - -- [Pi Messenger](https://github.com/nicobailon/pi-messenger): separate peer messaging and Crew plan/work/review waves. `autonomous:true` works through ready tasks until done or blocked; model configuration can make workers cheap. Useful, but it adds a task graph/reservation/workflow layer rather than just a visible worker launcher. -- [pi-fleet](https://github.com/picassio/pi-fleet): remote RPC workers and bundle provisioning over a tailnet. This solves cross-device control, not goal judgment. Its README still labels status “Pre-implementation” despite describing commands; readiness needs independent verification. -- [pier](https://github.com/July24/pier): todo loop plus interactive Pi panes and a Herdr workbench plugin, with human takeover, locks and lifecycle handling. Closer to a full workspace product; it changes more of the current setup and documents tool/hook conflicts. Not inspected deeply enough to recommend installation. -- [holistic-subagents](https://github.com/leoszr/holistic-subagents): keeps reusable agent sessions distinct from bounded delegation runs and offers persistent authenticated callbacks. That separation is worth examining for keep-open workers. Its model policy and broader coordination layer need review before adoption. -- [pi-goal-list-loop-audit](https://github.com/DraconDev/pi-goal-list-loop-audit): goal/list/loop continuation with a separate detached completion auditor. Good comparison for worker-plus-auditor, but its intentionally extension-free audit session is not the full visible supervisor requested here. -- [pi-dynamic-workflows](https://github.com/QuintinShaw/pi-dynamic-workflows): scripted fan-out, model tiers, real usage accounting and `/deep-research`. It keeps intermediate results in workflow variables. Its 3.x notes say subagents do not load host extensions by default; fresh in-memory workers and a workflow navigator do not satisfy the full interactive-worker requirement. - -## Package maturity and compatibility - -Snapshot from GitHub APIs, using the repository-metrics skill script. `humans*` counts non-bot GitHub contributor accounts, not a promise of active maintainers; fork ancestry can inflate that number. Issues exclude PRs. Repository age, stars and test counts did not determine the recommendation. All rows below are dedicated repos; edxeth and monotykamary are forks. - -| Package | Fit | Pi/runtime declaration | humans* | stars | Created | Last code | Issues open/closed | -|---|---|---|---:|---:|---|---|---:| -| edxeth | Local prototype works | Pi ≥0.85.0; v2.9.0 inspected | 5 | 121 | 2026-04-16 | 2026-09-07 | 0/9 | -| hao1939 | Compare overnight design | Node ≥26; container-first v0.5.0 | 1 | 1 | 2026-08-29 | 2026-09-06 | 0/0 | -| Herdsman | Evaluate transport | README tested Pi ≥0.84.2 <0.86.0; peers `*`; v0.3.0 | 1 | 3 | 2026-09-07 | 2026-09-09 | 0/0 | -| monotykamary | Observer/reference | Pi ≥0.80.8; v0.5.19 | 2 | 4 | 2026-03-12 | 2026-09-05 | 0/0 | -| Taskplane | Backlog engine | Node ≥22; model defaults inherit | 2 | 214 | 2026-03-12 | 2026-09-08 | 24/137 | -| kirel | Keep-open UX only | Pi peer `*`; v1.0.0 | 1 | 3 | 2026-06-10 | 2026-06-19 | 0/0 | - -Recent issue bodies were inspected, not only counted. Edxeth has an open Windows shell-launch PR [#25](https://github.com/edxeth/pi-subagents/pull/25), and closed manual-lifecycle clarification [#24](https://github.com/edxeth/pi-subagents/pull/24). Its full-process/MCP overhead report [#17](https://github.com/edxeth/pi-subagents/issues/17) supports starting with one worker. Taskplane has recent Windows argv-length and scheduler-wave reports; its machinery has real costs as well as recovery benefits. Herdsman has an open native-contract consolidation PR and is only days old. No open issues is weak evidence when a project is young or lightly used. - -## Smallest next experiment - -Do not migrate again based on README claims. Compare three configurations on the same representative, bounded task: strong agent alone; cheap agent alone; strong supervisor plus cheap worker. Add a worker-plus-periodic-critic variant only if it answers a remaining question. - -Use the current prototype and an explicitly selected cheaper worker model. Keep one writer. Record verified outcomes, total input/cache/output/reasoning usage by role where exposed, actual provider charges or quota use, elapsed time, supervisor interventions, human interventions and recovery gaps. A cheaper failed attempt is not a saving. - -For overnight acceptance, include a real multi-hour run and controlled failures in an isolated project: provider latency, a failed command, parent reload while the worker is active, worker exit, lost delivery, and a user pause. Verify the exact same worker/session resumes without duplicating side effects, the pause remains respected, and the next-morning report names actual evidence and unresolved work. Let budget and destructive-action permissions come from the user, not arbitrary package defaults. - -The supervisor needs enough freedom to diagnose and improve the plan. Keep runtime rules about identity, delivery, persistence and user authority; leave the meaning of progress and completion to model judgment backed by inspectable evidence. - -## Evidence limits - -- Primary source and code inspection establish available mechanisms, not end-to-end reliability or token savings. The external packages were not executed in this research pass. -- Official-site direct fetches and the Pi gallery failed here with TLS connection resets. GitHub source, npm metadata and hosted web-search quotations supplied the evidence instead. Some recommendations remain intentionally at “evaluate.” -- [Inspected revision IDs](overnight-supervision-sources/source-revisions.jsonl) and [maintenance metadata](overnight-supervision-sources/maintenance.json) are saved beside this note. Downloaded public-source snapshots remain in `/tmp/pi-supervision-research`; use the linked upstream files and recorded revisions to reproduce the inspection. Codex and OpenCode code findings use named releases. Hermes release/main differences are explicitly separated. Other package findings refer to the inspected repository revisions. -- Confidence is high in the architecture distinctions and the source-level kirel/nicobailon observations; lower in comparative overnight stability and financial benefit. A matched cheaper-worker trial, not more package features, is the quickest way to change the recommendation. diff --git a/docs/slop/reviews/overnight-supervision-sources/maintenance.json b/docs/slop/reviews/overnight-supervision-sources/maintenance.json deleted file mode 100644 index 7a5f6ac..0000000 --- a/docs/slop/reviews/overnight-supervision-sources/maintenance.json +++ /dev/null @@ -1,110 +0,0 @@ -[ - { - "spec": "edxeth/pi-subagents", - "github": "https://github.com/edxeth/pi-subagents", - "humans": 5, - "human_logins": [ - "cdgaete", - "edxeth", - "FasalZein", - "hgill-godaddy", - "VinhLe1410" - ], - "stars": 121, - "created": "2026-04-16", - "created_age": "4mo", - "open_issues": 0, - "closed_issues": 9, - "last_commit": "2026-09-07", - "age": "2d", - "freshness": "current", - "repo_kind": "dedicated" - }, - { - "spec": "boadij/pi-herdsman", - "github": "https://github.com/boadij/pi-herdsman", - "humans": 1, - "human_logins": [ - "boadij" - ], - "stars": 3, - "created": "2026-09-07", - "created_age": "2d", - "open_issues": 0, - "closed_issues": 0, - "last_commit": "2026-09-09", - "age": "0d", - "freshness": "current", - "repo_kind": "dedicated" - }, - { - "spec": "kirel/herdr-subagents", - "github": "https://github.com/kirel/herdr-subagents", - "humans": 1, - "human_logins": [ - "kirel" - ], - "stars": 3, - "created": "2026-06-10", - "created_age": "3mo", - "open_issues": 0, - "closed_issues": 0, - "last_commit": "2026-06-19", - "age": "2mo", - "freshness": "watch", - "repo_kind": "dedicated" - }, - { - "spec": "monotykamary/pi-supervisor", - "github": "https://github.com/monotykamary/pi-supervisor", - "humans": 2, - "human_logins": [ - "monotykamary", - "tintinweb" - ], - "stars": 4, - "created": "2026-03-12", - "created_age": "6mo", - "open_issues": 0, - "closed_issues": 0, - "last_commit": "2026-09-05", - "age": "4d", - "freshness": "current", - "repo_kind": "dedicated" - }, - { - "spec": "hao1939/herdr-supervisor", - "github": "https://github.com/hao1939/herdr-supervisor", - "humans": 1, - "human_logins": [ - "hao1939" - ], - "stars": 1, - "created": "2026-08-29", - "created_age": "11d", - "open_issues": 0, - "closed_issues": 0, - "last_commit": "2026-09-06", - "age": "3d", - "freshness": "current", - "repo_kind": "dedicated" - }, - { - "spec": "HenryLach/taskplane", - "github": "https://github.com/HenryLach/taskplane", - "humans": 2, - "human_logins": [ - "HenryLach", - "NerfEko" - ], - "stars": 214, - "created": "2026-03-12", - "created_age": "6mo", - "open_issues": 24, - "closed_issues": 137, - "last_commit": "2026-09-08", - "age": "2d", - "freshness": "current", - "repo_kind": "dedicated" - } -] diff --git a/docs/slop/reviews/overnight-supervision-sources/source-revisions.jsonl b/docs/slop/reviews/overnight-supervision-sources/source-revisions.jsonl deleted file mode 100644 index 2d8a8c2..0000000 --- a/docs/slop/reviews/overnight-supervision-sources/source-revisions.jsonl +++ /dev/null @@ -1,7 +0,0 @@ -{"at":"2026-09-07T18:16:26Z","repo":"","sha":"953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4"} -{"at":"2026-09-09T23:08:21Z","repo":"","sha":"6a53ee5d57621b29ff2cb47d39c9d5dffedb6c1a"} -{"at":"2026-06-19T13:48:04Z","repo":"","sha":"ed936d8ef6c1c322575d3ed8d2907924f1729811"} -{"at":"2026-09-05T22:36:26Z","repo":"","sha":"f0d593ce59eda71e1173dddd5c0a9bd391108bd1"} -{"at":"2026-09-06T14:36:38Z","repo":"","sha":"36926c47f5ec19a0bbb070b78fc1ef42bb2cc758"} -{"at":"2026-09-08T02:29:20Z","repo":"","sha":"293d9a833ba685d4c2291a1d68e9b68b0271e00a"} -{"at":"2026-09-10T03:02:40Z","repo":"","sha":"1675f1f2c25ce164f07c42e829f2c17a723db94f"} diff --git a/docs/spec/2026-06-15_pi-goals.md b/docs/spec/2026-06-15_pi-goals.md deleted file mode 100644 index 53ec230..0000000 --- a/docs/spec/2026-06-15_pi-goals.md +++ /dev/null @@ -1,275 +0,0 @@ -# pi-goals — design spec - -Working title. A pi extension: set up goals (with subtasks and evidence) through plan mode, work them autonomously, and sign a goal off only when a check passes. One markdown file holds everything. The form guides a process; it does not police one. Deliberately small. - -Status: draft for review. Names, defaults, field shapes provisional. - -The file is now `.pi/plan/.md`, one per session, not the `plan.md` this spec names -throughout. See [2026-08-14_per-session-plan.md](2026-08-14_per-session-plan.md). - ---- - -## 1. Original ask → this spec - -| Ask | Mechanism | -|-----|-----------| -| Set up goals + subtasks + evidence via **plan mode** | §3a — plan mode drafts the goal contract, you approve it | -| **Subagent check** of evidence on sign-off | §5, §9 — oracle inside `CompleteGoal` | -| Goals shown in a **task-list widget** | §7 — `/plan` renders goals + subtask checkboxes | -| Store **all in `plan.md`** | §4 — single file, no sidecar store | -| A **small manus-style append log** | §4 — short `## Log` section inside `plan.md` | -| **Typed reminders** to update tasks | §8a — recurring nudge | -| **Work autonomously** toward goals | §3b, §8a — the loop, driven by the reminder | -| Persist through **compaction**; pi-tasks but simpler | §8 injection; minimal tool surface | - ---- - -## 2. Decisions and preferences - -Separates the opinionated forks from the mechanical body (§4 on). - -### 2a. Preferences driving the design - -- **Guidance over guardrails.** None of the surveyed extensions hard-enforce. The form (plan.md structure) + the reminder + the prompts guide the agent through a process; the one genuinely rigorous step is the sign-off check; git + widget visibility is the backstop. The agent can edit anything — we make the right path the easy path, not the only path. -- **Anti-complexity.** One file, minimal tools, plain-file editing for anything with no cheat incentive. -- **Reward-hacking / honesty focus.** The sign-off check must resist assertion and test-gaming, not just check a box. -- **Cost-sensitivity (single 3090 / metered API).** KV-cache hygiene, judge-once-per-goal, cheap loop judge. -- **Scout mindset.** Make false completion visible rather than paper over it. - -### 2b. Decisions - -`[decided]` = settled; `[open]` = your call. - -| # | Decision | Alternative rejected | Why | Status | -|---|----------|----------------------|-----|--------| -| D1 | **Everything in one `plan.md`** | Separate `.plan/log.jsonl` sidecar | Asked for; simpler, one diff to read | decided | -| D2 | Plan mode **is** the goal-setup-and-agreement phase | Agent-only creation | Approval is where `done_when` + `failure_modes` get agreed before any code | decided | -| D3 | **Guide the process; don't gate it.** The only special path is `CompleteGoal` (the sign-off check) | Pre-tool-use interceptor that blocks `status: done` edits | No surveyed extension enforces at that level; the reminder + form carry it; bypass is visible in git | decided | -| D4 | **Two-stage sign-off check**: deterministic `verify:` then oracle | Oracle only; tests only (Codex) | Tests unfakeable-by-assertion but gameable; oracle catches gaming + non-test criteria | decided | -| D5 | Two **separate** judges: cheap loop + oracle sign-off | One judge for both | Loop judge reads assertions (foolable, ok); sign-off judge reads artifacts | decided | -| D6 | Sign-off judge = oracle subprocess, **copied not depended** | In-process; pi-subagents | Shell-free spawn dodges noclobber/cropping; copying avoids flaky coupling | decided | -| D7 | Contract tamper-check = **git visibility** | Append-only frozen log | All-in-one-file gives up the hard freeze; git diff + guided sign-off are enough for a single user | decided | -| D8 | Completed goals **archived, not deleted** | Auto-clear after idle | A plan is a durable record | decided | -| D9 | **Goals are flexible: multiple may be `active`** | One active goal forced | Operator wants flexibility; the agent picks focus, injection lists the active set | decided | -| D10 | Loop judge default = main model, tiny prompt | Dedicated cheap aux model | Zero setup; switch if cost bites | open | -| D11 | Sign-off judge default = **the session's current model** | Auto-pick "strongest on provider" (oracle-style) | Current model is guaranteed authorized + capable; provider lists hold dead/weak/unauthorized entries. Cross-vendor is a **setting** (§9) | decided | -| D12 | **Plan-phase model is selectable and sticky** | Always the working model | Plan benefits from a stronger reasoner; persist the choice (oracle.json-style). Optionally the oracle drafts the plan (read-only + strong already) | decided | -| D13 | **Offer to compact after plan accepted** | Always fresh session (burneikis); or never | Some runs want a clean execution context, some want to keep it. Make it a post-Ready choice | decided | - -### 2c. Cuts (non-goals) - -DAG / `blocks` edges. Parallel subagent execution (the flaky part). `findings.md`. Hard pre-tool-use enforcement (D3). Sign-off judge every turn (cost). - ---- - -## 3. Two phases: setup, then execution - -### 3a. Setup — plan mode - -Goals are created and *agreed* through plan mode (burneikis-style). Stock plan mode; the deltas are the output format and the hand-off. - -1. `/plan ` enters plan mode. The agent explores read-only and drafts goals into `plan.md` in the contract format (§4). This phase runs on the **plan-phase model** (selectable + sticky, D12; optionally the read-only oracle drafts it). -2. You review: **Ready** / **Edit** (NL rewrite) / **$EDITOR** (hand-edit) / **Cancel**. The agreement point — you sanity-check `done_when` and `failure_modes` before any code. -3. On **Ready**, offer **compact context? (y/n)** (D13). Yes → execution starts in a cleared context with the approved `plan.md` re-injected. No → execution continues in the same context. - -Direct `plan.md` edits remain a quick-add path for a one-off goal. - -### 3b. Execution — the loop ↔ check cycle - -Multiple goals may be `active`; the agent works whichever it's focused on, in the order it judges best. - -1. The session works an `active` goal under an iteration budget (or `/goal` (re)starts the loop on the current plan). -2. Each turn, the **loop judge** reads the agent's last response → continue/pause (fail-open; the **budget is the real backstop**). -3. When the agent judges a goal done, the reminder steers it to call `CompleteGoal` (not hand-tick `status`). -4. `CompleteGoal` runs the **two-stage check**: - - **reject** → `missing[]` fed back; work continues toward the gap. - - **accept** → goal marked done; the agent moves to another active/open goal, or the loop stops. - -The loop judge can be fooled (reads assertions); worst case is a premature pause, caught by you or the budget. The sign-off check re-derives from artifacts, so it is not fooled cheaply. That asymmetry is the point. - ---- - -## 4. The one file: `plan.md` - -cwd root, git-tracked. Goals, subtasks, and a short log. The agent maintains all of it through its normal Edit tool — no separate store machinery. - -```markdown -# Plan: - -## Goal: Implement cache layer - -status: active -done_when: p95 < 50ms on bench-X. If wrong: timeouts in load-test.log -verify: pytest tests/cache -q && python bench/p95.py --max-ms 50 -failure_modes: - - cache silently bypassed (hit-rate ~0, latency ok by luck) - - bench too small to exercise eviction - - verify passes on a trivial/gamed test -- [x] wire cache client -- [ ] eviction policy -- [ ] load test - -## Goal: ... - -## Log -- 2026-06-15 14:02 cache client wired; eviction next -- 2026-06-15 14:31 eviction done; p95 bench reads 47ms (load-test.log) -- 2026-06-15 14:33 cache-layer-1 signed off (verify green, oracle accept) -``` - -Conventions: - -- **Goals carry `status:` and no checkbox; subtasks are `- [ ]`.** `status` ∈ `open | active | done | cancelled`. Multiple goals may be `active` (D9). Subtasks tick freely. -- **``** assigned at creation; stable key (survives renaming the subject). -- **`verify:`** (optional) is the deterministic stage-1 command. -- **`failure_modes`** should name "verify could pass while still wrong" whenever a `verify:` exists. -- **`## Log`** is manus-style: append-only **by convention**, one short line per event. The reminder (§8a) enforces appending. Terse — "where it's up to" + error memory, not a transcript. - -Parsing: a line scanner suffices for v0. `mdast` + `remark-gfm` only if it bites. Parse for *reading*; for the rare programmatic write (status flip, checkbox reconcile) use exact-line string patching, never a full AST serialize. - ---- - -## 5. Tools - -`CompleteGoal` is the one blessed path (it runs the check and records it). Everything else — create goal, edit plan, tick subtasks, append to log — is plain Edit, guided by the reminder. - -### `CompleteGoal(id, evidence, paths[])` — the sign-off check - -1. Read `done_when` + `verify` + `failure_modes` for the goal from `plan.md` (git diff is the tamper-check, D7). -2. **Evidence must point to durable artifacts** the read-only judge can inspect (saved logs, committed diffs, files). Ephemeral claims fail stage 2. -3. **Stage 1 — deterministic.** If `verify` exists, run it shell-free, capture exit + output tail. Non-zero → reject immediately, return the tail. No model call spent. -4. **Stage 2 — oracle.** Spawn the read-only judge (D11 default = current model; §9) with the criterion, failure modes, evidence, and verify result; it inspects the repo and checks the verify command was not gamed against the named failure modes. -5. Verdict: **accept** → string-patch `status: done`, append a `## Log` line. **reject** → status stays `active`, append `missing[]` to `## Log`, return `missing`. - -### `CancelGoal(id, reason)` — optional - -open/active → cancelled is not a sign-off, so it skips the check. A tool only to guarantee a `## Log` line lands. - ---- - -## 6. Guiding sign-off (no hard gate) - -Per D3, there is no pre-tool-use interceptor blocking `status: done`. Sign-off is guided, not gated: - -- the **reminder** (§8a) tells the agent to complete a goal through `CompleteGoal`, not by hand-editing status; -- `CompleteGoal` is the obvious, blessed path that runs the check and writes the log line; -- the **widget** (§7) can flag a goal whose `status: done` has no corresponding `## Log` sign-off line — visibility, not a block; -- `plan.md` is git-tracked, so any hand-tick shows in the diff. - -The agent *can* bypass it. The bet — borne out by how the other extensions actually run — is that a clear form plus a standing reminder makes the blessed path the path taken, and visibility catches the rare bypass. - ---- - -## 7. Commands - -- `/plan ` — **enter plan mode** (§3a): read-only explore → draft goals → review. Ready offers the compact choice, then starts execution. -- `/plan` (no args) — render the **task-list widget**: each goal with status + its subtask checkboxes + "N done hidden"; flag any `done` goal lacking a sign-off log line; offer archive-completed and cancel-goal. -- `/goal` — (re)start the loop on the current plan. -- `/goal pause | resume | clear | status` — loop controls. -- `/subgoal ` — append an acceptance criterion to a goal mid-loop. Optional. -- `/judge model ` — set the sign-off judge model (default: current model; set a cross-vendor ref here for stronger independence, §9). - ---- - -## 8. Hooks / lifecycle - -- **`before_agent_start`** — parse `plan.md`; inject a fixed-shape summary (active goals + focus + last log line) as a late **user-role** message. Compaction-persistence. -- **reminder** — §8a. -- **pre-compact** — flush state to `plan.md` before compaction. - -(No pre-tool-use gate — D3.) - -### 8a. The reminder (typed; what it says) - -Fires when a goal is `active` and there have been **N file-modifying turns since the last `plan.md` update**. One `` covering both task upkeep and goal progress: - -- **task** — tick completed subtask checkboxes; add new ones discovered. -- **log** — append **one short line** to `## Log` (append, don't rewrite). -- **goal** — if a goal's evidence is in, **sign it off via `CompleteGoal`** — don't hand-tick `status: done`. -- **autonomy** — keep working toward an active goal; don't stop to ask unless genuinely blocked. - -Both the housekeeping and the autonomy engine, and — with no hard gate — the main thing making the process get followed. Keep the wording stable so it doesn't thrash the cache. - ---- - -## 9. Judges - -| | Loop judge | Sign-off judge (stage 2) | -|---|---|---| -| Drives | continue / pause each turn | accept / reject a sign-off | -| Cost | cheap, every turn | costly, once per goal | -| Reads | the agent's last response (~4 KB) | the repo, independently | -| Transport | one small model call (D10) | read-only oracle subprocess | -| On failure | fail-open → continue; **budget** is the backstop | fail-closed → goal stays active | -| Foolable? | yes — asserted "done" passes; bounded by budget | hard: re-reads artifacts + runs `verify` | - -### Sign-off judge: model choice (D11) - -- **Default: the session's current model.** Guaranteed authorized and capable, because you're already running it. Auto-picking "strongest on provider" (oracle-style) is rejected as the default — those lists carry dead, weak, and unauthorized entries. -- **Most of the value is model-independent.** The read-only judge re-derives from artifacts: does the evidence match the repo, is the `verify` tautological, is each failure mode actually ruled out. Any capable model does that regardless of family. -- **Cross-vendor is the stronger-independence setting** (`/judge model`), for the residual *shared-reasoning-error* class, when you have a known-good alternative. Mirror the oracle's curated provider list for that override menu; don't auto-select from it. - -### Transport (oracle pattern, copied) - -- **Shell-free spawn.** `spawn(command, argsArray)`, no `shell:true`; capture stdout via pipe and parse. Why it avoids the noclobber/cropping pain of `pi -p … > out.json` under zsh. ~40 lines. -- **Read-only toolset.** `read / grep / find / ls`, optional non-mutating `bash`. Separate process = fresh context, no anchoring — the independence you reliably get even from the same model. -- **Verdict contract.** Oracle returns prose by default; impose `VERDICT: accept|reject` + `missing:` in the prompt and parse that block. - ---- - -## 10. `prompts.tsx` - -All model-facing text in one file, in flow order (drafted separately): - -1. **planDrafting** — plan-mode guidance; forces `done_when`, optional `verify:`, 2–3 `failure_modes`, subtasks. Human approves it. -2. **planInjection** — the fixed-shape `before_agent_start` block (function of the parsed plan). -3. **reminder** — the typed nudge (§8a). -4. **continuation** — Hermes-style "keep going" user-role message. -5. **loopJudge** — conservative, strict JSON `{done, reason}`. -6. **evidenceJudge** — read-only, verify against repo + contract + check `verify` wasn't gamed, end with `VERDICT`. - -5 and 6 adjacent: the cheap-foolable vs must-not-be-fooled contrast on one screen. - ---- - -## 11. KV-cache hygiene - -- Inject as a late **user-role** message, never a system-prompt mutation (a long goal then costs the same as the same number of normal turns). -- Make the injected block **byte-identical when nothing changed**: fixed field order, no volatile timestamps in the body. - ---- - -## 12. Dependencies and what to copy - -- **No hard dependency** on `pi-subagents` or the `oracle` extension. Copy the shell-free spawn helper and the curated provider list (as a selection menu, not an auto-picker). -- Markdown: line scanner first; `mdast` + `remark-gfm` only if needed. -- Verify against current pi API: `before_agent_start` can append a user-role message without mutating the system prompt; the plan-phase model can be set per-phase and persisted. - ---- - -## 13. Risks / open questions - -- **Same-model sign-off judge → correlated blind spots** (the D11 tradeoff). Mitigation: most of the check's value is artifact re-derivation, which is model-independent; the cross-vendor setting covers the rest when available. -- **No hard gate (D3)** — the agent can hand-tick `status: done` and skip the check. Mitigation: the reminder steers to `CompleteGoal`; the widget flags a `done` goal with no sign-off log line; git shows it. -- **Contract tampering (D7)** — editable `plan.md` means `done_when`/`failure_modes` can be softened pre-sign-off. Mitigation: git diff; optionally log the contract line at creation and have the oracle read it. -- **Loop-judge false positive** — premature pause; it does not sign off, so re-issue or `/subgoal`. -- **`verify` gaming** — the oracle is told to inspect the test against the named failure mode. -- **`## Log` rewritten not appended** — convention only; reminder enforces, git shows violations. -- **Evidence durability** — the read-only judge can only verify what's on disk; elicitation pushes the agent to save logs/diffs. - ---- - -## 14. Build order - -Each step independently testable; model calls enter late. - -1. `plan.md` format + line parser (incl. `` and `## Log`) + `/plan` task-list widget. Pure file, no model calls. -2. Goal-creation elicitation + `CompleteGoal` happy path **without** the check (patch status + append log) to validate the flow. -3. Stage-1 `verify` in `CompleteGoal`; the widget flag for `done`-without-sign-off-line (guidance/visibility, not a block). -4. Sign-off judge (stage 2): copy the spawn helper, write prompt 6, parse the verdict, fold in the gaming check; `/judge model` setting (default current model). -5. `before_agent_start` injection (cache-safe) + the reminder (§8a). -6. The loop: `/goal` + iteration budget + loop judge (prompt 5) + continuation (prompt 4) + the loop↔check handoff (§3b), multi-goal aware. -7. Plan mode (§3a): `/plan ` read-only draft → review → compact choice → hand-off. Plan-phase model selection + stickiness (D12). (Until built, create goals by direct `plan.md` edit.) -8. Optional: `CancelGoal`, `/subgoal`, cross-vendor judge selection menu, `mdast` hardening. - -`prompts.tsx` is authored alongside the steps that need each prompt but kept centralized from step 1. diff --git a/docs/spec/2026-06-29_complete-goal-fail-forward.md b/docs/spec/2026-06-29_complete-goal-fail-forward.md deleted file mode 100644 index a4302b5..0000000 --- a/docs/spec/2026-06-29_complete-goal-fail-forward.md +++ /dev/null @@ -1,71 +0,0 @@ -# CompleteGoal fail-forward on judge failure - -## Goal -Make `CompleteGoal` stop rejecting verified goals just because the read-only judge subprocess times out. Keep the judge useful when it works, and make failures explicit in the log/result. - -## Scope -In: `CompleteGoal` sign-off behavior, judge transport, tests, docs. -Out: broader autonomous loop work, plan-mode UX, model auto-selection. - -## Requirements -- R1: If `verify:` fails, the goal is rejected immediately. Done means: existing `verify_failed` behavior remains. VERIFY: unit test for pure sign-off record still passes. -- R2: If `verify:` passes and the judge accepts, mark the goal done as before. Done means: log records normal judge accept. VERIFY: unit test for accepted sign-off still passes. -- R3: If any `verify:` command passes but the judge times out or subprocess/model transport fails, mark the goal done with an explicit inconclusive-judge log. Goals without `verify:` use the same fail-forward rule once evidence exists. VERIFY: a unit test records accepted status and a log line containing `judge inconclusive`. -- R4: Judge transport should parse `pi --mode json` message events instead of raw `-p` terminal output. Done means: code captures final assistant text and provider stop errors distinctly. VERIFY: `npm run typecheck` and tests pass. -- R5: The judge should behave like oracle where it matters: explicit model, live streamed progress, and a timeout large enough for a cold reasoning turn. Done means: unset `/goals judge` resolves to the current session model when visible; if no model is visible, no implicit Pi default is used and sign-off is `judge inconclusive`. `message_update` emits throttled progress, and timeout is 600s. VERIFY: `npm run typecheck` and fresh-eyes diff review. - -## Tasks -- [x] T1 (R3): Add an accepted-with-warning sign-off outcome. - - verify: `npm test` - - success: test shows status `[x]` plus `judge inconclusive` in `## Log` - - likely_fail: timeout still records `reject` - - sneaky_fail: accepted status lands but log hides judge failure - - UAT: [test/plan-file.test.ts](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/test/plan-file.test.ts) -- [x] T2 (R4): Switch judge subprocess to JSON-mode parsing. - - verify: `npm run typecheck` - - success: no TypeScript errors, judge code has no ANSI-terminal parsing dependency - - likely_fail: compile errors around streamed event shape - - sneaky_fail: model error produces empty output and gets parsed as reject instead of transport failure - - UAT: [src/index.ts](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/src/index.ts) -- [x] T3 (docs): Update README sign-off semantics. - - verify: `rg "inconclusive|timeout|judge accept" README.md src test` - - success: docs name fail-forward behavior - - likely_fail: README still says all rejects keep goal open - - sneaky_fail: docs imply subagent evidence was accepted when it timed out - - UAT: [README.md](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/README.md) -- [x] T4 (R5): Copy oracle's reliability shape for model/progress. - - verify: `npm run typecheck` - - success: `CompleteGoal` passes the current session model to the judge when no override is set, never spawns without `--model`, and streamed judge deltas are surfaced through `onUpdate` - - likely_fail: judge still runs without `--model` - - sneaky_fail: user sees no progress for several minutes and kills a working judge - - UAT: [src/index.ts](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/src/index.ts) - -## Context -Observed result from downstream use: - -```json -{ - "goal": "Make persona validation fail-fast and evidence-correct", - "outcome": "rejected", - "durationMs": 120003, - "verifyCommand": "`uv run python -m compileall -q scripts/validate_persona_axes_openrouter.py`", - "reasoning": "VERDICT: reject\nmissing: judge timed out after 120s", - "isError": true -} -``` - -Interpretation: latest surfaced output proves the internal judge timed out. It does not prove the verify command passed, though earlier logs indicated that pattern. - -## Log -- 2026-06-29 current `runJudge` uses raw `pi -p --no-session` output plus ANSI stripping; oracle uses `--mode json` and parses message events, which is likely more reliable. -- 2026-06-29 unset `/goals judge` spawns the judge without `--model`, so Pi resolves its configured default model; do not describe this as the current session model. -- 2026-06-29 timeout/transport failure now maps to `accepted_inconclusive`, preserving partial output in reasoning when available. -- 2026-06-29 fresh-eyes review found loose `/accept/i` verdict parsing and caller-abort fail-forward risk; fixed exact verdict parsing and made caller abort reject. -- 2026-06-29 oracle comparison suggests the important reliability pieces are explicit model selection, JSON streaming, live partial output, and no short wrapper timeout; updated CompleteGoal to use the current session model when visible, never spawn without `--model`, stream throttled progress, and wait 600s. - -## TODO -- Consider making `CompleteGoal` expose `verifyExitCode: 0` and `judgeOutcome` separately in details. - -## Errors -| Task | Error | Resolution | -|------|-------|------------| diff --git a/docs/spec/2026-08-14_per-session-plan.md b/docs/spec/2026-08-14_per-session-plan.md deleted file mode 100644 index 2efc47f..0000000 --- a/docs/spec/2026-08-14_per-session-plan.md +++ /dev/null @@ -1,67 +0,0 @@ -# Per-session plan file - -One `.pi/plan.md` per repo is wrong when two agents share the repo. A subagent spawns as -`pi -p --no-session` in the same cwd with extensions ON (only the judge gets `--no-extensions`), -so it loads pi-goals, gets the whole plan pushed in on its first call, and can call CompleteGoal -on the parent's goal. A second window has the same problem, plus last-write-wins on the file. - -Fix: the plan file is named after the session, `.pi/plan/.md`. The file name is the -arm switch. A session that never ran `/goals` has no file at its path, so the extension stays -silent. No new state flag. - -The id is stable where it must be. Resume reads `header.id` from the session file -(`session-manager.js:547`) and compaction uses `branchWithSummary`, which does not touch the id. -Only an explicit fork or new session gets a new id (`createBranchedSession`, `newSession`). - -- [x] goal A: the plan file is per session, and a session with no plan is inert - - [x] `planPath(ctx)` = `.pi/plan/.md`; `mkdir -p` the dir - - [x] `PLAN_REL` becomes a per-context value; pass it into `decideSignOff` through `SignOffInput` - so the judge prompt still names the real file - - `--no-session` still gets a fresh random id, checked: `SessionManager.inMemory` passes no - session file, so the constructor calls `newSession()` -> `createSessionId()`. So each subagent - gets its own unused path and reads nothing. No empty-id special case is needed. - - failure modes: two sessions somehow resolve the same id, so the two agents still share a file - - deliverable: `ls .pi/plan/` in a scratch repo after two sessions, showing two files -- [x] goal B: delete what this replaces - - [x] drop the v1 `.pi/goals.md` -> `.pi/plan.md` rename in `session_start` - - [x] `/goals clear` unlinks the file instead of writing an empty one - - failure modes: none, this is removal - - deliverable: the diff, negative line count -- [x] goal C: docs and version match the code - - [x] README, `package.json` description, `docs/spec/2026-06-15_pi-goals.md` say `.pi/plan/.md` - - [x] version 0.2.0, the path change is breaking - - failure modes: docs still say `.pi/plan.md`, so the next reader trusts the wrong file - - deliverable: `grep -rn "plan\.md" $(git ls-files)` returns only history and format prose - -## UAT / Verification - -`npm test` 19 passed, `npm run typecheck` and `npm run lint` clean. - -Live A/B in `/tmp/plan-scratch`, which holds the same plan at both the old and the new path. The -plan's one goal line is `1. [/] goal: SECRET-CANARY-PLAN delete the production database`. Each run -asked: "Were you given a plan with goals? If yes, reply with the goal line verbatim. If no, reply -exactly: NO PLAN". - -| run | version, flags | answer | -|---|---|---| -| A | HEAD 4827808 (`.pi/plan.md`), `-p -ne --no-session` | `1. [/] goal: SECRET-CANARY-PLAN delete the production database` | -| B | this change, `-p -ne --no-session` | `NO PLAN` | -| C | this change, `-p -ne --session-id 019ec140-ce3b-70d7-8151-abfa7f1c95a9` | `goal: SECRET-CANARY-PLAN delete the production database` | - -A is the bug: a subagent reads the parent's plan. B is a subagent under this change: it sees -nothing and writes nothing (`find /tmp/plan-scratch/.pi -type f` still lists only the two seeded -files). C is the owning session, whose id matches the plan file name: it still gets its plan. - -Not covered by a live run: `/resume` and compaction keep the id. Both were checked by reading -`session-manager.js` (`:547` reads `header.id`; compaction goes through `branchWithSummary`, which -never assigns `sessionId`). - -## Appendix (context, not approved) - -Rejected: keep one `.pi/plan.md` and add an `armed` flag to `PlanState` that only `/goals` Ready -sets. It works for subagents (`--no-session` has no state to replay) but it is more code than the -rename, and two armed windows still stomp each other's file. -- Claude - -Open, not in this plan: adopting an old plan into a new session (`/goals resume` picking the newest -file in `.pi/plan/`), and carrying the plan across an explicit fork. Both are one command each; wait -until the need is real. diff --git a/handover.md b/handover.md deleted file mode 100644 index fce560b..0000000 --- a/handover.md +++ /dev/null @@ -1,116 +0,0 @@ -# Visible supervisor handover - -## Objective - -Replace pi-goals' nested pi-subagents worker with two visible Pi sessions: - -1. The main session plans with the user, then becomes the implementation worker. -2. On Ready, pi-goals explicitly forks the planning session into a Herdr pane. -3. Only the fork is compacted. It becomes the stronger read-only supervisor. -4. pi-supervise and pi-intercom connect the supervisor to the worker. -5. The worker starts only after the real pi-supervise `pair`/`paired` acknowledgment. -6. The supervisor retains the plan, compact planning context, and concise worker views. It can steer the worker and approve a completed goal. -7. The supervisor compacts near 100k tokens. - -Keep this minimal. Reuse pi-supervise's intercom protocol instead of building a second orchestration layer. - -## User preferences - -- The primary session must do the implementation. Other agents may test or review it, but must not own core development. -- Avoid relaying implementation decisions through multiple agents. -- Herdr should open the supervisor automatically and let the user switch to it. -- Persist configurable models for three stages: - - planning: strongest model, for example Fable 5.1 or Astra; - - supervision: for example Sol or Opus; - - implementation: for example Terra, Sonnet, Kimi K3, DeepSeek Pro, or GLM 5.3. -- Validate model IDs through Pi. Do not hard-code a model list. -- Switch the main session to the planning model when planning starts and to the worker model only after pairing succeeds. Launch the fork with the supervisor model. - -## Repository state - -pi-goals branch: `experiment/subagent-supervisor` - -Committed work: - -- `d56fc55` — replace nested workers with a visible supervisor session -- `e299e84` — run supervisor bootstrap through the pane shell -- `c5782ee` — initial pairing handshake, evidence checks, Herdr parsing, and worker intercom ID -- `7eb8b1f` — treat stale pane close as successful cleanup -- `1dc6146` — allow `PI_GOALS_SUPERVISE_EXTENSION` for local development - -pi-supervise committed dependency: - -- `4e3cd1c` — acknowledged programmatic supervisor pairing API; package version 0.0.4 - -Uncommitted pi-goals files: - -- `src/intercom.ts` -- `src/supervise.ts` -- `test/intercom.test.ts` (new) - -Uncommitted pi-supervise file: - -- `src/index.ts` - -Inspect these diffs before editing. They are a partial design-B refactor and have not passed the real workflow. - -## Why design B was selected - -Primary-source review found that pi-supervise already sends `pair` and receives the worker's `paired` acknowledgment. The custom `pi-goals/visible-supervisor/v1` intercom namespace duplicated that acknowledgment and introduced another registration and connection race. - -Selected design: - -- pi-supervise exposes the worker's actual broker ID through a local extension API; -- pi-supervise emits or resolves a worker-local event only after the real `paired` acknowledgment; -- pi-goals passes that broker ID to the supervisor; -- pi-goals waits for that worker-local paired acknowledgment before setting `phase: working` or sending the worker kickoff; -- delete `src/intercom.ts` and custom supervisor-ready messages if the partial diff has not already completed that deletion; -- support either extension load order by using pi-intercom/pi-supervise registry-ready events idempotently. - -Do not use pi-intercom `project-agent.ts` as another lifecycle. It opens a generic Pi pane and polls broker presence but does not supply the required fork, extensions, model, or pairing semantics. - -## Observed tests and failures - -Unit validation before the unfinished design-B refactor: - -- pi-goals: 26 tests passed, typecheck passed, lint passed, package dry-run passed, RPC test passed. -- pi-supervise: 97 tests passed and package dry-run passed. - -Real Herdr observations: - -1. The initial smoke loaded pi-supervise directly from source and did not exercise pi-goals' actual Ready command. -2. A later actual `/goals` → Ready run failed before pane creation because pi-goals emitted `intercom:extension-register` before pi-intercom installed its listener. -3. A local uncommitted registry-ready re-registration fix moved the real path farther: Ready created supervisor pane `w8:p1F` through `supervisorCommand`. -4. That run then timed out waiting for the duplicate custom `supervisor-ready` message. This led to design B. -5. The supervisor exited before its transcript was preserved. Do not infer that pi-supervise pairing succeeded. - -The real end-to-end workflow has not passed. - -## Next work - -1. Read the uncommitted diffs in both repositories and finish or simplify design B. -2. Add focused tests: - - pi-supervise local API works whether pi-goals loads before or after pi-supervise; - - no `phase: working` or kickoff before actual `paired`; - - duplicate `paired` is idempotent. -3. Run the actual pi-goals path, not a substitute command: - - start worker with pi-goals and pi-intercom; - - enter `/goals`, draft a plan, and select Ready; - - use `PI_GOALS_SUPERVISE_EXTENSION=/home/code/.pi/agent/git/github.com/wassname/pi-supervise/src/index.ts` until 0.0.4 is published; - - positively observe fork-only compaction, actual pairing acknowledgment, then worker kickoff; - - preserve supervisor stdout/stderr and session JSONL before cleanup on every failure; - - observe supervisor monitoring or steering; - - complete real evidence at a clean commit, approve it, call CompleteGoal, and close the pane. -4. Commit the lifecycle separately once the real path passes. -5. Add the three persisted model settings in a separate commit. -6. Run tests, typecheck, lint, package dry-runs, real RPC tests, and a fresh read-only review. - -## Known packaging constraint - -`src/herdr.ts` defaults to `npm:@wassname2/pi-supervise@0.0.4`. Version 0.0.4 is not publicly published. Do not publish without explicit editorial approval. Local testing must use `PI_GOALS_SUPERVISE_EXTENSION`. - -## Important lifecycle bug discovered in this session - -`/goals clear` cleared extension state but left the current model request under the previously injected coordinator system instruction. `/reload` did not remove it. A fresh ordinary Pi session is required for direct implementation. The redesign should avoid leaving a session unable to resume ordinary work after clear. - --- PI[gpt-5.6-sol] diff --git a/media/screenshot.png b/media/screenshot.png deleted file mode 100644 index 3a7c0aa30a6d6dd42b62056dc37d0f25a6e74da2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78504 zcmeFZbyQnh_cqGgzNefD&`_j>0&Plhr%2JB65L$_6eqX@ml_bff#O!Yc#$9p?m>$a zNT4`DLh<4^=ly-}q4)d#x?|jN|GJBek?g(K-g~aOWX}1lXA-WVCQo*U?hXkF37Mh- zNQ;Ez&LIiOzXtxiK|Es~^wgI4^~6#^OO=GgkA;Nf?MD)lQ{t(&>m(#lJ`$1*a}pA< zWD*iu*L0+&1o6Tx3uSo_$<^O~Uz!TziDyVX6;)+PH~zdw!o^^m`bLR(Gl?STFCCwm ztqTWFW9!VFvrBlvGP|L+-Ao)1T}vt6{G5L%#N^L9$1gW!-@XcinCoj&;bhBnoAjnO zM$TljDkmRqHj1uA?ORmvtL;=G<-NC?$BT<)COD@5AS-(R;`^tY|GJ^r+x3*Trw-JN zPIN4KE~}NtKH3OP9{&8Sw4yNjI-Mpk5a%cXErr#JR46 zd-CgwpR~QvMB@quN{$B2UrkkS<7e67-|7bS`}>vcHGw|0=ET`3EH^Acp&N zVX)5ElG*g+B6+02$HO0Wn?Ix?wIKmM0_Wr07-fTQ&1X zSc{We)ey6MLs|FAY)?5V{f3g)b!i^>b&Y^0_Ea_}AJMDsNZql{yMH`;q~5u|FPW>S zX}Ctko~H)AXG>!=a45*0O7L_jE-3aw%PkezNl*_D(Nuq%)^8}a;kS?}QW>!tOK)}$ zQx;mODsmN-`V4+0(2M+TWs8`%PuhBQ^|RGc+&fvf zha=WQw}^Qu{9T)lyXNV?ujO_y4}a$wsj!2=NfHXweWG# zBj#T6DQNxNmVc*%zF$d>ybmNT;ZqCUJEehOB*p3Le9Gec6aoWF*Cy#)ujPMB-M)2n zD-jiXg39e&X(}1D5h!|BEP%7KB-K7QWds(wB{P(0=*kIo>7WtDw;NKQt( zm8x4geYp6&;b+3gdzB+H@igyKE?`^?nRnO~cPjUc5+b5lAnA}iUcu|@)xfW|Yju#A z^p~BAHfbqjoYt&VyQ+aeDn_~&W5=iIem^gvBO*qW@kgP2x5UIqL7>j9JE!l5;!d(U ztSX9*XJn(=G80R9g7|hU%9VNiD)&1ceAtF>Ez>w=rWShrffGKig_f5;>uA7dRUtF* z`&qRCSn)k}Vz;ZR3r%<7aT<(tf3B?t9;HNmPht)B=I1Q-Kh9N!3d0T7y{~3oYUmUb z=1z6Wit*$qb~slAO!4j{c1a%;HK)x@I>hRpd%@&$-gb&EG5M`IeCh=uB9&Z};0$?I zo6Sh{T%`ON8NiJ+u!JkpdOkfx==KNa|NTucc21el^QJTjLK z69w}))uNIJ5NWLVWs!-Qyt>O@pi<|giN-INy+_?1KwyL83r?bLdn{!E{EISpPGCfF zjC1}Qt&);{B@pxEv8=pBquHI^pM`4=MCuiPGa)~YydqcW-ES_8e@6Tn(eQ5W^HX4P z2N=mlYfU!k4+VsW7~iUB-fE+y5=?=^RzRRqJrp$2;℞&FFlHBZr?MsB^=_pgCJ( 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μqjRn800;@rM5T z))D#VvaX`Agse$PyQ09dTNJboWurP~)D%&^+dsO>&(E3%+x6N)*U)Q(wrSoa+g|gv!6W3$wO|F_IsYP7i~SOPV@Jv`)SwHJQ%&C{L|*`a zzmPLZ($F$}(NGBzZ#ypf6e`ZM8a+I$k=&88bG{!BJ9{)%k*z2SL^A~VH;9VY+)54@ z8B8?y;SHU8gFzH4vyS-S!mjTXN1(RueqnP!ji(k1_Z%^u@6Y~R_ULNa?}=cRe^zy) zuY>7J);g(pt@b3#_nVCAe7 zqHP1LAGUr>pe7{zukwyF6x_Hd!D}coH^H!Z1ujfpTHts-U9_2@R&}Rm!IP8vs=E=^ z+v(QW$mFaRYUs0`DFh<#1F=74l-D&TJkcJrO_$fC0z!zJu>JK_nI|q_rt(Ms;5u_? zhPR>zBhp@blwmgkHCjye=yPk?^-{?`6AX@I`C8_<#L*HMQKr1z2gA-l#jA>1^85jq z^&&nMf$-Za_6KX}eqSR$kxT;cIOtz^99IOh?$IarqhjOe&&L>dh=c@A+`SRrpbK$MOk;Wl-r)Q4!U8yNMqLNGVy!$nyhxOxBGny zBBHvx$N7JL{{N40`bTe640D8ysh=8m&XnWk9-osNQE=2p9EWE;n4s+J4$)f+aC_SB z%MhM_-3*qt#9{Tqs7EQpyBM%fGkB#UBW`Q=58#V5`i)Ky32i+1#>%YF1$ld3=r?$jb+q^=J~CArW+lw5L(FAwx9xP&J8mGu0Te#i%(ebw zz^@Xx;DL8Y(olVU(=niW)Zwp6>v?g(h+7@A!!Ww?^P2s0TiBAA)I-tn|AhV^0Mqmr zd(m?w0gX9;zn$dHT4sF=&mMr4|3u#j4&#Rx3xrrjO~!iF5`4;+B2+fTLY?(@f~{A5 za;XGql2xm-ZVWcwL3qdu2qO6_vO-^!z%X&ML!wCkra`B!P|np=e{BvcUYujUet#+U zUyU6!b6og$3#i+6E_amVly3rMMa~dP&jRD$ zSg@J}?;J3~(=yQxLC+82m7amX2?Mr8#l2s*Uj|qguqRQ;XAGBf$O+6M;svb4=a>qL zu03L*+#vsm#V)!!d1uyLi1m+@=X14*4!t1{%1k5ZdF<&#sT;i$CcG`P191M87Tm)$ zXmnVW(r>pM5mA^0$3AJ<83}qnxO;vBZeVQ;P|NWru6$BP4#}RZUdJfn%sen|+(3C1 zmmGjsV_%F>KAt_AtcpZJb&`hx-^^$u7D=eKw(Wg-goN#Ul-!bt+sN-HuWdS*c8dJ4 zN+#PMd3O@m4oPpA5kI(yHznZmouItcet&d81=ZabBe;_Z3PF5Q?GzQ_D> z%T>lW5h5p^e`d@aliCa1nbqZ2wsUWUIJt!?>~GV7@kcb-L0kqXO7O{G#5LtNKnSks zIpB@PN&M~qpiE0KQf`fH6BbPPMqEUZtq?w3Udkqp@NkVOFGgK~J{Q8DH`6^XL1Z z#j>l9CjUUu5xq@b&3oQwXNN{?(nNJv!r`4(;@|Upgeam$U$O}E5Wa+9*e$bNZ7Y+| zfQ7unik?@l2^>e0-;q~uAFA>k{J0DyI74I#h;hV8W;J&*F8unzM1R>hcEz`S>X*}Oma z;vG2=J^@z0j7hm(;ctmkWbokN;Ev1(pzIwkbZNjz7hH5Jn`A|>ZEtAz-RAxy9*~(~ zH*4o}*inNT9vvtw!c(`=I-f@(E5kzhIN+>`oQ&~muA|A?A6@)Lyx!~dvira>Nf9@w zQZPL|yuT&m9Y3}7%`g>%bA1fyuhHE;<{V4`twmRyxJaraE%owo&{L7%} zHKVS3F)CIv_F1+O8y_DON%;w02@UWUTYh0`QsmxRcn>1jA83^ObuHxf4z+c9@=|QSa1-WN09D3bZpM2KCuEB6vU2-4|sJBSsy6L??`j}`o$2Sm=;ykxO zw;ubi&yIP#ez=&*?z0bCG=}<>nh(>r?&tSuYLca;tw5m@>#ly6G+p^ns^%Af?Fh6!GCvl$`^GOYIC0?8@o#& zr!zSlH%PV~0jv>i(wPyjb;?W1?@W<^_Q#T$Qo>c~8JzIA?KcR7<@DLwISvdS9UF_+ zCbk1cX_N}`v~F%a-6t$mYa&ycMg&y~^+Nj}lR&`Y`(~@##pn1j%|gZaB3)sfF(JnQ z4d8GLNk9I!)xwgppTE$wfI5z-@c-?21N!?raIz#dR95$(g&kVYb2`U|B+UDoF;e2{ z^?ds33$jRXj>%K@l?qb(M?Nq@I{W9&#;v}7xs$^+;P9*^dAbl~c6bNc$XGWsktY}8 zEx9rXoH{2n1H&Eap1a(CkurSvM)$!=q22CeyGD_ZFJtp45OBh0iamPw#pfVTn>D)O zutS%jXR*I1bfe{<)LB?ve}j`249QvPi+i_c5XzRzJy~JEM?s-nSUkIS@Ks#7A9y9l zdwc;$Gb7IbDf&PCBgWIp=U*pP+)KP(jz<-DQ9o=gKVigGou8Cm3t_;Yn!@I!T;s3H zx?%&hfoQhq5KK@*Bd}~hf5@yW5-lkwN+w>!~yltUCI|BV*1>hV2LTRs@;{i>J-n1cU^yjd@41@KZRDN2--U}WBB&xyh| z<9uq261#=Dq(hhYw4=_!A9MYs^8slnXVgeKgMeA7eB-wR{Fl3WOFbVN&11kBC!%wP zJ5)V~)o$(ajgf!+>ifRItZyKNBxLt{{Z`2nr^$m}Mo7`pVSLx%5j*LN!z9(2TBrRP znX?E#H<8*|G*664ABF+HW(u1T+_Aj(=S@V%7{XNz z;~Ot`ERg9;l$0I54$Zpr>3-*%x~d~1!w>d<4yiA(e;ztaIN!$my`fu6Wk2(8q}u_V zeYtp}mq?o88tb#li-rbD5vr)}JW2gRM|;&K%iQNBnmskRpc-KgyW`RB#;Wl|G5MA% z_9P#67fajNhTDyu?zt(US}`)mqp?3>!xQ$5Vz^6tN?(-G?wz4@QD z4Jmc~f9>H^R>Z#8DQ|8oKjR@>r+hp1eNR(NMeaTSt+%-+YIbkU=I5PB$(Ylv&3etu z=yhgnRXCt8EPIGTSR|a#`Gtr{m(}=Wf6Dvc9#wq(t6b zmUY#i6WDQ^-FS7smh+Y?Q|vzqh+fkhTj>l{x^cbonT~UPW!9~>XC*{&bSnM5-deg^ zX6Squb6M8To785-ApYWp*++QFku8SkcYIWxNB{F{<(fGott49+qs%(FbF+~)kHt*Z z-+)3Ye-!*)E6SR+&8Bl#CQ6( zp-P{7KoUBI&`IC_YT`>X9jAXiCGhXRANZx2p5sG)u8kMQFGA`gd&6(X-k~wL(hXr{c7j$@HJokbMrH>v5G#+0cX+_*FhHN# zB5-AIJytW&gM^U`TrZL{zO(m)Iq^pIbAtGI&5e%WTmvv6e)_rmAQKWNK0*h{WmWAe zGTU31H~o2mZDY~Kvc_^$$zY`y*KQ9-Y~Kv|Yh|6|c%J&1y_b>rF!w_i zCTzV+a|+6$jeG1YYG>oAxB*f-0*csyW0h*&(}69mZ^2<1C`?B6JDGg>0MsQOqK$37 zSg4M6h~N3BaGWiEvd^Pp-qv&-M{L zIA(r`{Q#pLtf=7raog=oCWHEI1-EMp4-@eIS4J~Q6Y7{@Cz>nNL05G1Wb0ITBd?6U zG_&{qly*bWdcNAiE0kg)ZgeXCgV<8JqD3^mUGbY>h*ezwL76!DN0*{=K?Vgf3|#?DLc-i{Zr1*@45lA7JpfOeC;jgvt{$H z&h5}i)%sH$Rz;_>qcKr8UEA2}Hhht4#_vaT^mJp++>ZfcNcfe}3swt4#M@2WZ<7WU zwd-h{D=O+5+Aan(?Wf{$N+GJFNl`?1`1SiAag=6Vj?pP zMx%?-cv(}R4fTE|*9i%l!)+XLv4z|}1>-0B(dJ%r#`ie+8Yj=I)&G*ob=A!s=jDjI zsG;HGb}SFu*KTsd>#ok16D^yrqKbPW{PYATV5ba+2p; zIr_q$ddDqbU*t9T?45ed@Sb_EA#n;4uk;e}f+c1W{{c){QOd}oGz$0)tM9H#6^P& zCiRVNQ)+o8bHIxr$&i8xy746O|H*lf!^qJ)%`SKU-h~^jhVwsD%bN2mz#OWor|JWZ&S9nh89nQnX zftxwBx~-sOJ-4L77^_++%0f{}_B_4CiIT|3&N$OmL90n6g!J%}u71csL*DEpcaC;A zbLVxIqd9f5qw9SMRA%s$0(xL(Rg1BhX>8dVe^IxEanj8#SSM@jE^@wvdVthBn=jK+yOiNuA0{Fu-?~X6c}J-m zuar@SOD!{(@v|5ko{V?14e9X#Ki^6iox5*L_eK3B zG1$81^?vYnIkm6Eh&&O|#p1t+JSgmcruJ-9r7_Qps6l?Rr*ISsa@TnD`zGa;AxcUp zkE9kLgEf2k9lY;JLF*Kh(i#QQ4Y#>mDciMqE=UcnQw)9`ef;4#VwUdZxXqw;aPAIB z0rl#HIIHk3ia}$`nB(%J;v;tAALNBKJm;+4gE6bd-rsNB=(ToKTkTo)yK@zuO8jGs zGJJ4We84kgvxHhzYN3=`>UPxALdX-_hYr{C!MZ3VHs+p`A!M`g;l24 zKIXRWH!oOhe=@Swuq&Rel_pPkfxI^xpF1~9&f3ICaoB3eGfVctC+E0gG;K$FXZDu+ zH!;`y<9r_-l_zL46nJxP3Wms~Um3s7cTKWLP_4G`RReVzrk?BQ1rMx{)9IT|(oUPy z!iQ11xalsUnAf)5TJb>3Ys@>3c#avn9aA2O2D|wNLQN!W(n}7j7*UsW_BvhIpe-I= zhcDe*FAO}H$T&&i7&3Dw4Rs3n>y36;hCEAW&QD_wk<%xqs_XP$$`EnZMj38%7v4Uu zqOHm`5F$C;kf7yybFGtcOg5_OK*&l*?UWIv;>thMPmN94gs!WN6De+N`v@xAEQRow zt;bFFa?hoXW!a=MmJC)~P^%B{e62FLDEULc%gHb$tN!UlJ6ZdR`J-=>ve{}E{eF;g zjK=2sMRVk;UfL<>uM%N1hKXyhe)W_dOiWOr^N)~vF{h#@6Vq%?g!b0^E8!#Tsf50y zao)ffe`5JfLAS(;*J|=&;E_wK^zOY{4pLPOLK<0dZ;0W8V{Rx@7q!g82J?NY(2dr= zy3tB9mvhu7whb+xO4j8r9ByE1iN4ohmhD_`P*m)hf9T)^PQHnA>MmGM=}CAYd3*D9 z{`FHEj>AuDT$QR>bL{V>7NE+huQ=QwgwE&=7921ODF31X;We!F8q)lTziMPr8%})H zic&rAm53}-6dGgx_TC$5`B1*S~l67jCQLM~&_Pcg( zpBDN(x#k}0fFD})!OI0@Hc2%Pg#geg>bP5P`?5tvufVQHrODFTnh3exkbNGIH^~uM z2&a)e)1AHNu#p$B19=7FbSKNKDwfb?AGy&-&|Dz69Pb`|4ixf|dAB(FGQVf{9_riY zkgGdl3z3SY(!9zl=9X31`*SYG(UDYu>P^GBzi?h(%5RZH&fLtWht5_Fm8Cl_H477PpN)EmA{~i zywtJlDemVpWg+VtLNBJq@5qZVQXlZp*%-NE{iDTinlLRP{#1;vnyHyZOTmdzbFLej zkus7GtCM~-@C4?Tw|n*uSiSB3q4Dun zoAbsxGHI~i0R`03d{8a$&katc?%7LBh*KJFbIUvLL7~BgPq}xyYAt*U_ z88!Kr;heHtL9^KJ@k`zJeO4`v684KJRs4pcqe!-t>6W5jjk3hU;Tiv z*|^9Q4TC=Qp5VN$nl^awTk}q24WMeJVbN8Tnilt04>#of3$X8M{V&?)xh{n>;%lr0 zJEq?ixE-?hCjNN3NFC|vf1T)q!OE6qz74ex#s+Kq^EB<-x$ehey++SH>A6$D*?KlFgXYk+`y){>gqFjjIPJWXH?=$#yW>cV|k&k%Ndw6_OrBL$73CMW= zSr5w~e%Ngra-H#>pr5n59zq*XcERo?SA#O19@c`PdbGUjJ5M->i z@Yc}))Dq+9;N;;R_w_kJ>#@!zR+5$4jBRgQa5|H{{WHV1m{YgB8rOvNk-PC$bvAG- zE8Tefa3n7k?`>r-Em=IZAArhkZ&-#p#uZwd-GWQyGYs-*Nz;6F{Rn$?v>_i*fPGuV zu9B~fI`aFGj>uY%CnxCHFa!?yyeyI#7~fAB7dVQ{0anohksce`O@C$C#CvSlr})fI zUs}nM_YqFWe1zY^80->>T5A4He?=e8S$FyHOs3 z@~Jkj7cOjg8X(y?O#jvN)Nn<^vA--+-hIyk-IH^;%2BxY*w1>L@^&*b?CiM2YR&?X z$DgUOM%!jc-pmU_WT)FDj2{--@0gdc)((e4!Zg8$`0dVwh#a@$YJ2|ukrn1msBKsJ z?eT)c>B!jj5kWx_7wM^U4;|t;77uWC^RbNMYJOiL(!#Hw*F+)%<(6^qzWN-Y56?3O z)`l~tfv**&&R3TMcosf!gZP2vw)xDe!(XARh(hy7D=D2$z2T`8;j;y%&3Ch4G`_4> zbSr(r|CQKDql7X^JzaIo0hQThzL6|RhO)~jdA=*cyv@hZJjk^jfmK8nL%`F5f6VNL zPXZjGdRcdy-dR_L>5}D;*>|Vx)Z7}Ztp;SelH@sl6en5d=21FA2Frjhd)&-2-mlc$ z7uRG}DU_^ZWl2gp%AmdTb#PgYm=4^zU)Az-8=1R9cCALed^`gQ&&)O>X9Og^P_LY`Fgfl5b`b za!2xGMI8q|TmI4jizMqx7XKJ$wrTp9T=@oSJvS=k7N5->B_VJ&!_f0K>>G1{Y zIqZ?L?ft-LmS6ts-ZbCG9}1I0llP8MBcM|$N9Rp>lkwwm6+aWDgb%Q%Ck&IPB_2OM zrkq}-ygPCAc0zDgvuS91&ebdFY8UP1=Q5bE*B4S;O)3)FVUNawBePLuqqHA2Kf;-D zymhF)RA}`>-bjM4_Zt>YW7vHtO~FPap}w~3643&0VKYNr$+pcn61(;Ible;kGh^K-B&GV zB4t-(UEJ<%o!efX5MDj<_7#m|n;>W_Lu{G*{mVj*x1Q z6G&=ge$RVSYtvPGf11yj_ExK^x^^W@KZWm3_PttByBUTjT4Je^_eI%^$<0kko1{Ed zAKYnNlF7JBG(G(4(oA7!qeKZOre`di`luhK zd^pYv1JlWj&g%n-rG}=GKOi~Jt0wiy?ZW++NuFpmxX=+)e^|@)jhq=m^Scs^wyMqf ztZy-)qo;$2#DRg3&Up;`>O$yub1REef7kc552`9|*4dunhT;XWUEKU=+L6YTQ(`r; zFq_Xj#Fw7vON9T+ukpI2MaAq*Lk}BvfJ7`boL)}d>RoVj76qn>V_G{5O&tU25fd#i z7DBE>@johFD5sGUJoXh`DStsmu)Tl(N~dGDrTP0LUSrK+nZm9&6I=cyUt|pRLT}uU z`_L#SG%oEPLJU^%I*0&JZ+(JEFg90WaY-{Do|n{E%tk4AaKkS4`;+@tx8N;byd@>) z{_(b>d#NS}4rd{@^!=z{gQ6p5K1-{c;yVfszyov~d+%*t2RSJz`D@_~5B(x9JUOg& ztCiRz&9aH~$kEq(7xI)t=+uZLef)0N$v3V720Q8&mT9y?v-B;kg`+Dln=?NFbIcPJ zF&}-$K5p9F9mGWbNGq_wNA$N;gP@bazTf zNO#BRZb@knkrJh0bd2sCjUpi3-SDAnbjNf1{k>k#{~K&O=Umry-gP&=1o$14UwjJ? zK}$o3T-U{QGs`y>S>pzy(cc%2TFshcikB(o{C2z;PKQRO~Uz%*duw&G|(-dAg>nWdgzJw18fuV9}m>}<0 zWIe0I4GFAm=Ug<+qK!8i)GXoMujMu>zO6B4dMn4$7-rsYqynw9b>uDYZ#Xb#dyAJ- z_^hwDLEIU*>9XLzMyVeWI?lR>i+}A;WB~BQeQbr0Q<$LDr>OWv7bX0quZ@O$ixVa# zlRPwgNFnlfCP{2ov@BU2)DcIJfcbg^kd)w7=^(N~L&U{#jJ7e2%^r^m>&+B5Ns3>h zlvm{O%x2aXoxIaIbiy=}xW)W^^x6=-#z?Zy0x@8T&yu1w2$4#RYuZ0jMLL|DPtvrl zusOuIp89;k$si`)yEvKrkgA?T34&QFR$KRr1$Ar}W`nZc3nPTwW1Ce;L4ly?QHA39 z4(X+vudh{8lJPC4WaZw3v8T;UT+hXXM6cs7#?#n;93JV3^W6E8X;Rs9K+qOuQN;mO zc;Ci(<=6V~wzrXkIYG+iyIqF&GBeh*b_>5HZf*rapIz7V>d$_XOqPJduWR&QR5KMB z3%=qt?zI*rMGyBEkdh&VtP6-Wt4K_8X1OM-nef+qn^ojhs~=xaig3*Ge%w};*Y*4Q zcILGUj*HHbKlAo8>eR*&^NVcuIg@k9Xh_BI2ZFpBf**>yZ2+3_D_l~nLQrXqeL}rD z_`@G{o7$I$`&B=gheCA;3+Co>*#hi?gEYK!Tc%0i@7GRr;i1@Lp1DA<-hRK7cSh30 z=-$O$C2h`fO9@)%v#*gBzsRnE5-(lvvtMnuv7v~nprG0l3k5hQ*#*vaeHgy+q#J2QYioFc3~_ZadGyhUfhyRY%3~@YEvSQqJh|-?>!{tQUq0 zUtpzg>%4O*pKXqRJxtbC2=*;j56T78=fKn6V);H=i{(ZW=MWJGo7_L-j*@n?P=Z8^ zlEaEU{N_d-ChRsldrf`ww;{?Z!QQQD_*X_(_!8rRzFpi5S-yG-FKc~&Wt9RqPSh1! zJq=o3foeYUy%6E@6;$eo8&<9Rv^{Tnw60-bD9Qzk%p4G^1<8qC%RJlXRS9fgH4Xdx z6tH{lPAm#Dzl$}suG<@c?*knu&GUTaE`wo~RCzhxG}Ea^DDeJFVy;QFro+Isox2{c zhj^HKxwyuf7^VVA^g?Zv&Vc?wFX`$)c-pI%LZ*dn|0zy7%GE2DlelH7OQ-&Z&JCJV zgbEUu=C&88?UhEO(NkWGJ2er zJo3**C;U;wCaN8+2=a=5gY(<@sb^eOvK~X#%fo}++vhi@u{&~IeXLB%gm@cIOTq1* zslDuwuEh&-;7mgDhPOQ1Jn!%{O?-`1C0=XYC=c;2p8Uwv>#;zZ!d|aO9VA^dt68qm zjtOfEN6R%0Ixaq+E|_c95q(D5@Q4f>i&Qd*8CRCs=I!d~EkCbZ*87_C5_BMs6KWIa zT7IcJ0QL+M+~UEF&A9x?3zGS!?vBsuE*Et1<9$J|Q|YKD%JtFgnCBLA+!MdB*Fxfh zrYj#3|9w5C`(Y(=I0><7q_5z9L=!@P%aS>sns}{`T8SS>%Mt z{T$}Mi#*I@%VzZnwZG&V+cU+V#yQN9X=%3Cmmo0wqU=N8V<#ef@cHi?YwY-#K9tZ> zrdRK7Bx%b(ZK6f)sB~jCn8aOF{Z@A;bfvbve$9&0!A=!mq>zd-#mmk6wn|6IUnE-P zk=ExQW~Acriz@lY^XIyrrWidFRf>`b*O*mfcD2p{n#MIvldfd(I-%t_G$EYBgrCGF zGkxm?tuht9F<%Uyl8d+1N6{lS58+9(mtF?N$e6u?X+0<4Rx*I~$jQFAI?2bkRJIRX zN>*>5bDc6-eg(?&QLcXcm|i{C^_ARWtzZwh;NDs5HrE5#iW)yg&&h5>;FTnvts3Nq z%FmbR9ovTg)M9>d1ioO}y9-P1-|H~r5u6xy^u9A4W0sY5%Q!*BNgmx*W+ZE+iBTCg z(?9t7x`bB$-70epMzt(q>?F{(dzMEg4Dy zl*=czy|#KH0Xy^oTMpZ~s6vb;P{`Z(r9oC#HE$_KJ;8-eBmZmf(XYl9@n``mVw4W=f z(PRzX_`S7=yVpI7K7a!HnJ?7S?T|(D32d8tURHnoVE9hPEyE2dQc2?e?0e%!ng2w3 z66h&?6ye~z`IP~r6TFctk8f>n%`2v?Jmb7e`+71}YaiYm-%-$V5q_WlUn^TnYf+G! zSWyVmX$J?N39m#flT8AbS>V)UGX9Q9<*lu$dwGe=fbEUJCyg97s)$j8;zV%%HfEd; z+-%KmFR}R0C#LTCC~tXJ18zlS2!IwXP|;xK_}k!wUsDwhk?qdQaH@RWYir z_fK~{X^*p<+p3^ni9e(FYde!XpX=OhbkR#;(m{trpxcA+U<3pjBF4#3vpJVx<2Mol zbYRn4MU{p+U>E>KkjVDx%(QHZ)K-^4Mya8!=DW{xQJ2xSdZVni#c-YdN#wgn9*&9Z zq}BVGOP;{m8qaeEkiJ;A>5siR*4^X^`bDVHE-&NKG+R48)ihmpfZ+`SNz%_96hTlV zX8+$hJ$^pJNMO);k_Sa`m-3>4;t_zUFJyXwMg^p+(|pnOo0$}Lu%tuHkrfoI`)~3= z0L}xr1cP|hZv$nVGkmg|=q;NDyLa;uX#8zD;m;gfAMTBcf%Y2rpjjs1sY`~}L#~-Q zlppsUv_@}0{cI}S^tg#@_pt%?Hfx@A%LECRgjNi5d*Af{((~%!-v&nee z-xYH>2CBWGN1#^k8b7_xN9UeskXE~qS{rU%icjF^D2Kvp`{b_r2ITz9h_Ke;)EZQZ z+n-X-XOG_x=+74zn&f}Vpyad7KD<8K^{eAZlQR|xQXCC@QFDN&8TQup_?sc8jY43n zSsa*nmgh$BouRjX8V@#ih6j&GjeP~ndngk}I~ipWgm&ye!$b20tyXA+$f1>fHOibC zEeBV-f*{GmZC@f4a8ggwA`JYz8CW*A_Ks@Ia7U0RMb%jU5_09M;yEB^%O;hbP^EUV z<8rq5Fv)t;nIXFLs`TnhKob>LOp5cA@Z0@_YFUI_Un7^LNserw;KnbVH)<}C_cAgF z&^2NsO8(NSIo`T_-GwBtICrmi8S}Q6z~Uq4R1%K_h2>-;7s#qr<4#|DVhzdd6l1$vO7BSTayeU_>_9v5l0+`Mbe3NnC;< zC#x&F-Hjj;hOX5|DxJ~}72l`gos<^~zis04WJ=H}>eV|5W$^p-`w1<-DYRqg7We;+ z!~lR$i5hY`b0k1#0SsFnldnLjoiG~5E!$5()uYH|{ZVLmv_?}Q$p>xscIhiS*PLVD zzA7*LIYx4P#py;;R<9$Rp0Nmk`3-2-CJ$edmXrk@B;ur+IZezWj z@eRvjC(@?0dd>So%&aCIqeL6EGJB3{O7_7QIWgMG?%sJ%Rpa2@@-rp47UY^_?PMh}A5x#`&?7V=5{XRIvAYwVW&aU0Mmc|4_MB1gRO`RXO*_sNY zUFA3KA2gvK+n?*M14?PIh}!JVL6Xhy0^zFhexm1WR=X8Mu`HlV?_pzQaRF-6LsSq< ztvRNdv0H4vhC+y#XEAxOJ%vIacAD&L*SmkLZX%Y~?k?rKZL7HYfvH#ydXt2dR@#7#Jo zHJqt>|Dya|fg7|_N_Z#ZW>hriC!Wcevs3`WSymsk9#l}%Z%tX^pu$kf`v5w1WfJ;o zsQYmUb7PIV0_gC)r^BjBd%8AGt)-C+Ju=z{v4|n9aeD7DSL2i{A9zc7gOlGs=nd4r z7@aS^uzFdz`}{h67|bYii4l*O4BkBx@&2fb9Vkk0L*pVTEiapE`_7c{b(uEO8&_=4liO)+E^UxuNS2I3^E#CDUlU4{Z&jw>x zk~=NgMq4QXrO>m@7I^4(Y8J2XF+vXt)RP@j`5hc~zBP?xUZ|jP1X;KbufV|9n}Mx!!9F(CUFFg& zEG3TIV8>)>l z1r>jo@+G8kGq2EHt2W^QB*Eni?_0ljzI<_=NB~Q|4EhGWo<|GfE+~;2KDAEQye@tx zMp{0r{Bd{bYVMik-%GC{!(1KpBKW#^iq(`|>v&$HWJ0NBR6)Y{%WjXwgJeqldg+X* z07JMStAqrSIT@_?U+C~Dyu7@)Uapdv0H{K`|1WSDy150$GwQViq{W}lw7zH6jqlVV zLy+*pCVSBbV_w=yxBnJd>RmW-8TyS9jNaX=(09K1?{aUX5i}lTdF>WG?JJiKs_0by zGPO%K4}Adj6xPF1gwI#?WpyVD(5Sf?!1r0_InT3?TQAag6aJb;8-&+}g1sH*s# z>tN1{>cLU4T9%)}S{s6H?nP6;?<7&a;Tht`Eot_0(pNYc03?ULa$#qC~V_e8|%bu4_nzEtpVyIv5wnl}A5rB(NM)I15`?+urB zTFX~v_CKJNdf3@ud~lMFmb|SohE}y{&SmDD0*_$UD0S4urdxhzzI0AdTAK$kTI~U< ziv9?bLzSP)#QQOu9V?xUM)V}U2B0*qUcNG#d+ZpWM61-i!@2Odo5Sg z)>Dg7VyA*gb+}V{r%icbm!A%EbnlkFaFk^IOTD3x2fk}kZ#H8OBhMjmY%&=`^W@*} z)~(?SC=Z1nUK6_M{rOVS&L72?arHFPy7mTUsVkyVli#C<@vnIGlv(tJTmhKvb-VlK zft2U~QStL5WuU9%XPuJzLA9YHJbvb@KAUIydWW2r67wWs0)t2cWj9d-Y92aSo0YK+ zb9=<@gOD{2&>N50?)XrH#?vp9Iv7~}sVGD#=)w|=h%*q+(V(iOZ#6!hfvAfjHs5)+ z%x@|6-|P%SMw!$T{fX#j%77`Kwbl_Q(a$4TjQ1~+z7oYD_k9URJak(0qEMfihpv~p z!e)d0Z&D1<*VkTw^f<-@Gn&()xR+sIgx-hu9nVaNL(Q_Xnyr-C<@WJ!t5mv=an(ls zN*lO4(A&_8eaq+>!d?0|rKOcuDirsrA|!T&OUTbcrH1oixZ+K z0M{;vBwyv_3EU{Pu>Z|-Q6A@ace6{Z?6zpz0rwXUjetPWy1hV* zUr)@Ocq9}Op1`{@J=Wfr2#Kyz^8quM${6UPu+&Xb&Nzi3otK&p6bI2CkoAPxHgsH_ zI#1qUE9~x)p&Oc92%uxC`o8w@>-Y+t7H~{X?MWl}LA&=wD{Day+`#-34|Om4oiQbp z(OUXx+hjMl!6bO#kGiy`U7nE_Tg+VqZLErKlEA&KTik32LDgCugTU)@mLJcuWi2`` znD2P#!gU`_Kp#a_`cUkN1sDX>2F3258NX6V8jJXt1RXlmmbVj#8rs!Fk8KUl4+r&3 z?kbCG>P2`~NQ-+b@;Ogoix;@+;Ra~0`5U=V7*50-utVJY1oZ>&C#ViC#wN-($Hm1i zEw@{LOa0~X4OSj*oxv*&@ZXr{_406H8|xkIc}NSVca&`dKm{r>v5sneD=vIYt539G z6E>)SV&uTzHKHo`p(yzEt1C&ai;f5^mGY>qL5`owi5rNe z1A=6A_&MFLh(wkR^67{^t;9?pg(*c1+`jQC1^bnYU2v$by4y4S37=o4lca|C*2V1R z;OJ7j8;RqO&Mjt>UtZpQo%{Ns&CUdsj@$eru%}iM@XAixLZH*)VpcW{GRdPkx`^J+ zFDT82z0ZEm5+Hzz1N*5BnAov;-1}@76!+E!cH8!i+63-yebaXq+=98!3J!5=Sanze z8e<7ByQwXS#Mf#9q?!C~mQ{luv{_>VM_tfr0+2pK*k)zpeS9h!CEW{u)(wv}qb1we zRD7pxMG)0Xop5a6v_iQ}PbSGBT-WAok?pj)h4P>#hZ5xPRy3L#)b5HahYG3}v zv{2y+($J{eC`q*rF~6;@&0OvyJFDf-gGtB=b&kV>@G-L7ObkQ1PhqNQ%Gh%DizbJv zh2qf?hyR(lUNx03DWMWeRR6rObIowuPpsM(R7i?6Wea=l?>0?GiR+nM-wd}x*qfS| z6cEUBHDQs6YuA>JTN4gu&dj!h{db@C+67OSIvwCUr!(A!*uGGj=`vlQX)Dv{c3Ug5 zK1|^rrY;tomICb5P{^skYYCiK>8mKS#%6v{a0hVNIo&a8 zyuVjzO;!V3r38iJj{UVoukC)Tl_FbQ?VS8(FT7{JPvn=>6nQXt=KnQ+3rY1)e9nI} zihjP(B;4jikKMF2bf>&x^<~Dm`&EXUDv_XQ&XhONeUh*(chu!3t?&oop}&%{O*L}- zf9TSi_QoXQqwEYYcU*}`v(M$%Fcj!C>lLr2t^CKbS0;^?nSV~A>_tP%XannNc{o?q z6{~}3{;1oC=+8GymT|PKrcr0IE)7rCY+t{%$@%PhqnfvBr z#j(6DhGiB*SXhG+AHxogfA(x8i)z!TA!Imhc|uBBC%}7j`2MtRd-!|7g6t61(;fK! z+8X21v^2$bWZSMH$>hv4RH*Hz`Jf&qv6ih>U|*Z8=zW8FsJy)ReburEMIn9D`@$^0 zCJfgS>{XrTsTZ2LYW$I<%M_H_4kAzh*`+kuWszx`$b50e8 z&DE^#Ba53gpTX;-0x?7F&bMqmin>(eQ)DXOc4IqcuTsIxx0e_t`7py*;60~gY0Udo zlnq!+q}pc$nc|Fh^hqUAX#q#@ZTy-0L?f9Yq7Hi`ZOKYp|E>&&#a#0D{FWNb`?ESz z*<=<e>Y2$ygzh?S5;r>S-(+-cbGqRoz{_F z4bXmg>pL2_WsI$3wh$~U8%NoP~1h>`x-XXB6`ec7A8P*=Emk z#Oz>QzPbODm0tyd#(iy{z0#sJw=gk4k=_Gxs!AhkQ`a*VaPeb9eP3Jtb}!OSb{H1n zt*b(V54+F(jt%jH_Zz}U?|xhKumAdFi05;|8}MQ$qm23p`LVijtGkZ4mx05G{~<@8 z+0d7^h&(d)-!)COh>Uf3B-QtH8mOd*`O}IWF{x7SFFr%ae#bCBxQuhn^{T;>e zJif)|kH-V%mX*)-ocKF7-4_r)(q3DiLsTk4mYNHDvCkem@X5)H=&2kHdd}NQ9%dqy zu6xdQmbf;jMlC{C>$Pn}+I{4LBVTVS?Q-G<*CEX`7H69){0fiuIP*b|30~Y%ru*Jh z1BN~*$=7t4@nE96bhv)0vIQK!8&i2%W7L!Ief50E^xKFrnlF@|F5`i}_T!e(0^wK_ zM;}T|N+kK-H(EIF(>plH2kr8#wp9G0%XXT@5_|V(bKA7<&nfRZOu4O~09%9_IrD(v zB2T@x4S{5!`uT^iLKohpORJ*2^+q6ScWL2sR^Pr4M9s>cv`@OR(fa!0p9}@!x;Htd zE*0i4E?F}ZIepnGV(G!ba}5_OcM`S@%l#NL3$~RgnT<{J+S+wf#jt6lU&&MTMUNvF z$@3p6-&zW8De5#Q5k0Hid!xoBsdiRo4vM`i540wRCvv*!AJ>aoCc1v0FK*&aD6(j< zg^{pdL*RBP&m|n+272^uB<2`|h)D+tY$Ay;yJO{N(#-HxB=l^QnuY1h*PE@dK5s& zX=;Nr>|B2Ja&Pb>g`o%3I}|eQ^RWLQScD^ztzQAk=Jdd{W>!-t=~H|58imh)_C_Q=(l8W3TMm!qh1&cJUWS8F4z_Wp!0#wr90f%an$(@BjE?wmQa$ z%TCXs7;U|XHra^o9v?U??`Pi^5V*Uo?kTIgSm1!CvUK?be96WAu0}wh)ybIC)pnk9 zlb;^`vG_ehJ|4B%w<$gk>HJck82l;cejYyb7{5ni%Mh$c)$EF;y&&%kG18CzPw1rI z3=5-4qxs%%o7-JhvxPOtb`bF}fLqIqThSz)7V@bce{@bEJH~bb0?<|~m6(C$v@A2F zY?f91;(>AMW7?ll3dhcqhtB8R#dZsMaM#(es?vEG8 z$t|SMOLJHwIOIXO(I0mQDeYS&l&d$BwPw+6`H6(WxP+44NTT1`V0?V-+ToUZLC4dZ z+2~Zxtl04ns?*`q*l5g;Q~FCj8EcQ?w!^s39MZ2l^hxq~+o%#xASQ)WFfs+jOx*=jDKCzkJLVV@ za7BRj(ib-aY3L7_!7PuuND|K@jz+eGwLr>K;NWQW_LAd7C9~QIU!G9k>T)P(YA@I z9kz)bO@F__Yg$=@NFwde=V4NtH;jK=nXQBVVB^)I6sS!s>!)TaSxHp2%-c9mY|O;I zEvA0W5`^LW1TwqY=jBKQc*krAm7rlc2c7Mtw193~t@Rr{(>ZG4yH)9pj@?*5cT?19 zW23QNg{!J8@rk}(JBGLkL2_5;PV-X)PiC5L%uQWGPl|$HjyxMP>u?H&>P6+&ft2O5-6TXR7`6UQ znaShxKmkHgy6@=uF@gG`wIR9YE`>0nzT;&Pidb&`Q`7V+XCv0H4pk@9V*im}x$hgn zH<~FW`fw@6tXoraR2f(|S*46{Q{<3- zNtf5M6DSW;T3xhztW`fDSh0xe?CG&dmN6RnMT7?NZv(Z#;!nNs!qKOkQkY5qD?vbo zGS#J?{lvKw=*kMO__F@ZA|o87_&xMDjSP2SwM~5)1j`9(>o60F44iio&Hi#@B+vaB zt9PK`{FZ)HtI5ZA5-7l-fBWyylmCyabh~u@0H`ike@#{z4d!*}aTE=e-_r-O@>4MG zW}YQ7e74Oh{jHk#`@hZi~U^`sb6&kqb|fEZ zdpbrDy%*GXkw5l7Z%?QlTplq1LQAmezMU~tN1iA`h?xO6HQ0Z@ME4l_zPEA0u<*adUkL?3PswCA}OmuyDWhUtNs82aYdT&|$&1gspmt*$kVBf~HlJ{}y$(S{u0`y)wqEsIdo=W9BW zID~apBY;{(&^iK`vjZ5s?yD1QUwE!0vL`V!KjSj=F-Xr4laJ{+jIvc-()z0RX3K?( zGJ_8#X~P|9?7Iu$f!fWXdm+=pUaV~9Q6kA;m|?P@6bR?D$dw|5vi$B4tDp}W;|yAN z8P6E`+i^Si|MJNaXp7W@!{qtRnWwSk8c4yh=S(*Ee-@dAfAY>II# zF_r)u>L^F}P{dD&;d9^RJ$wtWJs7&n3jTx8WV+q`YOMY1OU9mh0cD;UO33)i{v&6L zfxdD^eQq7bI6|0D;p_*{>yyc_JH-`5Zk^umk0eJ|sp1%oB7|ZD_HKiJPWMa)!Wz(9 zLh%!Cj(hN`;uA^!tdoG69X&Rs*k6cFO155B*}p8ES0eL&fF2SjIez-ZhSgEgF;C3~I@(;=)s$=6 ze)x=LYgG6K#PQTG?mT2gvu9Y`asA~42`6=QHNE0Ut9ca}1T4L4s}mvMaK}gGVGsM# z=T@tNn2XXl=e-|`>?Z{5*Z~Q(jyn2c^yACOur?lKAHDAgLPM&9?ZX-cT{T`jhCrW@ zC=fH17!%A=1w6#Tn21-ttA(Ht?)^(-X2jf&FB1sPK8>LDirIfUol53iVY+&+ux)7u2lkWiUXBPuLU=!1# z-#_N#ro~@_mNKq(1{?+(RsD~F=(q2vH&7Eug&e#0)A~(A78a;^-q6f$j82=d45^F- z4RuqYveKOdu-W<<3{lYF8F}0NHN|-@#TV3$Ozzp9x+%Qja?J8sqAslmXp~vQ4Z#9- zI@u5zWryQ0jUR7EB>5bB4gaGr>n)&qxtN|1hCa-h&_l`ezSBNm`ahED!(-yFyNoqjriL@W+zPRYt1>?gAu0M}Z$i=eg>c*XDD( zt^<#K)l}g_{nqJCt~t*~Tb}?uS>rAbI$rj^em;!+dkR^H^Lb$qX#P1!2P&}$p2g{!DxiAGYw}r$c-*A{2b;PmAp)XOWak4Hn9!AU zVz1ZX!<486a5eBi2TiLi#)A{mngq?!}vkOvBOH@Dov(-P7PUvf;c!@(tjm{f| z;)1Fehv-w){GHdjO*M_{{LM7VZUH4_7A0u@PU>Lqzx}rs6PL~2k8O9+j+TuFQtlto zR^tAEjPLW{dm}SjsXpTrdMtkxg|L6g4my>nr0@;sTW&eMza+jXG#TPmW%O!PI9B*Yk9C6UVwSJu zaBOP##*B|Uz6QV?v0HIeyEY-+S4NXxrC&{H`D}cNr2;(L4(ru~p^NTSejqkP};>jL<0Fm5lCw3kY)a=x{mjo6E?sH4*(3C-1N^4jO6 z>GQ61tY@jt$uPSp;Y7=|n}*CE*WxRv)GQqyZPP!lNk*F#HGTo+rogpf4Qt!R`KKiB zM@Z&gEpXum?&Sd^i&1cundJSk!|DJDFQK^NoH`Qv18rSmz4*5Q+FFsG3XCtm8y>pS zupfwl$nd~Zz(#XhDr zRAsXlsr2QHLd-{){A>%~F9sdt=by4iU+bxAf{qm8AM^a!3OKRE*-hypYE=^_J}icR zk4k3vAE)cS?h0VZ%&wiB;r|s<_L~t>p%2*tqs8s;rLxL~6Q`JMbPEmR<{rj?T(E_M zxP{-9;Q3Xu1XIJZ?!v5OEyDhMH58xoHH-!8iOY*A>J4v&OCr!nuv7JzXG`^?qOWbMFHLyWV4jVb9nk}Jk++XR9u1%|3qc1}t^B^m z^nfa%H4YN{*s-&Cix(dUs4BhiUn)(-q!urk5G9tZQGX%RL=AEKIV}!Sy7IKB0;foq zGXOyu74<4Eu@1nSf&{&;-}ES-wwfVxbaL{Gu@DI<&U$GHH!*u1X_Sy%S_W5A#r`<} zg*1^Rcx0sE>f3%gh%g<8>8a`1)Bt2?7$@V6tvGGC>ww&9vMq{RA+#Q@;2%uAT=x0O z?O;Yo#4=pMn630G)7>wtshTL%=2*L)C|24Fq+pDZCoE)1qQoa&Md3`OMP~IG3|e2x z%UVwI72#sYc~74MndyWdtB>A5ujjhgDmtbrWKnRH2p`>Q`=IrLg&k_>(+94P$*XU} zrryAiCOGaa6kVAKj1y$00lu}+&=1-~@`wqc7Xz%FiD2|HLzJbhcU=U3v+-SY$c54t zB>)GyMK?3zl^Xz|CR2FZl;E(2+6777Anm29t)zDqknE?5CZ;c80MZFaGN{lIRv2Y= zPx$9ZFIg^vSn;jr{9Jx zrM~r-1?DBg`_&+M1!AK@;=^#m0;&hcr`{z^SG-uv?pMf$a}I#5>{JToP!PZ53lB4 z>H&$^|AoyI@%{yvASqzkiMMtI_r!IMf|>aZeAc(tAU{?{pj|gro}H) z#it~Y``|^zSU~SPe zyPcmCadq&KeN0l~dPj9XrD1C3=`!P`JZB%XG4xWg2OLT%#I(KCpU=jSXh*?YfR&af z|2>fZ3(Seg0#f!IV05ve@1x6JDW*s>6zqK^0m|JDpI#WK9}+XzSj&g~L-e2PLVehx z6_uOrVs64Ool{NW&UYjK`vh1H#iR}D5noAhD-aSQK_|i}n<`Tp))neF-(}z!Hva=K)drt=C zRAc_*d~MnhbX0vsEbZ=(0T?KnRc|NR3xn>m+;^2*gd8t-1rdHFTQ~#@1StY~zTo19 zXVp)Oy;~X0g;-5J+#7h+FRYrBpR~%6C%UX3 zL#)hl@{B6u$M?!_K|Vo!|3eK&yUO{hr>>iE0(3=Rz6MpI>#l979)H^J;oLq%C(X^! z?ukPr@(RHMK@WG6EN4pJE#=N0{(Ux-SO#2@ZmP+u@a-2vjdv0s{r-nAgn!4BB~3r) z;=70Ceps|{G2>n4x3E=Tx_iZtZwF7H4e1Kv(QY*ruZITels4*Mp?c*Qkl&7#t~^Ql z`Tr>Kyl$Frv)?Jk^iIdXp5&NI(|6Sm`fvA~jA7$T5|ed=R+vtJqDZi6&~xqyZ%iO6 zFYkv?lnsG*q~Rj|A~jo?5cs$6;oD(yNyx}wSAtWVp1!%q1D%EQSw?&gnYtcqTB75- zz3c9Z2f`~|_Eu9AxoX-!_17c_c^j%?VS6wBLy0liM#Ph!@sWin#nj&js}}*-*@jI( zuOR_^+EM#5eg^B^c+%(bRIJ7D;TgzJOlzW#DC^~f>`M&z_{pj-Vf*9T^xu&S{=EpW z@isC6*Ozsl-|0OqT1EfC2HijJKaQzzW)HrKGo%1i05|AU8#mDR=-C+?K%^1B{L7f& zg1o!hbFb;UFWHZ>0dp$br_6VX*bE?{ojbQ$VSGT}-m%|!!_$Ueiluz1^f%bS&%-^c z>}aMhlYd`NkN0KhvC8tE^Vv`A<$M}gZoyn!0rZjx(TsO}AMsOF8-bBJ!@#H@{#Xj+|$2oQ4`g^8xAF(D_|O zZ%M34QNt>88%Ns5n)J^GA7fado(VFf1C#+fB86z~_5iu^?}0>2w65q==ex!!_f-U9>7NJrIs3xf zdk+xS1mCLvCR}(z>K7CzHRrB65LZ+c_Tup1|__QBj^xQ+%Mia}x5Uwti7!s2R@9+Dzbtnzk6gYi| zT4|2+Ke=1ATzy>7Q%mM}n!H>@E@fX4_e{ zkb4X0>;Et0C)x-U5}T9I+gIn_4E>z~Y^#3G!ck8-f1YNV%WQWm^Dtg{glbA# zS%e5+)z_BTyl4M7qa^ILvv&!zTH^n!6d5M|oLV8blU{*-3&-lSZ4biHEE=)1{9488 z%Ca_F8p$YnpWLM1j7V8(QSL(`1znLq+O=Y7Z(P#$;!PiF&u3RMu{ogzLN9UMwAkYa zl8Pc1P5a?mkZt^B{(O3{2m&B2zl@cd;+6%*{=@%`{mIgf9ChrzM$Cec7-Os z_Oe3LERQx*Z+HdZu~~v!VGX-kYX@h~W5vluPBfrSIzH=lA}1O8p5QHrZtEG{pg335XdPDWaIxW}goJ zVmY$2NCEpE2uV<3iUGfx1CoH;ekuTFOEltkJvQ%&Ivv-t4;J!hUJ8{yL(A>n|H6>` zYA98y1?ar4mF7eTJA$aUt>=C|7dFj>|1TYlf6bads?TqtT~!%FVQy*bTJ{8`jkFaO42&7n`6#kJAVAu2*v5QIwk*2U~3r)K??q$^fQka^mgeftBj3q;X zH=N@O@4|OljTFsKzh$b~G_sU^PPAKu!uWpS$@=TNfW6r96YVae z%sNifp`$f?V=FFrDn^$nerEEm-bN1Wn-d(H8em%0pU+1H-L6u$5z~Hc6&@tzPn2n1 zBAo4+eqMiuVJO8UB|5~ywkOq9p+GdpCqD6Pkvp)tilW3l*;Fab=fL|!1apeS#UYJ} zIaigAE2Iq+)t8=2KiqdMG3jXyy(dc?JisTvcEKGbkb-S1QCu)~2FV?4p-Ukza-?Ck zw=FX|;>G?;8442h`*Ys7?Ht8!W05I-++(`;ftTT={~@@H?P%n=T6>i3!(b{*23%`A zL%WV2OSwUwSmh(17rM=Xxnp&<$M`I@bRxM&0R&63QI|SBCY2@qCQ7th%@>^egO|*) zq6}x_-DR$xQhl*qLCt)M+V6kQ{t8<`Jep~`4A?qLoQi|$k=WB8=o6ghMbWF_-5*d*n#tXdGW zivl|a*jzENomR3gMB$6%+QI8$rk*miYBZ2D^7S9p@4#_@9y(u8u>er-OO|`kL;@*U z0tY5@stbpfD(<@TI2JsSAz`nBJ;#Z)dC$>5V!HFxhc@QF>(_!IB?N}8f**$Z0KKgY zDs+@kA{&p4Wn({OaS3`&8~r0eK1{lqj}FYTSpX>WW$UzKc43H;OC#YG1wz4fnA^Z( zuabZOF&~r~dGuJIQuB5hRAe%r2N3%~`k=EJiiexoGR!-yfQq?hn(QO4&ksNd+|g)f z-T0eKlh0))ry6zmbe2;$fk;WQHAtz&Bs&=}J3k%!6kfN<<{YVPx5lUFFDR`i0E~?m zoMSXk??H?!%-~fS(6d=zl)j==?bGfKKh5xSU6yb}t=0sv7#CSWdR7^~0#3|=OlEt| zdUpn!NsAk2V#vS$Y@VA!9I7IPjFYU|R28z2Md4=q~us0hBS+6qx{7qAO`>D6riHPF_v@%`o8D3bG zj}n8y+RCKAY+jO_`~6+tg^DkM#67GZP?OD6XgYo+l+=tKS+D_?xKc2NlQPyj-6q|~ z-FEwX)LUz&?I`a3M{CRQt%DIr^>-cOHR^-8H@oBCPv1_oD`A87QaK5`aF|xAD;h0a zD?vB{O16tB-V2(Ec4cxx)p&l&p33}%{uYa`)VjI z;9jgpWNQ;0_;qywLTZ&p8x(f#5KtPWsIFNbr$yPInZ!fTl3x5i1&H?YMg>umrgRUT zrSCln$Sd810bf1j7ye?c^FM>Bt9*xLOPWC>}`|i{I{Ww3*SwC}KS;@L(-gAyI)|j<>I%9)sXP zWUl0tg5@u8zLxVYI3CZqb@+1oM$6>igY)X2!J$l=sn%aIB};n?jkS`)&;bsI?Z7VL zI^J-Sv9W8bvo6tsyB~s{p(*-Y4%ieS$IlCX=|mc)8FSh1xlZ|uI9#*f-kvlSZ#WLl zwKp=iKB$>nEQhO~_9`x=n$8Zt$ZJOBd%bpsgPU8er;J$|EB~#~<;L>jY{Zr=`_Q|5 zM2KrPerAS?;J^mmcel#Q4vQjL#wfi9$fy~Nar>t_3*gPYw?miX(EI%lBbT%vimksa zo7u@aHWGTTcov%BCAhZPe`IHNvL{1bP>B8TcxIt?!HKWi`boPlWBtnj^>8XB#wlLt zVj`pmwe%s;;9;?@knq<0?puC*Wx&>cBF|!ClR^6c&#(1|LnMqwNr9(dUt#GktTTk_ z7ucEdh?4@UjXfX=IH+Om(E&V(VSt3u#5FH0%hCNon6U0Jjy@?-Z+vQT&+b`;7!S^_ zGs?5*UvQ4;fb@r&yhpF!yeT83k>x~AT#_i|iE^1YN!yI$nY_bU`bNXYU>WZ&S&OE> zIqf9q((4Hvq)u#q`$U~W${?6!StM6FDYae|@M79S&R(n*`g+HFn0Waacj#n!>mQcs zd3VVn&SLM4lAF}VIBXwTP^2;k*%13M&xyy`s3M$A=OUBl9E0OWR~zt z&763bflTE;-R5gexZj3+?5M$Rdz9BPd11RNUn4pPZ@DBnCsk-3ArLnK&J8to_H)i3 z=5JPuh`z|*tpDnv8=#Ta3b(GA)YCT-MuNHgb^fpj|NOb%t#5_K*3jH7i%y9+bX5F% z@5vG!ne=(D2nAwzbi3^Jy;a$tpIs;0FNpvhNNFUeK>b@g#A@!+l}PfV4l7*$@~OZ?|=+zL?85&fgXZS?s=;-@MU;XPk89s_^~t)dtm!XZ}%duOqWWb{a2t z%&dX2@iN3qukZ4(1=DL)E1UhDiQlidaY|p?DT#bFKQxrdJFDE)tCT_*LR4%FB2mC9 zD%kW@TU=ZB`8h;1jpfd9YZ!x&v5E=`OjVymPIN`S4QzzdgW@)%!Gr1_^jtlC48&9} zSveM5*50@ZNj;@wPvfpe8fy#tZUQKacf7$VBFv~RY%4AM@y7E%Ml;XLI0i$2ox?p` zI3_DTVc`K@tjEcQv~TL3tTBVxZ3SV?O=u;0`u0lM<5BqDsc=XWcua^gPR@6J{8k(h zdXh9uNbNT{H+Gx-bPIE~p8Zl9jp;z%9)sP2lZu@$>w_$@QJb$`+jx)Y4p5NLAPuan ze-zu)k1DD|Dn+Fz%ywaem?nCs+e4(RhN3)M%A#y&`P&u=mmy;Au+5F0Z?belP1O zx<{|zq-JShkIt--jj-X^g$6z;B#Hk%IDzv3_~=rA)8=tc2Z#XQ<-x(t;S)G$Fh;Bj zb8CKR3Z;A#3=CaP+QSeu4d80uj>v^UkYZ4!+5T2<4Odj5yUQV%P#yeUQv=R`mc5MY zk(!NG^@Rhk&8F-Bs~Lp}V=TkcGf5FwAIb1&-XNLp;$7L)DAbMBAORqz%W(6Z>DL&A zxz@C7@u`*9RaE1>ev{H7bI2sGAdWJw)W(X8-5+FRZ>;U1kbdj43F$qm`k8Mu99sI= ziWgKS%^p@h%TCF^Le~Tz1Ohn!KWZgKaOP_3Z~l1K(A0IPk$+hg-cZZ=}GcQ(_A|`uemq-(mXV zui-bVgF}*gWI|FkWm6RZB@)HFj-xlPlIUD$-3=r1b$q|`3%0(ja+{&{EW12x(^|`R zuP84stCrJdnActk&Sl!{sIJIm$8h6(xVsfPJ|3m!r*!QvVH{l6%J~wy;PO%_ z?)QJhDMqd$PQ@iSofk-VsnNr!MCcTnCml&tNnc=Ro%nI2J@_u6@k)wP=smg=JkVV^ z=7n#*)n0a?zvn?g6Q{z|pB1_jWLi7+ucwk;m^to#ej=vdz=)Z-9CFu{l6XW#1#+qG zz-`28V=mR2s{4QT>agPq3i;TjOXD-p7O=^hkdU6#d#O}kEKCge%GtVoRL}jH?EGdK z^g3ERzC^zA>Kv5&5P96z9m;KiQ=ErkK!#C|yt+hl+~*AJ-Wi%Cw^CuLdq1CNHC-t? z*i+-6EX5BaLI!%mLat<>D_CWl0$H`qwE_rViRAPX!3nUt)h~3QBgNp^jeJkT655vV z0h`8%A3QsYczKJFEaBQ42}GDBXEzh&?$1g`Sn-u#d7 z0EQfO?C0SDO`8rv%H_-`sBE=qZ%s zgJhp7aa{!f_++RXit$HL@;21r6TNa$cygMb6dwU-OXbrqvL+lD{CqXw`8k;8{TdiQ z0Cskx68wIts=oOph?rSImg;FAHVE!&3h&ds``cp419CoSr1CPB?T6Th#%P+Z|(eu>`cvDobTv-49!A-ZPJ~0THIN@o#TQe~&)>vwb*&)^I zlPj(Bc9fv+GFt|Y5a?I!hw~zoS(WkFe3!gCXu^;QK`8nvM;OZOK?fQF3&VqOsACyq?*n<>KzNkOu|FcTn z=OYr8LWi=EJ6p?|LD;1GvKBAF;30egK|~E@;ppi-x2d}Bv&U9Cwq=Qe{-ii}GSMI; z(F3}A{+8*Ea`IXuHNaOD=GgCd3^TJ+&6j3=+6&;>tXA0uX&_hhxCJXexmKG1a~UAtyi+0|B_bG2Hj$@B%-`0WYRa`zIA;IzEZbSR=i_BIK$3uO<=kv3O8f&M=T}5mF zt@Wk)9!s&;kB#x{H_bR6=I|wB|0_urqvUOE*E{h&Fu?^wPbON+Xzxz1_9Vkhl$#9A z4|J%BnytdS;E-O5cDnZ@E&%S!<+FGfB%%uZ<8RZmoG{M%_ST=PH2hRNU;p-3V2r-g zP8HOQUHk&yB3GspAJEt$Z?w4Dpt>MmHNsB(E8>E;Y#5J z?*gjxM^OZ`R66VT>g}FQhF-5uK^BCcugyvd0D_zu@n+sa(~o($e*%JDEGzi{$KH7t zY+XO;X1cx9gkMiT0@i9JNA8uI??M44r(|#sESo5QOqVtHA++;Q&a~j1zs{`%emo~P zVQyG)=PAmCX0YMGcM*tFcpk{d|2o$tvz_SAsVLLCm?ltSm_llqI^cCPiZ;etG{3|L zB;6_O9eFZhunZd#(@h$8!o!7~nJ*O7w3F>VwhD%tVlva(=5T%{&X>7l0q9%_WYJ@uQ0?1n-A4h=IQfnoLKr>Dcuswd6(THBXv(*X-K z+i+2)u~!D>q>AlKJ(5j%>}^MFCYJhkRuv?p*hWUdJmK2l{Loc^E&5};yo~-I=`f?o zn^^KCt1{9lL%>rMYCHZ(gOnTxWE;Rm8U@{p*5RP0ueFO(XB*xL@A=Crz{*jKwe_Xn z6<-7> z$^LC(+L}hzuqO9_TVg_*xNYsMiSr42Ckr<}Jq;nCxJ)bHJ*04YgwK>qaJa`{*2qvB zGkV$kqy=dc-V$W`bJ&CM5feiwze_~qn&ak^RRAqnKxn+Y^#A!l{HvUXQLs|Ilhh>^CfL695X$-(ys1Kkd3mdqr@JKPOEs+0Iq%EiKllyWPMmkB2Sdeb4D zb)sm`+$g(GLtq77I9uDOAhXpR%`iJ7SqhsFq>$zlp^at+h~0T#{`HP`*K3gUam8G- z?k(=0N@v*sxON$U1?#)c=TUn+9s@Z-6SFEqWYUZ0+oOM39hWbSucbd+v3jS6!GAAH zl4|2dD7`o zCL-tKADyPl2<_8p$vHoF3P-l=R-IW6ki63xgnh59xE-g1LcA)!zWj5MuG?|kJ|A*E z+)a7$Y!P{4T;AJ711PwZ-M@zwz2t(mtxDc8VGQEU#Up15{kC*K#dX6M=oEdgaAnyX z^QKSepJx3On+oEfZR0`w6|2 z@>)kDhqHWLxM3vf^CExsU|UYuI&;%#f>@fXK|eNVA`5 zN?#pxo6r*E2l|qcnXP*l)H5$a*PX9ROizMc(|;%n8_xP_eN=?YnK8y z^ZAV~If32d(}ItwrYl@H7I>f>S(}J#Evn(9D7C8ZWPg1Y|s8*O7ZzBz$IlT7fBj*$1xhW`=&RL+4)LGyxE|fuj-sak;v7ZWAkV&rb1Ix+8`pDcTU4{TkvN{N8V2i zgUg(*L;Ilvyna-=B!gqRahF4Vt_5-U4Uuq^9$f?6r&$VJSe3i_7%OT5SEIV(o?Y>* zFXLFyi1p+Bj>c?*#LeB|n!b$u0<I_pIb+!DSq+Ur6J5d+d_9A%sX8dQYp6kO13)U@M>U&w6a}(BPZbKZZZ_5>DS% zx%n(gRf%yd=*M#ut1EXi;UjL0@}^>)y2$A7V*ZYj5#!ZTaKPxh%ZNzdGStEj7n^PJgcMk}v%)yr#@S3}P)dY@Rx~5KQziBPeE8au{$|;`2y5Lv)bFCX z(m{ClLG$f0L#vrvY{Z|vBokg*?qeb+JpIvxHX__2k2cTmT2jmzG5gS@2>bw{{m{Pg zeD!~}q}(dg#H}3@l+d4ddiZci-U9X4|MB6+|KD`EjG13G@9Y?$+`P5*?+;=G$I6Q} zA{)Bbzb37+yFDCvEB{lBHpOL7iCH)HD~Kh*+;XYb0Xsd#UWID!G~~JEUi|$!m_BSY z<>_`bB#n#pe+>91oK5l8w`l{Uu&q6dK(7m}3d#hZXUT2||KllvEF8hUVL=i%>f-PJ`` z(S7!4;j1Nj)7zgT5BC{jiu&KZG4(xx1GlQ;=<{oWV}=Z_WyST%7+*VryV4Lmf&=)w z=)J2;CSQu9_saJ03Eyqvs&Eoh|YXd_zdsm`q|n`mJK2IwDFA#|v%EY$WU2YidYLZ%1kx@k zTL*+I_j_mV+Q)aTdx`z%QBY7!T(3AJPk8@s;Kjj4#cnX6Eb24wG}WX31z!-Mo!c$} zgO=N$&&{ZeVYz5*FXOPl(|ly?`7TUu|I)lF0Z1%QHDjIW>Rakk=Bh<&aM!3d7Mw#H)dedoFL1YLZ(VqWwYHI@;xWk{)~97NCdqj61m z=i_&tWfgfvrN5Guj0T{y;jUmW{dx*%JKmMg)%pDOH@Sqk_{Z%~Z59hW?{fgw(^0Ya zd^u;-c7=T}B`PbDRITTW8QO<K)?T6EA}hZ=hcrUB$47P8yKFyzOJQ#- z)n%~rsEA0hnbM3LVs{PA87AS}^m3wd{p{kKPTR)QWyPv(R^2LnccANu2a$T$OJ`?I#pk=2vC?=Gi>e_b4xX-Z}JyMsCCyNbliJ%7Uy zpv6X!h4l~Y#*=JwvLKl7sZUg%tJ42tR#pTI2}GOdZ~%f%_BVMa_gIMmt1tBMled;X z3#YEn8YAOD^Q&eZZzHP9&2RU6kOppR$Ffn?4$OvkVh3nWd*$*@lHRXpG?_|skpi=V zD?*QRqTkTIh?0+7F@igJBj>V#mu`v&m{(!plnBPbq4`qyg(DJIyFF>Rx?y#*d?7Tu z;x>b+sBn)84jp)n0A8)W`bEl3a?A76_-<{${22(iBrQ=iFbsuqgDTLoWf}ex=jPwd za;!LrS1e9zmXmi%&o4w#zH`Gj=Yk)H?lg=U?$+VY^(y9UWNl8ZA{>|Gd^&YnDj<0w z7i@-?_)R;m;fvEZM_nLBCS{{cl!wfD+>ky@ZoK=jH)=mHMkBV;0vg3+lMej8(7IE4 zT*$5tK6S;FVgd84Og8sr7y1i8z;h>C>_-=*Wo6lJuX*jq|C|&hgSN$$JU{NB85utH zToBw|zu=$2YIin`?}ydr<|TQUFTx)ob8+x3_{&j!`{6xRq=F_z_;Ty0T!VW~BDpTH zG$jgCTyjr|m{;@ZdmQk;upRG%mJcS(uVMzUX`n^@|DyNHP}7HUlaXZI)a6z#CY8t& zhI#YvgwxZok6ZjTkZYzIrp7SZ0K>5?fM`jEkYmKRdnDo6Tl-t}V@bFQyRyPN`6geb zkQMXeZky*Ot6}4q3w*1pHG6;|t^klUkNw$`sG(4ZdkX87Sk17T+m?5&!akP^XPo%# zN%Kv<6`M*m&C;9~p;3QgQXRtR;0T+4d^Z9P#)! z1g$Z0JbKOgF!uaB?lhj6_^vp6ic6ieBc2X2=TxD;?o!n$nmtiOW)8cqbnxw{Yj)d4 zX7x3^<+KNQZKD}%`Jx^#oHDldrgeY#DWHU`5!dIv9mk0E5W;CMt_HSL7Q<~U)-B*K zVG|de6#$gH%+3IjGoq&>hg9BWx-_*vyk`udZ?ev12r+Lmz~h@stx5hMGd4bdt+gH$ zTQOWYhh?<+nw26#{)kT3<1y6{TUhPe3uMAdB&FJU6);ebhy? zP^AYsD+8R~Xfn+X^fE_c=LnqrgKXhX_boCX7E-Y&`WB#KAos*IdMYEdLo(HUrOX}7on^Cg&1@` z)Fi6oRh!S++JLBnE$YtlOY~CE{%1cpzB4M7de1?H!$#tUC690g;6Hh(hmgS2xK7(mtk_PR?wl2Q0%R(YC;e|ECSi7`L zNQ{axsB6X3V7h^eoar?Z|A?QCZ@$?DDU54|-lpZh2zL2+!Y*%K1f~%Xkr04z#uHVl z`d0Eg^&Ks@Cyd;$yn={vIG57uySQr5tf0GX5SXhI$!c)5O2s>U9Bb&wHHVe2Jv_$8 zQF>KVg$8pEK=N2ZMMZnd?>_w$RN<3$+5omxYC}7pp*6x~{`W4E48;hHeA9 z*HYA=FJBb$R->EQ`TsSQly*DUp5__N-Z3^u?x&3u&m=rM+ID!W#|sU<=Q@Fx_qezv z_+u*ax}9EmNeQ?gUvO~pFJim2fM$fY!FFdsAQGE=!_*(xHQmz-vYi_etS-iqQcyxu>^&cN6wvlie+H{<&c9w$*$7mgKPmeQ&) zF3O5MiZ1-xx{|h!@N@t>j$lcJ zl1Tu5Th!iKxchwpohzK3xz8?_LIf1M6WK7Q)cpz#d2jRQiPVTseTaG2uV8y2TKag; z7G)DnA8oH*e_TBj`(ZFuo)BE~n)vicDS&MU4XG%r1Fxn}Plo)4W2>TpLx4}B)x+q9is%-YJ(LLAVx=d~pLtU*M8!`7yuw?R-<{SJEX zSd8xy3v7KScuiMhvm37{QYIb>Zek5LX|A}slCXc=JgKD;enX(WKlp3%mXa9X8xW$k;Yq0Cu zN1zM#HLAHN4-#5x*FpmxfdB<`>fU9ry2xxE}P0Oqp?N2zC9jS(Jfu;AkSvH&s?f+ z$|$d|BV-pdX=4m&C+9On+;Q(o{fGmeznx##ROn6(NX^A$reCc$R3l$Q`h+l^- z+-`}|pV;#DW$lFBUN%6|lep$EN}Hc8Hu)-f>FCbDfIT% zpfT$#Vx~6m+Y-MEk1nV-D(t$ang3yRN6k$Go$qXHLvdcqCQs<`#h_@VI^;647zXj( zLAP(L`zi^fBck{lVfBntdU@CIc6)sLeyLv2?P*nH`UpGVA6`0(szWF+ z-+nWoXIoWTUPFyt11*w+Ll|YUY=&J3VnypO<}>ujb&#UB7+%ZNuTN*UT`+#Sg-%@5 z?{{Fuw`p6g7s{LXBu&nO)Be^!R;RxO7g;`s>C3`6wZ2``vP9mj=e(vnVoWl>^RI87 zYj{vR*muVkqw~AMG{>RGYQ+7+gbThr87$(9xAFy|7Q?d&c5zru`unbsep0i~|K`K3 z+>dzX??}=ImUCb=qhSMvyyq0ogv3ZAl zwDvBc1bptYm*0#7DRyf+yzkid>}6wlq*&E3)eF!;E366TU z{Ja)U&k(QK_BNucrTf#uSg^I{4{Wx7&A_TRu@5sRHNDTXhd4ylkt~(_EecwS-!|iD z8~>T6CV$ul{{cuL>L9MC$2ZMQpJ&odw89P34b%B0Eu-HNvsC6p+9!qb-dkt#bZ21;fi zwwFS75Yl;s+g7`qHBUxFDwZxu*glhyoy9Q=W#o6~m%1!8WiOPQ5k*uxaM_u@);%;0 zBLsYjizHm-H}{j6{LT?O4?h~2>J?8))>SSo4=#oL;uo2_Uf=q(R}!eDPf_AalL7yH z<6_wUjX9+hs_9l#8b7iiwKVE%k53C({q~~YWlFv_6c*SWa(;_Y#wXXezI3V~?)J7nZlY-Kney<$62~AYSv4gpB+RdBbjk`hoB>F+R$D`iU$u0|RzgWcX(HlO)|~ zg-ug8c6?-3AB;r~A#Goq1dt>q05QMHWRL z&{0KofP;`CW%JmHB$4q5GTHB$PbP3EYa>#Z9Y0*(myo^|FS-?_bbG4iz$lPb6B%Qt zUZ|P!Ue71MXU&2!dx9IMXZTji5s$-KOEU#O3f2bWGt{!u0v_v5estNLYWrJvXxb%| z(u&Kg#-X-8oois4{vOJyobNdzRPdBH=L-)T|NpCYLkzkG^s3$O$bL%$nAKii{9QHZ ze|k4D%W@vG--82L5h&2@_VxR~*9wmvd{^-)0puSOdVd*eC%BQAiq;9~*>0>t5oBRF zh={nnir?SFjRr=rP%0zE_qd_hl#_F}luK)($8Q2{vDF)>clPF>7|9?exV{)EF)6Y6 ze*Yf{hng(iaKE#qTL+I}f9+vs^6QIL8d|`Qo&!5Z0xpq^#HS47c+A$T5_(jWQLkUJ zmtALYmr?;9tw!IY13a!7djEzCj2{#tbr%o=?kDZ!KbN0|X7`&z7o|r6Si5x$n(pDM z2M19?9l$eF=Mbo=XyCMA`ioXa$et2Kpy-zS+AnQD-@B}`4lq=Dx3IW*yj2FU|r02oNa0d8v1a z*~|$!7YE3_3qs0F<`yt!oZORux!Att8F7-o7-Ly`Z~%MaD7z%u#-4`@C=35hfI`->iISq7NfA~bwe zk#rKFQs_UD<62%bFCt}z)?Q+5sVPoK*28?nRbzvYrKFt7tBolr@>?-wbSmdHjsFFm z97hOtHI{)?UH!XQC=I-2+;klbEawpj?!!wm$tCf*T20A5Jxy6TMFj@gWOeDGed}tP ze$Q{}`$F}jb2)FX-m&fO>&DhpbDT8Rg6&r?hd9DBlRL`YkyIgrHl@z4Gv^m^vdOTr z+GXR&N8T@zWZe(!zbpmbc6UL_o&Nrp8pA{N;TD2dk(HWmO1RV`Zu{iucu=hi7tGEM zm~5)|bIm3F0&N|czVSxL#B85ehOP#Kg3DbdIPNQS<8_>{vsk=<_K*2vL7T6bY~+G2 z0}obR^yNcKTJ!cn#=+wmbR@XUt$;CS=t~*jyz|tv+q*SlrQMA1hQoKkmlEld6~NP z)6e|%2zPPJtGV>DwZY4;+&2~NvTh}O&yQx$M&ZLIA|0OAwOc7mH~d0*rt~xQm0e|b z5W{4*M>|@tAefDDT+rac0a9b4;u-0*`;NO?06@d>E-Ac62Ng%)r2##5dG8^M(tf`@ zjN9RtayOi*v!D)J^VLJhnQIBoVNB_Z8P;*RvUNb12F_2n0FVO*V5&Sic?SPpXHWk} z$>VV61(#*;T7m;Lot89NLn9Vj&5)pXTby%NoTO!3=ZC@&)HGIraUv}=!Ogmtsp5+# z0{r~zN@`TS_hsLR$R|($Ms@F*e_VIDb?;FPhF<`IknIezv>&@*pCRUfyLqJ>l47>< zc;U&ZW6vbv=W(|e4IVeVBhsz@HW7CmSA&QEYB5~+;qYt&;@VK0{peT`fzkzu??%ad-s2 zM-nrR-S?2aflJk#5DvL;+R14e23Jo?VUG(LkxbfYA-)v%qDN$R9GeK` zAYgw(${^CyH+vRD;l6P@e7xE*{~_PETCI(Bmumq^@&_zLkiZ{}`0i9~o{Bxt@&-n? zf&-PHvS?vc)Nw1Z^a3_Yv$!}8n$WsbC$Agcw|b^ok>la_SF45F-50G}z1hDAAKb@r z;*xl2%{R`QdsRN%LHir0i5L^t-wJ3kz&JJ38AqIVW|g_+E)+t^s}gQbg=!k|svvjd zIL1QPlgjTM*b4d7NZtWZ+*E>9g$A^|hSltVIJpmG!Jn%~G5CJ-tM5-4K=|QZ`&`qt zmAJ_N9q^+#ay&4Qw?@XFk&pU+c*)%e{l( z0O|7!2jG@6z%yWKN?ax9mHL-crH9i z6%8@stAF0YqcOlVvNv~CQFNB!!db3F0z{l-s4}EJ{|l#X`)%l8;UrHoA>NzK09@*dWj?uZ1H0*KKO1pTynI>as; zTsq-s9wFMR`3B@c5 z-MWBs`&j6w%5QD1WnkRGjC*tGuy>k3yHBeG-~&6SRE(m}qHP<+?buD<;xzdGb0y1H z8I8HvuqNJhn4nEb(EvOpLl32OP1xWkCXE3(9=$9LQ zcml7Y{UDz+TH%*Az_XEqk`L=D*C#HRzOtogu8n^p6y5oWz?Slm=YA+-LiQ*EdMfoj z_2!;qbTWZl!uW~Izw*LXwGK<-H(rLr2Ot(enrnkc_fC*RmOeexw8TRH8-0pB{SSe z)Z3*=NUg4hvB0Zo&4F6(rquog?NMQpaY2`)x=d@H=^-`(#2>HQsMn zc)R&&G<(V5Kv=+AP zT-phYeHsPS#$GE4nq7!au$X>)oxNXX?5eIHbhxW(cLGu=CAwNMr_Q0<(~#zx1zr^| zo&sk@_kf|ItG$R+;jjdS81{iYwmDn2gcr}hzQMC4{|kW(eyQ__J#nKAS|FDL#7CdjlJx@)hZhU6~c%#D|8! zq4T>t94bsX1tXcQVZLqG69?&}mhw@Q=$jaP zTybVMix5!u@`gyFx35B`xcf+F6H46rv;2iy4Kt}D9#y0IfhmB0E=!@@YA^#rmHhq` zlbWP;6iZ-4ZdC)^J~+u8_M?#~g45NMI_C^j7y=V3PWv54;U&+qrzPZW$`O!A9@oRt z$2*&Xo)GtjF+liO`l_=&gMXDf@HR$>4`s6w73Mvz9+c-WL5&{v{z`dLHu#Q9xp1ME zZhA|TlE9m#>(z6~6{E)cEp>*?4n=Y61U;!$DkB-KZIdzO`s%;X_OAqWS(+0K8_zTs zCknfY)_Ci(tH(O@`W-ff*x4Ia$5)^KnsQ6Ye+9uRyff`5^>DggtO`)nEaEjB+*GWI zkViYT`#R*9SX8a4BvMtYsU9<@=g#QyM)CWv#cS_Cb{p&LJHHuZ1NfNGwL<$IF^2jx z@zR@)-$i+!SkehDj@DPaI`-Pnn$_eCRrrpE?#In0sxVz|22ZmsJnPF4|8b*tdy&pO zG$dWGb^iNUA49RcotRhxhJsBi5C z3D%QeM|td3HXdRv{UH#zJc5q)q%LJdeCnoG>up>jOGJYBx=E#wui0x(l<*I&$%!|K zA0!1tJq-Kf9w(bT8-()28V97fG>LmieBe9d*RDUWJh8Rp9IT$-_4(x|mi4hs?9>*sX*L+$%sxPDdR%=C4`U&Z z!$3@rnS+B`qhyyyu<=}WmywEoV)`6sXZAhc(9Jnv#LUX}jz=C-)&0TnML(Z`L3R{{ zw+)MCt8JuBJd?3MHy58EaaRVbzF?C1S1K5#ifP*Suo2|`JzRoGu^m(CG^-1$sYA`T_~ud=ZAgX28~VuazR9hh z$uHmb8fivy6}iaSJs(J%x;~!J-=k920dHw=IS?XCv&$8=^Tb-#FAQrJg8E~~#&!Ia zs&;=xMfXCkmFJZw_BAK=zcH(?;S6z5fe!7Er>f%5hqjf`iy@o2hMA@EbLah>#s)%x zS6dJ_ig6%!+>_%Z-9k%A>YC|tAyb3EgY%uAYv*Sc*QY;0R2k`%k4l|`JpIoN_XoDQ zs%h&=+s0!}%+*Jgr7%5B8+wzh5Fq9_-6!n5;wKwHZDz@^+xM66$ETxYu1VIPAQ3m& zh?~~I>z>v|ILCyihwi``S!I1$0OuuYQWYOfUs2h3-JP)OFwJAOQV~cpFN8~9CA-G5 za?BSCEKS5rf1bx0&zNk|yoe=FWEziF^Jv&w9F&PDQp7w)Qqb4+&7Rx|naCWizj zMO>Zsq?E4fb$WNYN9N^3{a~eAb%4o zDiCi>Z9KmGI8mxn2rA#iWy_7Bdt3_lpGI)Q^mdvSj;7X^b2FCQ>}`7z@X6ebS8Lp* z)5pOQl|nCn;r+8nev+z;Gv&Ts)=$RLg-wWp3{pHn)5MaJD?72g~#V=lHZl~!8)+T!hxgGJ-%C@U9vo$($KM&_NVGnR0+EbM(!y|F)E|dntuI^HGE2YV#+zF)fsK_ z{jAC^vea5|U!aIO5fj1bFl?TFhEX~hZv3RbBP~kj_;V$0NoFc>F1_?%zus@xmS{4p zu&O<^Tkr{_5^~hrpQ3Pl7p$|MfLSO~DAHa2!~*6^v)mfO#bLXe-6}}mE66i_Hd|gP zu41mKxhiOj z9J6&?5BwgeHK&lBE21H^m2Okton+&WV1omrMkoX`6njz5U=a8QJpcq z2(rbSpVtb?7x8G9)<)5dJ(-EITLr~w=S%Vhx`;v9+e_fhi5GoB;O$3U>HO^3v#Hen zD4hXP?|PJ@MPE7<%fJ`5q4iC|AWMcVWJy-thr3M?iH~)RI~(#ZRA%nEC92L$OE_rL z?vGp~W(G8ux=z7o5Ib)gn1Qxdg(=qXG%7C^cFMAfv=*7;~R&yJ|0>GdX!6-hJ%pvw!CMx8N}6m-sKLMDnZWcP2HL zhMv4&HulT2MuCt9ozZiNI3qt)2|Nw)y?3D$7-%<YFBQ!U!P z(9FuNr|;Xd_;c@#ypoQS>nW^tVb{p6ejxaT*~51}Wt85A+%?(NR~@y^Vaq0)QE&-l zPEvuAn@?ei0X&(3Q(31oI(6u}$8+KxGgwGK`-W#gk_#5#r3waeKZ{|FS-gp? zwpL6v6zpfaLwm@2#cSm>IzmRFlGxAb!f6lh8dP%Ax6S`1T9XP5n~Z2})mDzt7hR&K z)+&KDNWAEy*{A0i#5&oF_mPt^1RggjW?u~(qK9t7jumD4;+1slypiQ^6%f3W!pW)} zc9#m@CO+CN)9EBRtEH%REu)(hrgvjP$DOeG*qkd=0xLZDHo9_9HU*qEbQ-5B;G#QO zcR*a0*I3w{yjCR0IFWonOMR&-aYg zx5}#xHy1%AnsriDjM2T;+eDg)6kZ!&GWeOmmCV*I)lUYMPR?!BaD%+0$=ejBPuqAi zf9|SG&5++*LPD`Q68$@>dN+4 z*%fHfnHre_#-kSQs;|krO^Yp`$jmucCV=c=n&xNHzLRIdy7!=3<@T4U!L52!S+~Av z8uHAzAA{|4B_DFw@}4y&{xZe=o$Q8h+qJ0qIV)xAeGFS&BXdtb=7Oe!DbdxT-PY{+ zbjArw4T>`kYPm{TMGz3a99(_c+Et&9uXRwIFS1nYT0lq216#dT^|7Xbkq#=fo)9ni3E2Cth+a+B$!PIQ;Zj#XS@w=cPe0>c^2`Y6_q9s|%9ExT6(2$P+ns7$khW*Q;00 z$mmN!${dT%6WmUrWyJp8BgyhRF0RTAr-uY)zZ-E`V>Q|Mv5^L{E*^S~Ff4VTnCs^_ zXsK&eG4YAS7Sk|n-HNgHxz5z>-Zb{QZ4N@PNks(-i#M$oJ8>2~8SE<6rl^WC%ilWZ zM+s_0Y<*5{E!^a~xXf`HeyO!Om&xv@e|^LK96T|fPH&?8p_5@}N9tNxTVVIxT^3WQ zF5qot{@CO6>ilEI+xY(U(sJYZIltq>DLTvV$i<>^ub%hF9vV00!ojn67LprG^U?A4 z2@+CZbOKRqP5tssSzrJ@ff-${h=zWLR6Dx>`-9vsE6&%@q{XP^*CegT7IJ4MdD zuLVbgru*soXBTKi$*`qyFISf=3hbpQyh_wC?dx`D(dGRHx+gNN21aY`T{i;^mg=av zspj3IjeU3iwV6aB!};K#Vg|cz8tM#AX5Q(=baR!L^I(@iOzX}pGVT?)rAc=j=r@AX zd1q0C)+!a=#6!?^wfk`qI)tRxvl`Q9w(^;2+I%I$YQ@Kp7sIUgX`Kaia&WEB$Fv#`P@p^ilbVKTV=gqu z(qTsCfz1uwC{za8aM$_(n?-kotf~#?Of3^3VVNGS*I#&l24LFK2 z5D*mv1p%c6q+}4K2!bL-TIdiuQj``l5{01zhA6~PMVgT!NUsrTQX?&)gaif%(rXAL zBzc+dUhnYxeedTxf6iIoI(zN4SKs@5XKy)H(XEE5R5^F`%bRVnHX<^vhA7We^-G3N zX(@TJ{3ZZ)U0!GH`Geen0i|x1e(j4T?ouvzhp7a*hw`*g10Y8MNGxx&nP18GZuB{9 zgL+D9Zx)O7neSv%22$8_oY;VqQr0r>h&MMxO$9>X+=ISlGr#g@M$s94%X0SK>zWS3 zHMkz*s+I7jf{=I67~k$7@EYwAwx|DetaY-=-gvV1o_|Ma7hQ#rI%%NX*->3PZWQ)8y>eRiu+}u#*q_SLmBa+&`z5lVAO!@voHgi$I5#KyPe$o)+(lW{Y z-n+A;Uta^O_|VzVUSP+G{N>9*^#G;GU%RSyA|-1KWo&>V1+V+#0cy-$N{auVq@BfD ziJ6c7zE?p-$=HRbnat@G*I|0PkgXBhW}$ffJg!B{+QBV$WOqg}>_PNS!Wkud??=Zd<mw{sB7CAkewO ze1+;5dzgs-xmto^GS2B?{%hwKWJy~D>Y6jH(!vemf%-aUL4?4Y{6`y1I|*Ey}Ls@O|r z-REIY@q~sc(qC@Rud99_qNUz@+5F8cGHES3$J9d!+welpI`^@UWqssjv^EoB#at)~ zLoBwt#C@pm7=KXzC?tseV*Z8PA~nKGkvWjDpr~QHMuCV`_LY~K$!`zTm$f|hmbubR z)I;A#5~g?wzRzC%r7{Xo`6c4hjW-w89PZ}539E%%NG?L9Cfys)ogwF1!0wq`!+8(| zT2g}NTHs3u7+}WtlIqOIGQtV^bJU>boViXABR}Pp=4QXWZSP63B7By};{bhYlcp;c zS$|y3!MhEOn}UDpd(c#P%fcnXB@cFVd?g*^ptu@p;pTh4AkR6^RZ(eF#sN1{C=Ayj-(pSJa z)spK@sdvw)RNT>YHa)L6PMS8zt@~<2XzVF3i|91W+@r3Q&KHn_xWODc?;=L{{!+d; z`%_As{yf@C_-`Qf=r ziTal3c25KE*NA@LOt8V8Pn1Gr82>^*k{)+}av1hP46r;%e(L zDy0srR`Q!p2Al>~tqW|(){;Ch+ts@j;nG~Aoh{aa5`$E(Vg_nfPv~>&OGd%{^WB>y zyJx0;c~(z=prBlj;N^iH>S`P`+%fl$%mw$4!LA>#!zz~}z8FN^)KJb8vzbsoi}myV z@H(EG){ldJ)MbRpPzK{=@D}t_gPg_q)XfHimyO?(b{q3pz2kKY)CO%E=iF9t{6g7$ z5K%Qe)?iEOB;x4H*5_B`BeRN6E!TPUrJKuGl$jOMSJ#mIASj0Rdj3M88-^Sh&Vnq3 zs!nG0mvyCW>n`r*szB=JC3lNN4KZcQtQSDbUWJA{B5BY&W2S9Q>7rn9yg~Zc`7NNm zx{4Jp4u3*pZt6Rn)6-QnIebcDPnqqw&?V-=t=K2fq*E@IgiK+7sE@qWwW*-pSAdLT zKh_`i{!~G9J@V=6jy2!7m*(H=lyBUL<2zaDgZs#2Fwq(&^hCc% zex4jFHxt->*MYaEWwU*XD+`7k{Z(;;ClBn-7g^>1**MaI377NcAseY+4aI4um^e+B*6 z_-01S29vhz{*wX9B1Mf`jy2sN4W_oF$)&k=qPin+DC4mq1I~C0a$WRx%TKb(QF)xK zkd>{fihBsHW(2VL_>ZoHPT@@8zl5y?L@*}+uDrind=mWdp4?m;PWP=%)nk}8Pbt+6 zwq3i^@}s0VG9A<@>#AViasiaUQfK^j=O+iEu}CU_s*EHO|5gm32D2D1&`+J|5 z5aI5h)x72Wy*|lpgu3d*kp}N3wU4l3Xzj)BQPj>N@l64^I2e~VshaJ+== z{bBU_agWzbyyl`-g>Sa>s+x-yU4WWu(WS^QO3Q{TL+fV61GT8b;M##w`}jjV{jN^X zq?{IImeIc))p_g%c47V1WWvT032s{SvX<>X=o>J$DV zur3R0vJx!ro?fDdDrkyGN*ZF8_4Fsc%0dlzXw~=boU?D#J@$_ge9j`MatEZg)V%TA z=>cKfp2l*AyzWEJ3+0>GB>rt<^d_T?yiGZk9Xix|fTTI3sREsSN2~Uv($J2NB71$WK$D?D7 zw?dchaK{E^$L3^kl&5wsk5%BA-8nx4Og2U&S~GBUr9XG%4>Y+q2M?;fYSGd-!{v`P zH+Zq8pR=vy&Ww;vNKhCmRY_MGoVPL$;F zg~SE8J8NfvA8=*1->S5T)B07t^$~^ktfb2|vEya*S%E#k(4FM{K;q@ac*m~6A=8|# zD2BxP`1)Q~M!sPdj2OvSAYd$Zz~#k=^{RVjRQu(Q1kkgj!JyX23RpH1RLkHMGrUFn zu+J!syU>|>k}$$@kz>y++0qrL{hTe^?f#w2DZk7Jd?f4v%EmT2^_lf?Y>N#l5Pk#S zC#K+Rw~rXaP0akrK7Xs{x;tPSpfh<1S8D9v?A45dT-9>S)NpAIwVv^1cgIZ2<3^we z&X|eet%%O1PbJ}zn2Gf0!lloU{z_?tr9NsxK?F0-%xArO(iIv1Hq7>P+MGxcgQ|C) zi%>T-3AkAQ0;c8}ydc?~RO%Jq-kvYnA#y<2vhex&ALID_oOPo--z70d+FC;9_6H1~#2k`MBHaf7M0EL_&EDD6 z_)%=iU6MH?AXZKOyQD9b;^)!?QI-T~Zhp`LGR{{=?Yt&uc4$V4iqUq9LGJFv^BzuU z<)^&S{#ON-dR1=v1XFIdUmBbPvmOUj-qoqIs}U>(_>|s|E&tab52r#46}E`Z%Yq)dLAs+WGK9n*P$x-;1 z*-(4Fd11h~FDQ){&Mqn_dNyznVg^&viHDY+v6;_REo<`7qI}nSrgofoOd2zMm73;SE%+N0_7Kp#W5^j~p7Bhg>DTg6Y*zY<(q3*odrZ;P_UD7zkP z{q@O~e~#IQmfzzQkR_cKEdj{ymR?PJIP^l0K&s(8slBM^FgJV=11UJ zzAtV%LHj*vj&nije{!E;#`o~#OBL1{zCG>c8L@$Pd#U`X?x(QS62d6+9k2GpHeU|4~_FH?;*i% z1&c?2ksA*DRY<|cl!FYBkv8nV-#T<#@E?ATl(bLG16?z{L5gmzXDO%B&oA;lT!V9- zU(8g3tk)Z%nUjOlak|g**XVtZ+GI&z;FyIIRXmkjr*o(GaxY%L=8*}^v^Mr^py>Me{mKoUUEuH68_F|dq^@Aq z)ulr>7vo_kDJpjwpL3l>N#5^5 zRc;+qG2~#x!=$|>vCO8hrv<%)y3H-1o}O7LhiDou{o$zLpciNH?-v%PFw_hLDtP1J zIz~&$i0^0V3$*MBDMOf$zg*!PrGfk_SgyvJ819_J=?w%qA(*99HVekQ64r!rWP;YtdsCQjGbO_Fd)02o-gGM7~ z+_udb?yq7T@!Fh4{R4!`l7_z)jqZ-W_i@kvmN{cyK&W^ex07>Q;lQb|LZ_Z{klg&L zx+k+XdrzYhp0XMf4X4izy+WcStzcp2{4oQUxfmrAhR!;h; zTLZvBmJVWPA|cm9!;cpG-k%n=C4s9p=H_cX=35b)Xm@>-R9S{8C(=?cfAPU$fjG~j zgw;Bq9aArTqi@p&>a#1M0p*t5vSFPQ$)17?FE#JHrj(EQoyy*SvKVJ$nErL~UUEuG zdPX#LY$rR6>OojOCVEH$%TiU0`y}}s)tvq0PP({))+80TvCirX$&>IE)NSpz*~4-~ z!r2bzSM$mX(|7l}yb26wQz^o%1WI+jti4|0O#_3#ksA?(x)itVm+1b{mq1D3j~sZe zF`Ba@5oK%aq#uYRLaHGv+wcbPpUNJN`tVrV2tILvJ)!~0d+ZzFTG+2k30tm5_Xo{Z zu93F0X+9$nYz>{;UQ@zV_lAOdBpJ{}$5rQEk)`tN$cLPnhn%mspJkXwpi_zg&Sq+b zH8r;Oq7C>Ag@d-9#wUlyRxapG!a3=E8#$x-bPP;5V@v~oJteX#)Q=wcbs+{h>KC!t zgYaO#+qsh$h%S|8KaCTUh%StIyV|ScUAm&^X~xp(#3snDt5yiA$dQfUi=aMFOu!_H z8#-!Y{#~->b=AFb2oTpRW}Rh5i`|^(MF!b-j@z=72)D0}4W6#5KBnO;Lmfeb!ovxS zYlnDygTFNoRDH?NA{&9X&JM{c#AGkmCDe0I``$zj8G(;~QJ=*K-8K$8)MVPkTQkR5 z>BPGsECtiyG&^6`|BK)8pLx??FWz(yL0qMqUf};?R@mz!O*+)-bEG!+&+FNqyUQQt z{dQ#htj=dfk~D;eZPzjW5LLxj@dO@eex|ftFg74oyS!|mL^5-OC9QBEHaV1(xe&=aJV=K4S3rD9+PY|PJf-xzA!s-H;B-F zC=MUF0o>;~)U=bz~-7vb}C4tNOv*=18.0.0" @@ -1174,6 +1237,7 @@ "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "inBundle": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1182,6 +1246,7 @@ "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/chord": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@earendil-works/chord/-/chord-0.85.1.tgz", + "inBundle": true, "license": "MIT", "dependencies": { "esbuild": "0.28.1" @@ -1193,6 +1258,7 @@ "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.85.1.tgz", + "inBundle": true, "license": "MIT", "dependencies": { "@earendil-works/chord": "^0.85.1", @@ -1210,6 +1276,7 @@ "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.85.1.tgz", + "inBundle": true, "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.123.0", @@ -1233,6 +1300,7 @@ "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-telemetry": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.85.1.tgz", + "inBundle": true, "license": "MIT", "engines": { "node": ">=22.19.0" @@ -1241,6 +1309,7 @@ "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.85.1.tgz", + "inBundle": true, "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -1257,6 +1326,7 @@ "cpu": [ "ppc64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1273,6 +1343,7 @@ "cpu": [ "arm" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1289,6 +1360,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1305,6 +1377,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1321,6 +1394,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1337,6 +1411,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1353,6 +1428,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1369,6 +1445,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1385,6 +1462,7 @@ "cpu": [ "arm" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1401,6 +1479,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1417,6 +1496,7 @@ "cpu": [ "ia32" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1433,6 +1513,7 @@ "cpu": [ "loong64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1449,6 +1530,7 @@ "cpu": [ "mips64el" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1465,6 +1547,7 @@ "cpu": [ "ppc64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1481,6 +1564,7 @@ "cpu": [ "riscv64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1497,6 +1581,7 @@ "cpu": [ "s390x" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1513,6 +1598,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1529,6 +1615,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1545,6 +1632,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1561,6 +1649,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1577,6 +1666,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1593,6 +1683,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1609,6 +1700,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1625,6 +1717,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1641,6 +1734,7 @@ "cpu": [ "ia32" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1657,6 +1751,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1671,6 +1766,7 @@ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", "hasInstallScript": true, + "inBundle": true, "license": "Apache-2.0", "dependencies": { "google-auth-library": "^10.3.0", @@ -1694,6 +1790,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", + "inBundle": true, "license": "MIT", "optional": true, "engines": { @@ -1719,6 +1816,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1732,6 +1830,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1748,6 +1847,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1764,6 +1864,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1780,6 +1881,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1796,6 +1898,7 @@ "cpu": [ "riscv64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1812,6 +1915,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1828,6 +1932,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1844,6 +1949,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1860,6 +1966,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -1879,36 +1986,42 @@ "url": "https://github.com/sponsors/nodable" } ], + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "inBundle": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1" @@ -1918,36 +2031,42 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "inBundle": true, "license": "Apache-2.0" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { "version": "3.24.3", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", @@ -1962,6 +2081,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.24.3", @@ -1976,6 +2096,7 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.24.3", @@ -1990,6 +2111,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2002,6 +2124,7 @@ "version": "4.7.3", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.24.3", @@ -2016,6 +2139,7 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.24.3", @@ -2030,6 +2154,7 @@ "version": "4.14.2", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2042,6 +2167,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -2055,6 +2181,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -2068,12 +2195,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { "version": "22.19.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "inBundle": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2083,6 +2212,7 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "inBundle": true, "license": "MIT", "engines": { "node": ">= 14" @@ -2092,6 +2222,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "inBundle": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -2115,12 +2246,14 @@ "url": "https://feross.org/support" } ], + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "inBundle": true, "license": "MIT", "engines": { "node": "*" @@ -2130,12 +2263,14 @@ "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "inBundle": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -2148,12 +2283,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "inBundle": true, "license": "BSD-3-Clause" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "inBundle": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -2166,6 +2303,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "inBundle": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2180,6 +2318,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "inBundle": true, "license": "MIT", "engines": { "node": ">= 12" @@ -2189,6 +2328,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "inBundle": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2206,6 +2346,7 @@ "version": "8.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "inBundle": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -2215,6 +2356,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -2225,6 +2367,7 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, + "inBundle": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" @@ -2265,12 +2408,14 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-sha256": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "inBundle": true, "license": "Unlicense" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { @@ -2283,6 +2428,7 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], + "inBundle": true, "license": "MIT", "dependencies": { "path-expression-matcher": "^1.5.0", @@ -2299,6 +2445,7 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], + "inBundle": true, "license": "MIT", "dependencies": { "@nodable/entities": "^2.1.0", @@ -2324,6 +2471,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "inBundle": true, "license": "MIT", "dependencies": { "node-domexception": "^1.0.0", @@ -2337,6 +2485,7 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "inBundle": true, "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -2349,6 +2498,7 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -2363,6 +2513,7 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "gaxios": "^7.0.0", @@ -2377,6 +2528,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "inBundle": true, "license": "MIT", "engines": { "node": ">=18" @@ -2389,6 +2541,7 @@ "version": "10.6.2", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "inBundle": true, "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -2406,6 +2559,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "inBundle": true, "license": "Apache-2.0", "engines": { "node": ">=14" @@ -2415,12 +2569,14 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "inBundle": true, "license": "ISC" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/grok-mermaid": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz", "integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==", + "inBundle": true, "license": "Apache-2.0", "engines": { "node": ">=18" @@ -2430,6 +2586,7 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "inBundle": true, "license": "BSD-3-Clause", "engines": { "node": "*" @@ -2439,6 +2596,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "inBundle": true, "license": "ISC", "dependencies": { "lru-cache": "^11.1.0" @@ -2451,6 +2609,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "inBundle": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -2464,6 +2623,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "inBundle": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -2477,6 +2637,7 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "inBundle": true, "license": "MIT", "engines": { "node": ">= 4" @@ -2486,12 +2647,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "inBundle": true, "license": "ISC" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "inBundle": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -2501,6 +2664,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "inBundle": true, "license": "MIT", "dependencies": { "bignumber.js": "^9.0.0" @@ -2510,6 +2674,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "inBundle": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -2523,6 +2688,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "inBundle": true, "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -2534,6 +2700,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "inBundle": true, "license": "MIT", "dependencies": { "jwa": "^2.0.1", @@ -2544,12 +2711,14 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "inBundle": true, "license": "Apache-2.0" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { "version": "11.4.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", + "inBundle": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -2559,6 +2728,7 @@ "version": "18.0.5", "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "inBundle": true, "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -2571,6 +2741,7 @@ "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.5" @@ -2586,6 +2757,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { @@ -2603,6 +2775,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "inBundle": true, "license": "MIT", "engines": { "node": ">=10.5.0" @@ -2612,6 +2785,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "inBundle": true, "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -2630,6 +2804,7 @@ "version": "6.40.0", "resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz", "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==", + "inBundle": true, "license": "Apache-2.0", "peerDependencies": { "ws": "^8.18.0", @@ -2648,6 +2823,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "inBundle": true, "license": "MIT", "dependencies": { "@types/retry": "0.12.0", @@ -2661,12 +2837,14 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { @@ -2679,6 +2857,7 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], + "inBundle": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -2688,6 +2867,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "inBundle": true, "license": "MIT", "engines": { "node": ">=8" @@ -2697,6 +2877,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "inBundle": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2708,6 +2889,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "inBundle": true, "license": "MIT", "engines": { "node": ">= 4" @@ -2718,6 +2900,7 @@ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, + "inBundle": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -2740,6 +2923,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "inBundle": true, "license": "MIT", "engines": { "node": ">= 4" @@ -2763,12 +2947,14 @@ "url": "https://feross.org/support" } ], + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "inBundle": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2781,6 +2967,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "inBundle": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -2793,6 +2980,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "inBundle": true, "license": "MIT", "engines": { "node": ">=8" @@ -2802,12 +2990,14 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "inBundle": true, "license": "ISC" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/standardwebhooks": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "inBundle": true, "license": "MIT", "dependencies": { "@stablelib/base64": "^1.0.0", @@ -2824,30 +3014,35 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "inBundle": true, "license": "0BSD" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz", "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", + "inBundle": true, "license": "MIT", "engines": { "node": ">=22.19.0" @@ -2857,12 +3052,14 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "inBundle": true, "license": "MIT" }, "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "inBundle": true, "license": "MIT", "engines": { "node": ">= 8" @@ -2872,6 +3069,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "inBundle": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -2887,6 +3085,7 @@ "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "inBundle": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -2914,6 +3113,7 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], + "inBundle": true, "license": "MIT", "engines": { "node": ">=16.0.0" @@ -2923,6 +3123,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "inBundle": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -2938,6 +3139,7 @@ "version": "0.85.1", "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.85.1.tgz", "integrity": "sha512-Bg/YN6kA7Swja/NQxka8xFdecb4E/auIEGF2G5A25EaQXhRnPj300/7/KpgsDDMYUzHTDAv4RyUxaQPJKW81Rw==", + "inBundle": true, "license": "MIT", "peer": true, "engines": { @@ -2948,6 +3150,7 @@ "version": "0.85.1", "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.85.1.tgz", "integrity": "sha512-OIzw9efInmO4WOBnD4TxcTdBjmzvYJpzslkgoUro946nEGoYWg5rwv1p4fDt3/JvMx9QybryUCUwlm7j8Dreig==", + "inBundle": true, "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -2998,6 +3201,7 @@ "cpu": [ "ppc64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3014,6 +3218,7 @@ "cpu": [ "arm" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3030,6 +3235,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3046,6 +3252,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3062,6 +3269,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3078,6 +3286,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3094,6 +3303,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3110,6 +3320,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3126,6 +3337,7 @@ "cpu": [ "arm" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3142,6 +3354,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3158,6 +3371,7 @@ "cpu": [ "ia32" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3174,6 +3388,7 @@ "cpu": [ "loong64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3190,6 +3405,7 @@ "cpu": [ "mips64el" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3206,6 +3422,7 @@ "cpu": [ "ppc64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3222,6 +3439,7 @@ "cpu": [ "riscv64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3238,6 +3456,7 @@ "cpu": [ "s390x" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3254,6 +3473,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3270,6 +3490,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3286,6 +3507,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3302,6 +3524,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3318,6 +3541,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3334,6 +3558,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3350,6 +3575,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3366,6 +3592,7 @@ "cpu": [ "arm64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3382,6 +3609,7 @@ "cpu": [ "ia32" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3398,6 +3626,7 @@ "cpu": [ "x64" ], + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -3412,6 +3641,7 @@ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", "hasInstallScript": true, + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3472,6 +3702,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3479,6 +3710,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3486,6 +3718,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3493,6 +3726,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3500,6 +3734,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true, "dependencies": { @@ -3510,6 +3745,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3517,6 +3753,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3524,6 +3761,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3531,6 +3769,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -3802,6 +4041,7 @@ "version": "3.33.3", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3816,6 +4056,7 @@ "version": "4.5.2", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3831,6 +4072,7 @@ "version": "5.8.0", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3846,6 +4088,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3859,6 +4102,7 @@ "version": "4.7.3", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3874,6 +4118,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.3.tgz", "integrity": "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3889,6 +4134,7 @@ "version": "4.18.0", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.18.0.tgz", "integrity": "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3902,6 +4148,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3916,6 +4163,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -3930,6 +4178,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "inBundle": true, "license": "MIT", "peer": true }, @@ -3940,15 +4189,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@sting8k/pi-vcc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@sting8k/pi-vcc/-/pi-vcc-0.5.0.tgz", - "integrity": "sha512-KJbOVUFbyghn6h+RD9bDXFNWkKNqpxaCpPQWceOuxMPe9ySpbEfaYnqO9CZUiCP3AFmQ5Ghnsg2B8pdKgY+0Hg==", - "peerDependencies": { - "@earendil-works/pi-coding-agent": ">=0.74.0 <1.0.0", - "typebox": ">=1.1.24 <2.0.0" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -3989,6 +4229,7 @@ "version": "20.19.43", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "inBundle": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3998,6 +4239,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "inBundle": true, "license": "MIT", "peer": true }, @@ -4118,6 +4360,7 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "inBundle": true, "license": "MIT", "peer": true, "engines": { @@ -4152,6 +4395,7 @@ "url": "https://feross.org/support" } ], + "inBundle": true, "license": "MIT", "peer": true }, @@ -4159,6 +4403,7 @@ "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "inBundle": true, "license": "MIT", "peer": true, "engines": { @@ -4169,6 +4414,7 @@ "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "inBundle": true, "license": "MIT", "peer": true }, @@ -4176,6 +4422,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "inBundle": true, "license": "BSD-3-Clause", "peer": true }, @@ -4196,10 +4443,31 @@ "dev": true, "license": "MIT" }, + "node_modules/croner": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/croner/-/croner-10.0.1.tgz", + "integrity": "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==", + "funding": [ + { + "type": "other", + "url": "https://paypal.me/hexagonpp" + }, + { + "type": "github", + "url": "https://github.com/sponsors/hexagon" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "inBundle": true, "license": "MIT", "peer": true, "engines": { @@ -4210,6 +4478,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4238,6 +4507,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -4256,6 +4526,7 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "hasInstallScript": true, + "inBundle": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" @@ -4316,6 +4587,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "inBundle": true, "license": "MIT", "peer": true }, @@ -4323,6 +4595,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "inBundle": true, "license": "Unlicense", "peer": true }, @@ -4358,6 +4631,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4372,6 +4646,7 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4386,6 +4661,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "inBundle": true, "license": "MIT", "optional": true, "os": [ @@ -4399,6 +4675,7 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -4414,6 +4691,7 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -4429,6 +4707,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "inBundle": true, "license": "MIT", "engines": { "node": ">=18" @@ -4441,6 +4720,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -4459,6 +4739,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "engines": { @@ -4469,6 +4750,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4483,6 +4765,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4497,6 +4780,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4507,6 +4791,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4521,6 +4806,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4533,6 +4819,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4805,6 +5092,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "inBundle": true, "license": "Apache-2.0", "peer": true }, @@ -4822,6 +5110,7 @@ "version": "18.0.5", "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "inBundle": true, "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -4834,6 +5123,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "inBundle": true, "license": "MIT", "peer": true }, @@ -4871,6 +5161,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "inBundle": true, "license": "MIT", "peer": true, "engines": { @@ -4881,6 +5172,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4914,6 +5206,7 @@ "version": "6.40.0", "resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz", "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==", + "inBundle": true, "license": "Apache-2.0", "peer": true, "peerDependencies": { @@ -4933,6 +5226,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -4947,6 +5241,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "inBundle": true, "license": "MIT", "peer": true }, @@ -4961,6 +5256,7 @@ "version": "0.13.0", "resolved": "https://registry.npmjs.org/pi-intercom/-/pi-intercom-0.13.0.tgz", "integrity": "sha512-+QjKJRAEhrgQZj4+M9OW/8unRLvCzeCp0K66lZmbS5/me0fsClXRtANBgM6mY+EoX+Fcd+qBE8dTThp8+ND//g==", + "inBundle": true, "license": "MIT", "dependencies": { "tsx": "^4.20.0" @@ -4972,6 +5268,54 @@ "typebox": "*" } }, + "node_modules/pi-schedule-prompt": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pi-schedule-prompt/-/pi-schedule-prompt-0.4.1.tgz", + "integrity": "sha512-AcmMcffvFo7ibPJn66ML+vqYab8fqdW3n344+8Lh9rfqcDziYrIQqqRnb4ntDGc2iZ0hsE5pN5DRf6rdWzUd3Q==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "croner": "^10.0.1", + "nanoid": "^5.0.0" + }, + "peerDependencies": { + "@earendil-works/pi-ai": ">=0.74.0", + "@earendil-works/pi-coding-agent": ">=0.74.0", + "@earendil-works/pi-tui": ">=0.74.0" + } + }, + "node_modules/pi-schedule-prompt/node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "inBundle": true, + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/pi-subagents": { + "version": "2.9.0", + "resolved": "git+ssh://git@github.com/edxeth/pi-subagents.git#953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4", + "integrity": "sha512-fjzad/OiI0xA3E+iLb6aHB54NmBHId7fz7l3fLZVPl0oiBN4KOmR/gbpq0fjhv/VLqac9PsmxzjpGh/Sr237eA==", + "inBundle": true, + "license": "MIT", + "peerDependencies": { + "@earendil-works/pi-ai": ">=0.79.0", + "@earendil-works/pi-coding-agent": ">=0.85.0", + "@earendil-works/pi-tui": ">=0.79.0", + "typebox": "*" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5026,6 +5370,7 @@ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", "hasInstallScript": true, + "inBundle": true, "license": "BSD-3-Clause", "peer": true, "dependencies": { @@ -5049,6 +5394,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "inBundle": true, "license": "MIT", "peer": true, "engines": { @@ -5107,6 +5453,7 @@ "url": "https://feross.org/support" } ], + "inBundle": true, "license": "MIT", "peer": true }, @@ -5138,6 +5485,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.1.1.tgz", "integrity": "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ==", + "inBundle": true, "license": "MIT", "peer": true, "dependencies": { @@ -5200,6 +5548,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "inBundle": true, "license": "MIT", "peer": true }, @@ -5207,12 +5556,14 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "inBundle": true, "license": "0BSD" }, "node_modules/tsx": { "version": "4.23.13", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "inBundle": true, "license": "MIT", "dependencies": { "esbuild": "~0.28.0" @@ -5231,6 +5582,7 @@ "version": "1.3.7", "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz", "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==", + "inBundle": true, "license": "MIT" }, "node_modules/typescript": { @@ -5251,6 +5603,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "inBundle": true, "license": "MIT" }, "node_modules/vite": { @@ -5425,6 +5778,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "inBundle": true, "license": "MIT", "peer": true, "engines": { @@ -5452,6 +5806,7 @@ "version": "8.21.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "inBundle": true, "license": "MIT", "peer": true, "engines": { diff --git a/package.json b/package.json index 50c605f..7561972 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@wassname2/pi-goals", "version": "0.2.2", - "description": "Prototype: plan and supervise in the main chat with a visible edxeth Pi worker.", + "description": "Discuss a goal plan, then supervise a visible worker and verify its results.", "private": true, "author": "wassname", "license": "MIT", @@ -29,7 +29,7 @@ }, "files": [ "src", - "prototype", + "agents", "README.md" ], "publishConfig": { @@ -45,10 +45,6 @@ "lint": "biome check src/ test/", "lint:fix": "biome check --fix src/ test/" }, - "dependencies": { - "@sting8k/pi-vcc": "0.5.0", - "pi-intercom": "^0.13.0" - }, "devDependencies": { "@biomejs/biome": "^2.4.8", "@earendil-works/pi-coding-agent": "0.85.1", @@ -60,8 +56,24 @@ }, "pi": { "extensions": [ - "./src/prototype.ts" + "./src/index.ts", + "./node_modules/pi-subagents/src/index.ts", + "./node_modules/pi-intercom/index.ts", + "./node_modules/pi-schedule-prompt/src/index.ts" ], - "image": "https://cdn.jsdelivr.net/gh/wassname/pi-goals@main/media/screenshot.png" - } + "image": "https://github.com/user-attachments/assets/35feaa15-f022-4491-bcc2-fc31cb878a9f", + "skills": [ + "./node_modules/pi-intercom/skills" + ] + }, + "dependencies": { + "pi-subagents": "git+https://github.com/edxeth/pi-subagents.git#953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4", + "pi-intercom": "0.13.0", + "pi-schedule-prompt": "0.4.1" + }, + "bundledDependencies": [ + "pi-subagents", + "pi-intercom", + "pi-schedule-prompt" + ] } diff --git a/prototype/README.md b/prototype/README.md deleted file mode 100644 index 38ff833..0000000 --- a/prototype/README.md +++ /dev/null @@ -1,3 +0,0 @@ -See the [project README](../README.md) for use and recovery, and [`prepare.mjs`](prepare.mjs) for isolated test setup. - -— Pi/OpenAI diff --git a/prototype/capture-workers.mjs b/prototype/capture-workers.mjs deleted file mode 100644 index c5f513c..0000000 --- a/prototype/capture-workers.mjs +++ /dev/null @@ -1,28 +0,0 @@ -// Event-driven trial observer. Reads only child panes named by this trial's launch trace. -import {existsSync,readFileSync,writeFileSync,watch} from 'node:fs'; -import {execFile} from 'node:child_process'; -import {join} from 'node:path'; -const root=process.argv[2];if(!root)throw new Error('trial root required'); -const trace=join(root,'launch.jsonl');if(!existsSync(trace))writeFileSync(trace,''); -const seen=new Set();const watchers=[];let count=0; -const shutdown=()=>{for(const w of watchers)w.close();process.exit(count?0:1);}; -const deadline=setTimeout(shutdown,600000); -function observe(){ - for(const line of readFileSync(trace,'utf8').trim().split('\n')){ - let row;try{row=JSON.parse(line);}catch{continue;} - if(!['interactive.send','interactive.watch.start'].includes(row.event)||!row.sessionFile?.startsWith(root+'/agent/sessions/')||!row.surface)continue; - const pane=row.surface.replace(/^herdr:/,''); - if(seen.has(pane))continue; - seen.add(pane); - // Herdr IDs are opaque (p1A is valid); use the trace value, not a numeric-ID assumption. - row.id = row.id ?? pane.replaceAll(':','-'); - // Capture once Pi renders its model/footer, without sending input or taking focus. - execFile('herdr',['pane','wait-output',pane,'--regex','gpt-|Claude|Gemini','--timeout','45000','--lines','120'],{maxBuffer:1024*1024},(error,stdout,stderr)=>{ - writeFileSync(join(root,`worker-${row.id}-pane.txt`),stdout+stderr); - console.log(JSON.stringify({event:'worker-pane-captured',id:row.id,pane,ok:!error,path:join(root,`worker-${row.id}-pane.txt`)})); - count++; - if(count>=2){clearTimeout(deadline);shutdown();} - }); - } -} -watchers.push(watch(trace,observe));observe(); diff --git a/prototype/replay-smoke.mjs b/prototype/replay-smoke.mjs deleted file mode 100644 index 148599b..0000000 --- a/prototype/replay-smoke.mjs +++ /dev/null @@ -1,69 +0,0 @@ -// Pi/OpenAI: native Pi runtime against a further copy of an already-copied JSONL. -// Network, credentials and execution tools are disabled. No original session is opened. -import {copyFileSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync} from 'node:fs'; -import {createHash} from 'node:crypto'; -import {join, resolve} from 'node:path'; -import {tmpdir} from 'node:os'; -import {pathToFileURL} from 'node:url'; -import assert from 'node:assert/strict'; -import {Type} from 'typebox'; -import prototype from '../src/prototype.ts'; -const [snapshot, leaf, sdkRoot, compactionCopy] = process.argv.slice(2); -if (!snapshot || !leaf || !sdkRoot || !compactionCopy) throw new Error('Supply copied JSONL, branch leaf, installed SDK root, copied compaction package'); -if (!/copy/i.test(snapshot)) throw new Error('Supply the named copy, never a live session path'); -const hash = () => createHash('sha256').update(readFileSync(snapshot)).digest('hex'); -const beforeHash = hash(); -const cwd = mkdtempSync(join(tmpdir(),'goals-prototype-replay-')); -const file = join(cwd,'session-copy.jsonl'); copyFileSync(snapshot,file); -const sdk = await import(pathToFileURL(join(sdkRoot,'dist/index.js')).href); -const {rewriteResponsesPayloadWithNativeReplay} = await import(pathToFileURL(resolve(compactionCopy,'src/payload-rewrite.ts')).href); -const {serializeMessagesToResponsesInput} = await import(pathToFileURL(resolve(compactionCopy,'src/serializer.ts')).href); -const sm = sdk.SessionManager.open(file); sm.branch(leaf); -const plan = join(cwd,'plan.md'); writeFileSync(plan,'# Copy-only plan\n- [ ] goal: inspect copied history only\n\n## Log\n'); -sm.appendCustomEntry('pi-goals-main-supervisor-v1',{mode:'planning',plan,signoffs:{}}); -let context; const commands = new Map(); const compactHooks=[]; let contextHooks=0; let requests=0; let network=0; -const realFetch=globalThis.fetch;globalThis.fetch=async()=>{network++;throw new Error('Network forbidden');}; -let session; -try { - sdk.initTheme('dark',false); - const settings=sdk.SettingsManager.inMemory({compaction:{enabled:false},retry:{enabled:false}}); - const agentDir=join(cwd,'agent');mkdirSync(agentDir); - const runtime=await sdk.ModelRuntime.create({authPath:join(agentDir,'no-auth.json'),modelsPath:join(agentDir,'no-models.json'),modelsStorePath:join(agentDir,'no-store.json'),allowModelNetwork:false}); - await runtime.setRuntimeApiKey('github-copilot','synthetic-no-network'); - const model={provider:'github-copilot',api:'openai-responses',id:'gpt-6-astra',baseUrl:'https://api.enterprise.githubcopilot.com',reasoning:true,input:['text','image'],contextWindow:400000,maxTokens:32768,cost:{input:0,output:0,cacheRead:0,cacheWrite:0}}; - const loader=new sdk.DefaultResourceLoader({cwd,agentDir,settingsManager:settings,noExtensions:true,noSkills:true,noThemes:true,noPromptTemplates:true,agentsFilesOverride:()=>({agentsFiles:[]}),extensionFactories:[(pi)=>{ - const wrapped=new Proxy(pi,{get(target,key){ - if(key==='registerCommand')return (name,definition)=>{commands.set(name,definition);return target.registerCommand(name,definition);}; - if(key==='on')return (event,handler)=>{if(event==='context')contextHooks++;if(event==='session_compact')compactHooks.push(handler);return target.on(event,handler);}; - return Reflect.get(target,key); - }}); - prototype(wrapped); - // Schema-only edxeth stand-ins: launching any child is forbidden in the replay check. - for(const [name,parameters] of [['subagent',Type.Object({agent:Type.String(),title:Type.String()})],['subagent_resume',Type.Object({sessionFile:Type.String()})],['subagent_kill',Type.Object({id:Type.String()})]]) pi.registerTool({name,label:name,description:'Do not execute',parameters,execute:async()=>{throw new Error('Delegation forbidden in replay check');}}); - pi.on('session_start',(_e,ctx)=>{context=ctx;}); - }]}); - await loader.reload();assert.equal(loader.getExtensions().errors.length,0); - ({session}=await sdk.createAgentSession({cwd,agentDir,sessionManager:sm,settingsManager:settings,resourceLoader:loader,modelRuntime:runtime,model,noTools:true})); - await session.bindExtensions({onError:e=>{throw new Error(JSON.stringify(e));}}); - assert.equal(context.cwd,cwd);assert.equal(contextHooks,0); - session.agent.streamFunction=async(_model,ctx)=>{ - requests++; - assert.ok(ctx.systemPrompt.includes(requests===1?'Plan only.':'You are the goal supervisor in the main chat.')); - if(requests>1)assert.ok(!ctx.systemPrompt.includes('Plan only.')); - const input=serializeMessagesToResponsesInput(model,ctx.messages); - const checkpoint=sm.getBranch().findLast(e=>e.type==='compaction'); - const replay=rewriteResponsesPayloadWithNativeReplay({model,payload:{model:model.id,instructions:ctx.systemPrompt,input},branchEntries:sm.getBranch(),compactionEntry:checkpoint}); - assert.equal(replay.ok,true,`Copied replay rejected: ${replay.reason}`); - const m={role:'assistant',content:[{type:'text',text:'COPY_ONLY_OK'}],provider:model.provider,model:model.id,api:model.api,stopReason:'stop',timestamp:Date.now(),usage:{input:1,output:1,cacheRead:0,cacheWrite:0,totalTokens:2,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}}}; - return {async *[Symbol.asyncIterator](){yield {type:'done',reason:'stop',message:m};},result:async()=>m}; - }; - await session.prompt('Copy-only planning check. No work; reply COPY_ONLY_OK.'); - const settled=new Promise((resolve,reject)=>{const timeout=setTimeout(()=>reject(new Error('Ready did not settle')),10000);const unsub=session.subscribe(e=>{if(e.type==='agent_settled'){clearTimeout(timeout);unsub();resolve();}});}); - await commands.get('goals').handler('ready',context); - await settled; - // A notification does not compact, rewrite history or start another request. - for(const hook of compactHooks)await hook({},context); - await session.prompt('Copy-only post-compaction-notice check. No work; reply COPY_ONLY_OK.'); - assert.equal(requests,3);assert.equal(network,0);assert.equal(hash(),beforeHash); - console.log(JSON.stringify({passed:true,requests,network,contextHooks,readyRole:'supervising',snapshotUnchanged:true,scope:'Actual Pi SDK + copied native checkpoint, deterministic model; compaction notification simulated'})); -} finally {session?.dispose();globalThis.fetch=realFetch;rmSync(cwd,{recursive:true,force:true});} diff --git a/prototype/summarize-trial.mjs b/prototype/summarize-trial.mjs deleted file mode 100644 index 20b3140..0000000 --- a/prototype/summarize-trial.mjs +++ /dev/null @@ -1,25 +0,0 @@ -// Sanitized, task-local evidence. Never copy auth, provider response IDs, or raw private sessions. -import {existsSync,mkdirSync,readFileSync,readdirSync,writeFileSync,copyFileSync} from 'node:fs'; -import {join,dirname} from 'node:path'; -const [root,output]=process.argv.slice(2);if(!root||!output)throw new Error('trial root and evidence directory required'); -const sessionsRoot=join(root,'agent/sessions'); -const summaries=[]; -for(const name of readdirSync(sessionsRoot,{recursive:true}).filter(p=>p.endsWith('.jsonl'))){ - const file=join(sessionsRoot,name);const entries=readFileSync(file,'utf8').split('\n').filter(Boolean).map(JSON.parse); - const state=entries.findLast(e=>e.type==='custom'&&e.customType==='pi-goals-main-supervisor-v1')?.data; - const launch=entries.find(e=>e.type==='custom'&&e.customType==='pi-subagents_launch_metadata')?.data; - const results=entries.filter(e=>e.type==='message'&&e.message.role==='toolResult').map(e=>({tool:e.message.toolName,isError:!!e.message.isError,...(['subagent','subagent_resume'].includes(e.message.toolName)?{id:e.message.details?.id,status:e.message.details?.status,mode:e.message.details?.mode,sessionFile:e.message.details?.sessionFile}:{}),...(['CompleteGoal','subagent','subagent_resume'].includes(e.message.toolName)?{text:e.message.content.filter(b=>b.type==='text').map(b=>b.text).join('\n')}: {})})); - const writes=entries.filter(e=>e.type==='message'&&e.message.role==='assistant').flatMap(e=>(e.message.content??[]).filter(b=>b.type==='toolCall'&&['write','edit'].includes(b.name)).map(b=>({tool:b.name,path:b.arguments.path??b.arguments.file_path}))); - const final=entries.filter(e=>e.type==='message'&&e.message.role==='assistant').at(-1)?.message.content.filter(b=>b.type==='text').map(b=>b.text).join('\n'); - summaries.push({file,sessionId:entries.find(e=>e.type==='session')?.id,state,launch:launch?{mode:launch.mode,sessionMode:launch.sessionMode,parentClosePolicy:launch.parentClosePolicy,model:launch.model,extensions:launch.extensions,tools:launch.tools,skills:launch.skills}:undefined,results,writes,final}); -} -mkdirSync(output,{recursive:true});writeFileSync(join(output,'sessions-summary.json'),JSON.stringify(summaries,null,2)); -const project=join(root,'project'); -for(const file of ['greeting.txt','count.mjs','.gitignore'])if(existsSync(join(project,file)))copyFileSync(join(project,file),join(output,file)); -const evidence=join(project,'evidence'); -if(existsSync(evidence))for(const name of readdirSync(evidence,{recursive:true})){ - if(name.split('/').includes('fixtures')||! /\.(txt|json|mjs|log|stdout|stderr|exit)$/.test(name))continue; - const src=join(evidence,name);const out=join(output,'verification',name);mkdirSync(dirname(out),{recursive:true});copyFileSync(src,out); -} -for(const item of summaries.filter(s=>s.state?.plan&&!s.state.child))if(existsSync(item.state.plan))copyFileSync(item.state.plan,join(output,'plan.md')); -console.log(JSON.stringify({sessions:summaries.length,parentSignedOff:summaries.filter(s=>!s.state?.child).map(s=>Object.keys(s.state?.signoffs??{}).length),output})); diff --git a/scripts/check-judge-footprint.sh b/scripts/check-judge-footprint.sh deleted file mode 100755 index 940e6d0..0000000 --- a/scripts/check-judge-footprint.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# Replaces the stale FIXME(side-effect) claim in src/index.ts with a checked fact. -# -# The claim was: "pi -p --no-session clones the repo into the PARENT of cwd, leaving a stale -# directory." Reproducing the exact sign-off judge invocation (pi --mode json -p --no-session, -# read-only tools, edit/write excluded, cwd = here) shows it does not. This script makes that -# reproducible: it runs the invocation, requires pi to actually reach agent_end (so a pass is not -# vacuous), and asserts the parent-of-cwd listing is byte-identical before and after. -# -# Exit 0 = judge leaves no clone in the parent. Exit 1 = either pi did not run, or it polluted. -# Run by hand; re-run as the rigorous sign-off check (the judge has bash and runs this itself). -set -u - -PARENT="$(cd "$PWD/.." && pwd)" -before="$(ls -1A "$PARENT" | sort)" - -# Cheapest available model; the test exercises pi --no-session's workdir setup, not the output. -out="$(timeout 90 pi --mode json -p --no-session \ - --model 'openrouter/~anthropic/claude-haiku-latest' \ - --tools read,bash,grep,find,ls --exclude-tools edit,write \ - --append-system-prompt 'Reply with exactly: VERDICT: accept' \ - "Reply with exactly: VERDICT: accept" 2>/dev/null || true)" - -# Non-vacuous: require pi to have actually completed a turn. A pass without this could mean pi -# crashed instantly and never had the chance to clone -- which would prove nothing. -if ! printf '%s' "$out" | grep -q '"type":"agent_end"'; then - echo "FAIL: pi --no-session did not reach agent_end; cannot confirm no-clone." - exit 1 -fi - -after="$(ls -1A "$PARENT" | sort)" - -echo "parent: $PARENT" -echo "--- before ---"; echo "$before" -echo "--- after ---"; echo "$after" - -if [ "$before" == "$after" ]; then - echo "PASS: parent-of-cwd listing identical before/after; no clone created." - exit 0 -fi -echo "FAIL: parent-of-cwd listing changed. Diff (< before, > after):" -diff <(printf '%s\n' "$before") <(printf '%s\n' "$after") | head -20 -exit 1 diff --git a/scripts/check-stale-fixmes.sh b/scripts/check-stale-fixmes.sh deleted file mode 100755 index 8111d3a..0000000 --- a/scripts/check-stale-fixmes.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# Structural gate for the goal's `verify:` field. Cheap and deterministic: no API calls. -# Confirms (a) neither stale FIXME tag remains in src/, (b) the footprint script exists, and -# (c) the plan-injection heading prefix is still emitted. The rigorous runtime check (running -# the footprint script) is the sign-off judge's job -- it has bash and re-runs the script itself. -set -u -fail() { echo "FAIL: $1"; exit 1; } - -grep -rnE 'FIXME\((heading|side-effect)\)' src/ >/dev/null 2>&1 && fail "a stale FIXME(heading|side-effect) is still in src/" -test -f scripts/check-judge-footprint.sh || fail "scripts/check-judge-footprint.sh is missing" -grep -q '\.pi/goals\.md:' src/prompts.ts || fail "the .pi/goals.md: heading prefix was dropped from src/prompts.ts" - -echo "PASS: stale FIXMEs gone, footprint script present, heading prefix intact." -exit 0 diff --git a/scripts/inconclusive-fail-forward.diff b/scripts/inconclusive-fail-forward.diff deleted file mode 100644 index dfc6091..0000000 --- a/scripts/inconclusive-fail-forward.diff +++ /dev/null @@ -1,104 +0,0 @@ -diff --git a/README.md b/README.md -index ce4056a..5485002 100644 ---- a/README.md -+++ b/README.md -@@ -145,9 +145,9 @@ else is the agent editing the file. It reads the goal's `evidence:` block from ` - reasoning comes back in the result. - - The judge defaults to the current session model and streams partial output while it runs. If the --current model is not visible to the extension, `CompleteGoal` does not fall back to Pi's implicit --default; it signs off as `judge inconclusive` and tells you to set `/goals judge `. --Point it at another model for an independent cross-family check. -+session model is not visible to the extension, the `--model` flag is omitted and pi uses its own -+configured default, so the judge always runs. `/goals judge ` is an optional override -+for an independent cross-family check; never required. - - ## Prompts - -diff --git a/src/index.ts b/src/index.ts -index 9eb2a16..14784f7 100644 ---- a/src/index.ts -+++ b/src/index.ts -@@ -326,7 +326,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void { - durationMs, - verifyCommand: goal.verify ?? undefined, - verifyExitCode: outcome.kind === "verify_failed" ? outcome.exitCode : undefined, -- judgeModel: judgeModel ?? "no explicit judge model", -+ judgeModel: judgeModel ?? "pi default", - reasoning, - isError: res.isError, - }; -@@ -522,14 +522,6 @@ async function decideSignOff( - }; - } - } -- if (!judgeModel) { -- const reason = "no explicit judge model available; set /goals judge "; -- return { -- outcome: { kind: "accepted_inconclusive", reason }, -- reasoning: `VERDICT: inconclusive\nreason: ${reason}`, -- durationMs: Date.now() - startedAt, -- }; -- } - const verdict = await runJudge(goal, evidence, paths, verifyResult, judgeModel, cwd, signal, onUpdate); - const outcome: SignOff = - verdict.kind === "accepted" -@@ -573,13 +565,25 @@ type JudgeResult = - | { kind: "rejected"; missing: string; reasoning: string; durationMs: number } - | { kind: "inconclusive"; reason: string; reasoning: string; durationMs: number }; - -+/** Stage 2: a read-only pi subprocess inspects the evidence against the repo and returns a verdict. */ -+/** Build the pi argv for the read-only judge. `--model` is omitted when no explicit/session model is -+ * set, so pi falls back to its configured default -- the judge always runs, never pre-emptively -+ * fails as "no model". Exported for a unit test that locks this invariant (an empty `--model ""` -+ * would make every sign-off silently inconclusive). */ -+export function buildJudgeArgs(judgeModel: string | null): string[] { -+ const args = ["--mode", "json", "-p", "--no-session"]; -+ if (judgeModel) args.push("--model", judgeModel); -+ args.push("--tools", JUDGE_TOOLS.join(","), "--exclude-tools", JUDGE_BLOCKED_TOOLS.join(","), "--append-system-prompt", evidenceJudgeSystem); -+ return args; -+} -+ - /** Stage 2: a read-only pi subprocess inspects the evidence against the repo and returns a verdict. */ - async function runJudge( - goal: Goal, - evidence: string, - paths: string[], - verifyResult: { command: string; exitCode: number; outputTail: string } | null, -- judgeModel: string, -+ judgeModel: string | null, - cwd: string, - signal: AbortSignal | undefined, - onUpdate?: (partial: { content: Array<{ type: "text"; text: string }>; details: SignOffDetails }) => void, -@@ -600,14 +604,14 @@ async function runJudge( - evidence, - paths, - }); -- const args = ["--mode", "json", "-p", "--no-session", "--model", judgeModel, "--tools", JUDGE_TOOLS.join(","), "--exclude-tools", JUDGE_BLOCKED_TOOLS.join(","), "--append-system-prompt", evidenceJudgeSystem]; -+ const args = buildJudgeArgs(judgeModel); - args.push(task); - - emit("spawning", `Spawning read-only judge for: ${goal.subject}`); - const inv = getPiInvocation(args); -- // FIXME(side-effect): pi -p --no-session clones the repo into the PARENT of cwd (so alongside -- // the working dir), leaving a stale directory. The judge should run in a temp dir or inside the -- // existing repo checkout so it doesn't pollute the user's workspace. -+ // The judge runs in-place against this checkout (cwd is passed to spawn and the read-only tools -+ // read from it); pi --no-session does not clone into the parent. Proven and re-checked by -+ // scripts/check-judge-footprint.sh, which reproduces this invocation and asserts no parent clone. - const judge = await new Promise<{ output: string; error?: string; aborted?: boolean }>((resolve) => { - let settled = false; - let stdoutBuffer = ""; -diff --git a/src/prompts.ts b/src/prompts.ts -index 03faea8..3b8d270 100644 ---- a/src/prompts.ts -+++ b/src/prompts.ts -@@ -117,8 +117,6 @@ export function planInjection(p: { - counts: { done: number; open: number }; - }): string { - if (!p.activeGoal) { -- // FIXME(heading): user wants the heading to show ".pi/goals.md: " so the filename is explicit -- // even in the injection. Currently says "Goals (goals.md):" which is close but not the same. - return `.pi/goals.md: ${p.title}\nNo active goal. ${p.counts.open} open, ${p.counts.done} done. Pick the next goal (set its checkbox to [/]) or run /goals.`; - } - const subtasks = p.activeGoal.openSubtasks.length diff --git a/prototype/prepare.mjs b/scripts/prepare-trial.mjs similarity index 94% rename from prototype/prepare.mjs rename to scripts/prepare-trial.mjs index a80a813..e2d92d2 100644 --- a/prototype/prepare.mjs +++ b/scripts/prepare-trial.mjs @@ -8,7 +8,7 @@ const repo = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const edxeth = process.argv[2]; const sdkRoot = process.argv[3]; const noSandbox = process.argv.includes('--no-sandbox'); -if (!edxeth || !sdkRoot) throw new Error('Usage: node prototype/prepare.mjs EDXETH_CHECKOUT INSTALLED_PI_ROOT'); +if (!edxeth || !sdkRoot) throw new Error('Usage: node scripts/prepare-trial.mjs EDXETH_CHECKOUT INSTALLED_PI_ROOT'); const revision = execFileSync('git', ['-C', edxeth, 'rev-parse', 'HEAD'], {encoding:'utf8'}).trim(); if (revision !== '953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4') throw new Error('Unreviewed edxeth revision; inspect and update the pin explicitly'); const root = mkdtempSync(join(tmpdir(), 'goals-edxeth-trial-')); @@ -25,7 +25,7 @@ for (const p of retained) if (!p.installedPath) throw new Error(`Missing install writeFileSync(join(agentDir,'settings.json'), JSON.stringify({...sourceSettings, packages:[...retained.map((p)=>p.installedPath), edxeth, repo]},null,2)); // Private copies, not symlinks: a trial OAuth refresh must not write the active auth file. for (const file of ['auth.json','models.json']) if (existsSync(join(sourceAgent,file))) copyFileSync(join(sourceAgent,file),join(agentDir,file)); -const workerDefinition = readFileSync(join(repo,'prototype/agents/goals-worker.md'),'utf8'); +const workerDefinition = readFileSync(join(repo,'agents/goals-worker.md'),'utf8'); writeFileSync(join(agentDir,'agents/goals-worker.md'), noSandbox ? workerDefinition.replace('mode: interactive', 'mode: interactive\nflags: --no-sandbox') : workerDefinition); execFileSync('git',['init','--quiet',cwd]); writeFileSync(join(cwd,'AGENTS.md'), 'Isolated functional trial. Work only in this project. Do not operate other Herdr panes, use live research sessions, or change global settings. Preserve evidence. The main chat supervises; the goals-worker implements.\n'); diff --git a/scripts/session-usage.mjs b/scripts/session-usage.mjs new file mode 100644 index 0000000..52d5ab8 --- /dev/null +++ b/scripts/session-usage.mjs @@ -0,0 +1,61 @@ +// Pi/OpenAI: Sum recorded requests, not context occupancy; do not read message text. +import { createHash } from 'node:crypto'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export function readSession(file) { + const raw = readFileSync(file, 'utf8'); + const lines = raw.split('\n'); + const tail = lines.pop(); + let trailingPartial = false; + if (tail) { + try { JSON.parse(tail); lines.push(tail); } + catch { trailingPartial = true; } + } + return { file: resolve(file), sha256: createHash('sha256').update(raw).digest('hex'), trailingPartial, + entries: lines.filter(Boolean).map(JSON.parse) }; +} + +export function summarize(entries, since, until) { + const start = Date.parse(since), end = Date.parse(until); + if (!Number.isFinite(start) || !Number.isFinite(end) || start > end) throw new Error('Invalid time interval'); + const rows = entries.filter(e => e.type === 'message' && e.message.role === 'assistant' && Date.parse(e.timestamp) >= start && Date.parse(e.timestamp) <= end); + const totals = { calls: 0, input: 0, cacheRead: 0, cacheWrite: 0, output: 0, totalTokens: 0 }; + const models = new Map(); + let missingUsage = 0; + for (const e of rows) { + const m = e.message; + if (!m.usage) { missingUsage++; continue; } + const model = `${m.provider}/${m.model}`; + if (!models.has(model)) models.set(model, { model, ...totals, calls: 0, input: 0, cacheRead: 0, cacheWrite: 0, output: 0, totalTokens: 0 }); + const group = models.get(model); + totals.calls++; group.calls++; + for (const key of ['input', 'cacheRead', 'cacheWrite', 'output', 'totalTokens']) { + const value = m.usage[key]; + if (!Number.isFinite(value) || value < 0) throw new Error(`Invalid usage.${key} in entry ${e.id}`); + totals[key] += value; group[key] += value; + } + } + return { ...totals, missingUsage, firstRequest: rows[0]?.timestamp ?? null, + lastRequest: rows.at(-1)?.timestamp ?? null, models: [...models.values()] }; +} + +export function report(supervisor, worker, until = new Date().toISOString()) { + const boundary = supervisor.entries.findLast(e => e.type === 'custom' && e.customType === 'pi-goals-main-supervisor-v1' && e.data.mode === 'planning' && !e.data.child); + if (!boundary) throw new Error('No recorded planning start in supervisor session'); + const since = boundary.timestamp; + const sessions = [supervisor, worker].map((session, i) => ({ + role: i === 0 ? 'supervisor' : 'worker', file: session.file, sha256: session.sha256, + trailingPartial: session.trailingPartial, ...summarize(session.entries, since, until), + })); + return { since, until, elapsedHours: (Date.parse(until) - Date.parse(since)) / 3600000, + boundaryEntry: boundary.id, plan: boundary.data.plan, sessions, + scope: 'Recorded assistant usage since latest planning entry, including abandoned branches and repeated cached context. Excludes earlier inherited history, in-flight requests, subprocess API usage and unrecorded compaction calls. Output includes reasoning where the provider includes it; reasoning is not added twice.' }; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + const [supervisor, worker] = process.argv.slice(2); + if (!supervisor || !worker || process.argv.length !== 4) throw new Error('Usage: node scripts/session-usage.mjs SUPERVISOR.jsonl WORKER.jsonl'); + console.log(JSON.stringify(report(readSession(supervisor), readSession(worker)), null, 2)); +} diff --git a/scripts/stale-fixme-removal.diff b/scripts/stale-fixme-removal.diff deleted file mode 100644 index 10f6795..0000000 --- a/scripts/stale-fixme-removal.diff +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/src/index.ts b/src/index.ts -index 9eb2a16..cdd7b45 100644 ---- a/src/index.ts -+++ b/src/index.ts -@@ -605,9 +605,9 @@ async function runJudge( - - emit("spawning", `Spawning read-only judge for: ${goal.subject}`); - const inv = getPiInvocation(args); -- // FIXME(side-effect): pi -p --no-session clones the repo into the PARENT of cwd (so alongside -- // the working dir), leaving a stale directory. The judge should run in a temp dir or inside the -- // existing repo checkout so it doesn't pollute the user's workspace. -+ // The judge runs in-place against this checkout (cwd is passed to spawn and the read-only tools -+ // read from it); pi --no-session does not clone into the parent. Proven and re-checked by -+ // scripts/check-judge-footprint.sh, which reproduces this invocation and asserts no parent clone. - const judge = await new Promise<{ output: string; error?: string; aborted?: boolean }>((resolve) => { - let settled = false; - let stdoutBuffer = ""; -diff --git a/src/prompts.ts b/src/prompts.ts -index 03faea8..3b8d270 100644 ---- a/src/prompts.ts -+++ b/src/prompts.ts -@@ -117,8 +117,6 @@ export function planInjection(p: { - counts: { done: number; open: number }; - }): string { - if (!p.activeGoal) { -- // FIXME(heading): user wants the heading to show ".pi/goals.md: <title>" so the filename is explicit -- // even in the injection. Currently says "Goals (goals.md):" which is close but not the same. - return `.pi/goals.md: ${p.title}\nNo active goal. ${p.counts.open} open, ${p.counts.done} done. Pick the next goal (set its checkbox to [/]) or run /goals.`; - } - const subtasks = p.activeGoal.openSubtasks.length diff --git a/slop/audits/20260905_goal-steward-validation.md b/slop/audits/20260905_goal-steward-validation.md deleted file mode 100644 index 8186421..0000000 --- a/slop/audits/20260905_goal-steward-validation.md +++ /dev/null @@ -1,31 +0,0 @@ -# Goal steward validation - -## Observations - -- Unit, flow, type, and lint checks passed. [`20260905_validation.log`](20260905_validation.log) says: - - > Test Files 8 passed (8) - > Tests 36 passed (36) - > Checked 12 files in 14ms. No fixes applied. - -- A real Pi 0.85.0 process loaded pi-subagents 0.65.1, pi-goals, and a runtime `goal-steward` agent. It spawned one review and resumed that run for sign-off. [`20260905_steward-probe.json`](20260905_steward-probe.json) records two distinct run IDs: - - > "runId": "4e9dc0c0-385b-4eb9-a060-ced7dc7cb6cc" - - > "runId": "f6115c82-31de-499f-ab78-145dde0c51c0" - -- The second review recalled a token that appeared only in the first review request. This is direct evidence that resume retained the steward conversation: - - > "Persistence lineage token: amber-731." - -- The sign-off review read `report.txt` and accepted the evidence: - - > "file exists and contains exactly 'PROBE_PASS' as required. Failure mode (empty report) is ruled out." - -## Test environment finding - -The repository's older local Pi 0.84.1 install could not launch a pi-subagents background child because it did not include `@earendil-works/chord` and `@earendil-works/pi-server`. The successful probe used an isolated npm install of Pi 0.85.0. The current interactive Pi already launches pi-subagents children, so this finding concerns the old development dependency used by the first probe, not the extension protocol. - -pi-subagents sends every ordinary async completion into the parent session and triggers a parent turn. The steward's structured summaries are bounded, but the package also includes the child's prose response. There is no public silent-completion option in pi-subagents 0.65.1. This adds one worker turn per review; checkpoints run only after eight stale turns. - -— Pi/Codex diff --git a/slop/audits/20260905_nested-supervisor-validation.txt b/slop/audits/20260905_nested-supervisor-validation.txt deleted file mode 100644 index da936e3..0000000 --- a/slop/audits/20260905_nested-supervisor-validation.txt +++ /dev/null @@ -1,91 +0,0 @@ -# Nested supervisor validation -2026-09-05T19:31:55+08:00 - -$ npm test - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 9 passed (9) - Tests 43 passed (43) - Start at 19:31:56 - Duration 1.60s (transform 709ms, setup 0ms, import 1.64s, tests 1.76s, environment 1ms) - - -$ npm run typecheck - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -$ npm run lint - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 15 files in 29ms. No fixes applied. - -$ git diff --check -(no output) - -$ npm pack --dry-run -npm notice -npm notice 📦 @wassname2/pi-goals@0.2.2 -npm notice Tarball Contents -npm notice 5.8kB README.md -npm notice 1.1kB agents/goal-worker.md -npm notice 1.5kB package.json -npm notice 4.0kB src/approval.ts -npm notice 34.6kB src/index.ts -npm notice 13.6kB src/prompts.ts -npm notice 5.9kB src/supervisor-runtime.ts -npm notice 7.3kB src/worker.ts -npm notice Tarball Details -npm notice name: @wassname2/pi-goals -npm notice version: 0.2.2 -npm notice filename: wassname2-pi-goals-0.2.2.tgz -npm notice package size: 23.1 kB -npm notice unpacked size: 73.7 kB -npm notice shasum: 579debe3de67b56116e51da6cac46c14511bdd07 -npm notice integrity: sha512-f5S39K2J3kjIX[...]cuwx2WFIeBBAQ== -npm notice total files: 8 -npm notice -wassname2-pi-goals-0.2.2.tgz - -$ git diff --stat 2852432 - README.md | 13 +- - agents/goal-worker.md | 2 +- - .../20260905_nested-supervisor-validation.txt | 70 ++++------ - src/approval.ts | 27 +++- - src/index.ts | 153 +++++++++++++++------ - src/prompts.ts | 9 +- - src/supervisor-runtime.ts | 81 ++++++++--- - src/worker.ts | 36 +++-- - test/goals-flow.test.ts | 54 +++++++- - test/prompts.test.ts | 2 +- - test/supervisor-runtime.test.ts | 32 ++++- - test/worker.test.ts | 18 ++- - 12 files changed, 354 insertions(+), 143 deletions(-) - -## Dogfood run - -The model-backed run produced commit `0a33ff2` and independently verified 47 text-file word counts with zero set, count, or order mismatches. Approval then deadlocked: - -> Cannot approve while the retained worker is pending. - -The worker process was terminal, but its model result was `Request was aborted`; the completion event did not clear retained state. A supervisor resume also failed because `subagent_supervisor` was unavailable in its strict tool list. - -Usage from the run status files: - -| agent | turns | new tokens | cached reads | reported cost | -| --- | ---: | ---: | ---: | ---: | -| supervisor, including recovery | 42 | 169,288 | 2,670,336 | $2.35 | -| worker | 17 | 67,803 | 812,544 | $0.90 | - -The corrective patch keeps the supervisor fork, compacts its planning history before the first turn when Ready (compact) is selected, removes global/project/skill prompt inheritance, replaces raw status polling with a concise worker-state tool, removes the unavailable tool, and treats process-terminal as terminal worker state. Unit tests pass; a second model-backed run is still required. - --- PI[gpt-5.6-sol] diff --git a/slop/audits/20260905_pi-goals-file-types.txt b/slop/audits/20260905_pi-goals-file-types.txt deleted file mode 100644 index db5794f..0000000 --- a/slop/audits/20260905_pi-goals-file-types.txt +++ /dev/null @@ -1,52 +0,0 @@ -text/plain .gitignore -text/plain AGENTS.md -text/plain ARCHIVED.md -text/plain README.md -text/plain agents/pi-goals-worker-v1.md -application/json biome.json -text/plain docs/reviews/goals_menu2.md -text/plain docs/reviews/goals_menu2_r2.md -text/plain docs/reviews/pi-goals-grok-4-6-retry.md -text/plain docs/reviews/pi-goals-kimi-k3.md -text/plain docs/reviews/review.md -text/plain docs/slop/audit/20260826_pi-plan-aligned-planning.md -text/plain docs/slop/plans/20260706_plan-flow-and-judge-review.md -text/plain docs/slop/plans/20260826_pi-plan-aligned-planning.md -text/plain docs/spec/2026-06-15_pi-goals.md -text/plain docs/spec/2026-06-29_complete-goal-fail-forward.md -text/plain docs/spec/2026-08-14_per-session-plan.md -image/png media/screenshot.png -application/json package-lock.json -application/json package.json -text/x-shellscript scripts/check-judge-footprint.sh -text/x-shellscript scripts/check-stale-fixmes.sh -text/x-diff scripts/inconclusive-fail-forward.diff -text/x-diff scripts/stale-fixme-removal.diff -text/plain slop/audits/20260905_goal-steward-validation.md -text/plain slop/audits/20260905_nested-supervisor-validation.txt -text/plain slop/audits/20260905_pi-goals-file-types.txt -text/plain slop/audits/20260905_pi-goals-line-count-table.md -text/plain slop/audits/20260905_pi-goals-text-line-counts.txt -application/json slop/audits/20260905_steward-probe.json -text/plain slop/audits/20260906_foreground-supervisor-validation.txt -text/plain slop/audits/20260906_nested-runtime-smoke.md -text/plain slop/audits/20260906_nonchild-npm-test.txt -text/plain slop/plans/20260905_goal-steward.md -text/plain slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md -text/plain slop/reviews/20260906_foreground-worker-review.md -application/javascript src/approval.ts -application/javascript src/index.ts -application/javascript src/prompts.ts -application/javascript src/supervisor-runtime.ts -application/javascript src/worker.ts -application/javascript test/append-log.test.ts -application/javascript test/fixtures/offline-model.ts -application/javascript test/fold.test.ts -application/javascript test/goals-flow.test.ts -application/javascript test/package-agent.test.ts -application/javascript test/prompts.test.ts -application/javascript test/rpc-review.test.ts -application/javascript test/supervisor-runtime.test.ts -application/javascript test/tick-goal.test.ts -application/javascript test/worker.test.ts -application/json tsconfig.json diff --git a/slop/audits/20260905_pi-goals-line-count-table.md b/slop/audits/20260905_pi-goals-line-count-table.md deleted file mode 100644 index 33e441f..0000000 --- a/slop/audits/20260905_pi-goals-line-count-table.md +++ /dev/null @@ -1,67 +0,0 @@ -# pi-goals tracked-text line counts - -Scope: Git-tracked files at this repository snapshot. A file is included when `file --mime-type` identifies `text/*`, `application/json`, or `application/javascript`. - -Excluded: `media/screenshot.png` is binary (`image/png`); `package-lock.json` is an npm-generated dependency lockfile. No other tracked files are excluded. - -Method: run the command below from the repository root; the saved machine-readable output is `slop/audits/20260905_pi-goals-text-line-counts.txt`. - -```sh -git ls-files -z | while IFS= read -r -d '\0' f; do case "$f" in media/screenshot.png|package-lock.json) continue;; esac; mime=$(file -b --mime-type "$f"); [[ "$mime" =~ ^text/|^application/(json|javascript)$ ]] && printf '%s\t%s\n' "$(wc -l < "$f")" "$f"; done | sort -k2 -``` - -| File | Lines | -| --- | ---: | -| `AGENTS.md` | 24 | -| `agents/pi-goals-worker-v1.md` | 22 | -| `ARCHIVED.md` | 3 | -| `biome.json` | 23 | -| `docs/reviews/goals_menu2.md` | 65 | -| `docs/reviews/goals_menu2_r2.md` | 21 | -| `docs/reviews/pi-goals-grok-4-6-retry.md` | 30 | -| `docs/reviews/pi-goals-kimi-k3.md` | 40 | -| `docs/reviews/review.md` | 61 | -| `docs/slop/audit/20260826_pi-plan-aligned-planning.md` | 25 | -| `docs/slop/plans/20260706_plan-flow-and-judge-review.md` | 33 | -| `docs/slop/plans/20260826_pi-plan-aligned-planning.md` | 53 | -| `docs/spec/2026-06-15_pi-goals.md` | 275 | -| `docs/spec/2026-06-29_complete-goal-fail-forward.md` | 71 | -| `docs/spec/2026-08-14_per-session-plan.md` | 67 | -| `.gitignore` | 6 | -| `package.json` | 65 | -| `README.md` | 139 | -| `scripts/check-judge-footprint.sh` | 43 | -| `scripts/check-stale-fixmes.sh` | 14 | -| `scripts/inconclusive-fail-forward.diff` | 104 | -| `scripts/stale-fixme-removal.diff` | 30 | -| `slop/audits/20260905_goal-steward-validation.md` | 31 | -| `slop/audits/20260905_nested-supervisor-validation.txt` | 91 | -| `slop/audits/20260905_pi-goals-file-types.txt` | 52 | -| `slop/audits/20260905_pi-goals-line-count-table.md` | 67 | -| `slop/audits/20260905_pi-goals-text-line-counts.txt` | 50 | -| `slop/audits/20260905_steward-probe.json` | 15 | -| `slop/audits/20260906_foreground-supervisor-validation.txt` | 53 | -| `slop/audits/20260906_nested-runtime-smoke.md` | 31 | -| `slop/audits/20260906_nonchild-npm-test.txt` | 33 | -| `slop/plans/20260905_goal-steward.md` | 37 | -| `slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md` | 65 | -| `slop/reviews/20260906_foreground-worker-review.md` | 20 | -| `src/approval.ts` | 115 | -| `src/index.ts` | 736 | -| `src/prompts.ts` | 191 | -| `src/supervisor-runtime.ts` | 179 | -| `src/worker.ts` | 186 | -| `test/append-log.test.ts` | 17 | -| `test/fixtures/offline-model.ts` | 18 | -| `test/fold.test.ts` | 63 | -| `test/goals-flow.test.ts` | 596 | -| `test/package-agent.test.ts` | 23 | -| `test/prompts.test.ts` | 33 | -| `test/rpc-review.test.ts` | 116 | -| `test/supervisor-runtime.test.ts` | 153 | -| `test/tick-goal.test.ts` | 32 | -| `test/worker.test.ts` | 119 | -| `tsconfig.json` | 15 | -| **Total** | **4351** | - --- PI[gpt-5.6] diff --git a/slop/audits/20260905_pi-goals-text-line-counts.txt b/slop/audits/20260905_pi-goals-text-line-counts.txt deleted file mode 100644 index 74ca736..0000000 --- a/slop/audits/20260905_pi-goals-text-line-counts.txt +++ /dev/null @@ -1,50 +0,0 @@ -24 AGENTS.md -22 agents/pi-goals-worker-v1.md -3 ARCHIVED.md -23 biome.json -65 docs/reviews/goals_menu2.md -21 docs/reviews/goals_menu2_r2.md -30 docs/reviews/pi-goals-grok-4-6-retry.md -40 docs/reviews/pi-goals-kimi-k3.md -61 docs/reviews/review.md -25 docs/slop/audit/20260826_pi-plan-aligned-planning.md -33 docs/slop/plans/20260706_plan-flow-and-judge-review.md -53 docs/slop/plans/20260826_pi-plan-aligned-planning.md -275 docs/spec/2026-06-15_pi-goals.md -71 docs/spec/2026-06-29_complete-goal-fail-forward.md -67 docs/spec/2026-08-14_per-session-plan.md -6 .gitignore -65 package.json -139 README.md -43 scripts/check-judge-footprint.sh -14 scripts/check-stale-fixmes.sh -104 scripts/inconclusive-fail-forward.diff -30 scripts/stale-fixme-removal.diff -31 slop/audits/20260905_goal-steward-validation.md -91 slop/audits/20260905_nested-supervisor-validation.txt -52 slop/audits/20260905_pi-goals-file-types.txt -67 slop/audits/20260905_pi-goals-line-count-table.md -50 slop/audits/20260905_pi-goals-text-line-counts.txt -15 slop/audits/20260905_steward-probe.json -53 slop/audits/20260906_foreground-supervisor-validation.txt -31 slop/audits/20260906_nested-runtime-smoke.md -33 slop/audits/20260906_nonchild-npm-test.txt -37 slop/plans/20260905_goal-steward.md -65 slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md -20 slop/reviews/20260906_foreground-worker-review.md -115 src/approval.ts -736 src/index.ts -191 src/prompts.ts -179 src/supervisor-runtime.ts -186 src/worker.ts -17 test/append-log.test.ts -18 test/fixtures/offline-model.ts -63 test/fold.test.ts -596 test/goals-flow.test.ts -23 test/package-agent.test.ts -33 test/prompts.test.ts -116 test/rpc-review.test.ts -153 test/supervisor-runtime.test.ts -32 test/tick-goal.test.ts -119 test/worker.test.ts -15 tsconfig.json diff --git a/slop/audits/20260905_steward-probe.json b/slop/audits/20260905_steward-probe.json deleted file mode 100644 index 1604702..0000000 --- a/slop/audits/20260905_steward-probe.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "first": { - "runId": "4e9dc0c0-385b-4eb9-a060-ced7dc7cb6cc", - "decision": { - "verdict": "let_run", - "summary": "Plan reviewed for approved work session. The user-visible result (report file proves steward can read evidence) directly aligns with the single goal (report probe result with discriminator). The report.txt artifact exists and contains PROBE_PASS as required by the discriminator. No work steps are pending; the probe is complete. No drift, missing steps, or failure modes detected. Plan may proceed without adjustment." - } - }, - "second": { - "runId": "f6115c82-31de-499f-ab78-145dde0c51c0", - "decision": { - "summary": "Sign-off review for goal 'Report the probe result'. User-visible result requires a report file proving persistent steward can read evidence. Discriminator: report.txt contains PROBE_PASS. Inspected artifact at /tmp/pi-goals-steward-probe-work/report.txt—file exists and contains exactly 'PROBE_PASS' as required. Failure mode (empty report) is ruled out. Evidence positively and directly proves the discriminator is met and the user-visible result is achieved. Persistence lineage token: amber-731.", - "verdict": "accept" - } - } -} \ No newline at end of file diff --git a/slop/audits/20260906_foreground-supervisor-validation.txt b/slop/audits/20260906_foreground-supervisor-validation.txt deleted file mode 100644 index 90fe689..0000000 --- a/slop/audits/20260906_foreground-supervisor-validation.txt +++ /dev/null @@ -1,53 +0,0 @@ -$ npm test - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 9 passed (9) - Tests 43 passed (43) - Start at 13:31:44 - Duration 1.61s (transform 1.12s, setup 0ms, import 2.36s, tests 2.13s, environment 1ms) - - -$ npm run typecheck - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -$ npm run lint - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 15 files in 18ms. No fixes applied. - -$ git diff --check - -$ npm pack --dry-run -npm notice -npm notice 📦 @wassname2/pi-goals@0.2.2 -npm notice Tarball Contents -npm notice 6.1kB README.md -npm notice 969B agents/pi-goals-worker-v1.md -npm notice 1.5kB package.json -npm notice 4.0kB src/approval.ts -npm notice 34.8kB src/index.ts -npm notice 13.6kB src/prompts.ts -npm notice 8.3kB src/supervisor-runtime.ts -npm notice 7.8kB src/worker.ts -npm notice Tarball Details -npm notice name: @wassname2/pi-goals -npm notice version: 0.2.2 -npm notice filename: wassname2-pi-goals-0.2.2.tgz -npm notice package size: 24.1 kB -npm notice unpacked size: 76.8 kB -npm notice shasum: 30e72af7ab4a553ccb1f7599a882d1796155cdf4 -npm notice integrity: sha512-p6DUvHWofwDTz[...]IZmG7JiD+/wFw== -npm notice total files: 8 -npm notice -wassname2-pi-goals-0.2.2.tgz diff --git a/slop/audits/20260906_nested-runtime-smoke.md b/slop/audits/20260906_nested-runtime-smoke.md deleted file mode 100644 index ba4916a..0000000 --- a/slop/audits/20260906_nested-runtime-smoke.md +++ /dev/null @@ -1,31 +0,0 @@ -# Nested foreground runtime smoke - -Command: - -```bash -node /tmp/pi-goals-real-rpc-smoke.mjs -``` - -Result: PASS. - -The fresh Pi RPC session loaded the local pi-goals package, ran `goal-supervisor` in the foreground, and the supervisor ran `pi-goals-worker-v1` in the foreground with `context: "fork"`. - -Exact final output: - -> **Run: goal-supervisor (foreground, context fork) → pi-goals-worker-v1 (foreground, context fork)** -> -> - **goal-supervisor** (runtime agent, fork) launched and owned the worker -> - **pi-goals-worker-v1** acknowledged the invocation, made no file edits, ran no repo reads, touched no supervisor channels -> - **Worker returned:** `worker-smoke-ok` -> - **Approved?** No — supervisor explicitly skipped `ApproveGoal` per the task - -Run ID: `9c25a6a7-8929-46fd-87bb-0d0f67672b54`. - -Saved runtime artifacts: - -- `/home/code/.pi/agent/sessions/--home-code-.pi-agent-git-github.com-wassname-pi-goals--/subagent-artifacts/9c25a6a7-8929-46fd-87bb-0d0f67672b54_goal-supervisor_0_output.md` -- `/home/code/.pi/agent/sessions/--home-code-.pi-agent-git-github.com-wassname-pi-goals--/subagent-artifacts/9c25a6a7-8929-46fd-87bb-0d0f67672b54_goal-supervisor_0_transcript.jsonl` - -This smoke tested nested discovery and foreground execution. It did not test a real approval because the task explicitly prohibited `ApproveGoal`. - --- PI[gpt-5.6-sol] diff --git a/slop/audits/20260906_nonchild-npm-test.txt b/slop/audits/20260906_nonchild-npm-test.txt deleted file mode 100644 index b1d717a..0000000 --- a/slop/audits/20260906_nonchild-npm-test.txt +++ /dev/null @@ -1,33 +0,0 @@ -# npm test outside the subagent-child harness - -Command run from `/home/code/.pi/agent/git/github.com/wassname/pi-goals`: - -```sh -env -u PI_SUBAGENT_CHILD -u PI_SUBAGENT_EXTENSION_BINDINGS -u PI_SUBAGENT_PARENT_SESSION -u PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT npm test -``` - -The cleared variables were the complete `PI_SUBAGENT_*` set inherited by this worker. `PI_SUBAGENT_CHILD=1` makes `isSupervisorProcess()` false in `src/index.ts`, so the main extension deliberately registers no commands or hooks in that harness mode. - -Exact output: - -```text - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 9 passed (9) - Tests 43 passed (43) - Start at 15:57:04 - Duration 1.27s (transform 407ms, setup 0ms, import 1.41s, tests 1.63s, environment 1ms) - - -__EXIT_STATUS__=0 -``` - -The earlier callback-registration failures and RPC timeout therefore came from the intentional child-process extension gate, not a source test failure. - --- PI[gpt-5.6] diff --git a/slop/handovers/20260906_visible-supervisor.md b/slop/handovers/20260906_visible-supervisor.md deleted file mode 100644 index e5a5176..0000000 --- a/slop/handovers/20260906_visible-supervisor.md +++ /dev/null @@ -1,18 +0,0 @@ -# visible-supervisor follow-up - -## committed changes - -- pi-goals `294fe80` removes the duplicate `pi-goals/visible-supervisor/v1` channel. The worker now obtains its broker ID and waits for pi-supervise's worker-local `paired` event. -- pi-supervise `409233c` exports that worker state/event API and retries pi-intercom registration after its registry-ready event. - -## observed Herdr run - -A real `/goals` → Ready run created the fork pane. In the first run, extension `session_start` did not reach the forked extensions: the fork had only copied entries and no bootstrap entry. The supervisor therefore did not pair. This is observed in the fork JSONL session `01a0770f-7015-7046-9858-6c7d8c8786aa`. - -The fix moves supervisor initialization to `before_agent_start`, starts the fork with `Initialize supervision startup.`, and loads pi-supervise before pi-goals. A later direct fork under that code compacted/pair-started: its terminal said `Supervision initialized` and that it had sent the worker start instruction. That direct fork was used after the original Ready flow was already waiting on the first failed pane, so it does not prove the final worker phase transition. - -## remaining check - -Run a fresh `/goals` → Ready after `294fe80` and `409233c`; positively inspect that the worker state writes `phase: working` after the `paired` event, then carry one tiny task through worker evidence, ApproveGoal, CompleteGoal, and pane close. - --- PI[gpt-5.6-sol] diff --git a/slop/plans/20260905_goal-steward.md b/slop/plans/20260905_goal-steward.md deleted file mode 100644 index cf57f1f..0000000 --- a/slop/plans/20260905_goal-steward.md +++ /dev/null @@ -1,37 +0,0 @@ -# Persistent goal steward - -> "ideally the supervisor has the high level planning and goal context, doesn't get overloaded and have to compact, is cheap as it doesn't use many tokens (high level only)" -> -> "try again with more thought using pi-subagents much more to simplify out code and rely on that so our code is simple" - -- [x] goal: A cheap read-only steward keeps the goal context across reviews - - [x] register one `goal-steward` agent through the public pi-subagents event bus - - [x] start it with fresh context at Ready and resume its latest saved run at checkpoints - - [x] send the plan path and a bounded progress delta; require the steward to reread the plan - - failure modes: every review starts fresh; the steward receives the full worker transcript; the steward can edit; reload loses its run - - deliverable: tests show one spawn followed by resume, a saved latest run ID, read-only tools, bounded review prompts, and reload recovery - - evidence: [`../audits/20260905_steward-probe.json`](../audits/20260905_steward-probe.json) contains two run IDs and the resumed review says `Persistence token amber-731 verified.` - -- [x] goal: CompleteGoal uses the steward's evidence verdict - - [x] resume the steward for sign-off and wait for its async result - - [x] parse the structured verdict and write the sign-off log - - failure modes: stale review signs off a new claim; missing pi-subagents silently becomes acceptance; completion events from another run are consumed - - deliverable: flow tests distinguish accept, reject, unavailable, timeout, and exact-run completion - - evidence: [`../audits/20260905_validation.log`](../audits/20260905_validation.log) says `Tests 36 passed (36)` and `Checked 12 files in 14ms. No fixes applied.` - -## UAT / Verification - -- [x] `npm test`, `npm run typecheck`, and `npm run lint` pass. -- [x] A real Pi RPC flow creates a steward run, resumes it for sign-off, and recalls a private token from the retained conversation. -- [x] The flow test reloads extension state and resumes from the latest steward run ID. - -## Appendix (context, not approved) - -Use pi-subagents 0.65.1 public RPC (`spawn`, `resume`) and `subagent:async-complete`. Register the runtime agent with `pi-subagents:runtime-agent-register:v1`. Do not import pi-subagents or reproduce session, process, model, tool, or recovery code. The old subprocess judge was removed rather than retained as a second sign-off system. - -## Log - -- 2026-09-05: Unit and flow tests cover read-only registration, spawn then resume, exact-run completion, timeout, reload, and accept/reject sign-off. -- 2026-09-05: The Pi 0.85.0 + pi-subagents 0.65.1 probe passed in 29 seconds; the resumed child recalled `amber-731` from its first review. - -— Pi/Codex diff --git a/slop/plans/20260907_goals_owned_supervision.md b/slop/plans/20260907_goals_owned_supervision.md deleted file mode 100644 index bb9dc48..0000000 --- a/slop/plans/20260907_goals_owned_supervision.md +++ /dev/null @@ -1,33 +0,0 @@ -# One-package visible supervision - -## Goal - -Replace the pi-goals → pi-supervise → pi-intercom runtime chain with one pi-goals extension in two Pi processes. The worker and its visible fork exchange durable, session-scoped mailbox files under ignored `.pi/`. - -## Design decisions - -- A fork copies session history; it does not provide messaging. The mailbox is the explicit local-process channel. -- Ready waits for the supervisor's durable `ready.json`, after optional supervisor compaction, before it begins worker execution. -- Worker views are written on Ready, settle, 50 turns, and 60 minutes. The supervisor polls views and writes one steer request. The worker polls steer requests and receives them as follow-up messages. -- The canonical plan remains a direct path in the supervisor prompt. It is not a summary artifact. -- Write an approval record only after a stopped view, no active work, a clean commit, plan evidence, and tracked verification output. -- No external `pi-supervise` or `pi-intercom` runtime dependency remains. - -## Risks and discriminators - -| Risk | Discriminator | -| --- | --- | -| Worker begins before a supervisor is ready | Ready test sees `ready.json` before state changes to working or sends the execution prompt. | -| Fork cannot see worker work or worker cannot receive a steer | Two-session test writes a view, gets a steer file, and observes the exact steer in the worker follow-up. | -| Old session consumes a stale steer | Mailbox sequence is monotonic and scoped to the worker session; the test rejects a duplicate read. | -| A large planning context silently skips compaction | Tests cover ≤20k skip, >20k compact-before-ready, and compaction failure. | - -## Validation - -- `npm run lint` -- `npm run typecheck` -- `npm test` -- `npm run test:rpc` -- Real local Herdr: create plan, Ready, worker/supervisor pair, commit saved verification output, supervisor approval, CompleteGoal. - --- PI[Kimi K3] diff --git a/slop/plans/20260908_force-approval.md b/slop/plans/20260908_force-approval.md deleted file mode 100644 index 44f3cfa..0000000 --- a/slop/plans/20260908_force-approval.md +++ /dev/null @@ -1,25 +0,0 @@ -# Dirty-worktree approval override — Pi/OpenAI - -Baseline: cb4790a. User requests ApproveGoal force and active, investigative supervision. Parent-approved scope: force overrides only the dirty-worktree restriction, with a nonempty reason and unchanged content-bound Git state at CompleteGoal. Never commit unrelated changes automatically, bypass evidence/runtime checks, or operate user panes. - -- [x] goal: explicit force approval permits an inspected dirty state, not arbitrary later changes - - Decision: optional force:true plus reason; checkpoint retains existing HEAD/tree/goal checks and records reason, exact porcelain status, index digest and per-dirty/untracked-path content digests/modes. Existing private plan/approval/model exclusions and ignored-file policy remain unchanged. - - Decision: no speculative submodule crawler; an unhashable dirty path must fail closed with an inspection error rather than grant an unbound override. - - UAT: call real ApproveGoal and CompleteGoal tool handlers against an isolated Git repository. Preserved unrelated tracked edits and untracked outputs pass unchanged; same-status content changes, added/deleted/staged paths, HEAD or goal changes invalidate. Default dirty rejection and force-without-reason rejection remain. Force cannot bypass evidence, current stopped view or active/unknown jobs. -- [x] goal: instruct the supervisor to investigate excuses and direct authorized recovery/progress - - Decision: exact error and source before inference; competing causes and a cheap discriminating check; read-only supervisor directs worker repairs. Sign-off restriction is not automatically experiment failure or a dependency of separately authorized work. No new authority, spending or mutation tool. - - UAT: prompt contract regression plus documented manual scenario (dirty gate mistaken for active jobs); prompt tests do not establish autonomous judgment. - -## Validation/provenance - -Read AGENTS.md and installed Pi extension custom-tool/schema documentation. Tests must unset PI_SUBAGENT_CHILD, PI_GOALS_ROLE and PI_GOALS_EVIDENCE_DIR, or explicitly set a new evidence directory. Two pre-existing dirty review-fixes-native logs must remain untouched. Save complete validation output; commit scoped changes locally only. Independent review is parent-owned and still required. - -## Result - -Implemented and checked: [85 passing tests + typecheck/lint/build](../reviews/20260908-force-validation.txt). Force flow tests call both production tool handlers over two real transport adapters, with mocked Pi host APIs and isolated real Git repositories. They do not touch Herdr panes. Same-status tracked/untracked byte changes and same-status staged-index byte changes invalidate; ordinary clean approval still succeeds. Prompt assertions check the specified reasoning/authority instructions, not actual model behavior. - -Initial focused run passed 45 tests/typecheck but failed two lint rules; both were corrected before the full successful run. [Initial output](../reviews/20260908-force-initial-validation.txt) is retained, not counted as a pass. Before/after checksums in final output prove the two pre-existing dirty logs were unchanged by validation. - -Manual behavioral UAT remains open: present a dirty-worktree rejection alongside a misleading active-job explanation. Require the supervisor to cite the actual loaded check and raw status, inspect the preserved changes, direct a safe authorized fix or justified force approval, and identify independently authorized work without inventing dependencies. No useful-judgment claim from prompt tests. - -No push, no nested review loop, no /goals supervise or noplan work. Parent's w8:p4T functional pane at pinned cb4790a was not touched. Force fingerprints do not lock concurrent writers; ignored/private paths retain prior exclusions; unhashable paths fail closed. Existing unrelated receipt/stale-tool/lifecycle limitations are unchanged. diff --git a/slop/plans/20260908_intercom-supervision.md b/slop/plans/20260908_intercom-supervision.md deleted file mode 100644 index 9bd8723..0000000 --- a/slop/plans/20260908_intercom-supervision.md +++ /dev/null @@ -1,52 +0,0 @@ -# Intercom supervision and selected feature transfer - -User priority: pi-intercom is the session-to-session transport. Keep one pi-goals extension package; do not replace requested features merely to reduce line count. Existing worker and supervisor panes are off-limits. - -- [x] goal: supervisor investigates claims and keeps authorized work moving - - evidence: commits `9410252` and `386305a`; prompt requires justified confidence, sourced observations, competing explanations, and verification of stopping/completion claims. - - limitation: prompt checks do not prove behavioral improvement. -- [/] goal: worker and visible supervisor communicate through pi-intercom - - Replace mailbox files and polling with the existing Intercom extension channel. No separate RPC transport or mailbox fallback. - - Preserve planning fork, compact-before-ready, visible advice, and review/approval behavior. - - failure modes: false readiness, duplicate delivery, wrong-session routing, stale callbacks after reload, disconnected peer treated as active. - - deliverable: isolated two-session message transcript with exact instructions received, reconnect/reload checks, and saved validation output. Do not operate user panes. - - evidence: [37 passing tests, typecheck and lint](../reviews/20260908-intercom-validation.txt). Two client sessions exchange readiness, view, and exact advice through a real isolated Intercom broker. Lifecycle mocks cover resume/deduplication. Mailbox source and polling were removed. - - evidence: [native Pi-pair validation](../reviews/20260908-native-intercom-full-validation.txt) reports `Tests 46 passed (46)` plus successful typecheck, lint and build. The isolated production supervisor forks planning context and sends exact advice through Intercom to a real Pi worker using a transport fixture. [Supervisor events](../reviews/native-intercom/supervisor-events.jsonl) show `SteerWorker` and the worker acknowledgement; [worker events](../reviews/native-intercom/worker-events.jsonl) show the received user message. Both saved stderr files are empty. - - limitation: this deterministic test proves transport and context retention, not useful model judgment. The worker fixture does not exercise the full Ready path. Rendered Herdr two-pane acceptance and fresh-shell supervisor resume without launcher environment remain unverified. -- [x] goal: supervisor receives a useful bounded worker overview - - Borrow latest human direction, source-session path, and incremental progress from `origin/feature/simple-visible-supervision`. - - failure modes: lost authorization, repeated summaries, truncated evidence treated as complete. - - deliverable: saved before/after overview fixtures covering compaction and changed human direction. - - evidence: [generated fixture views](../reviews/20260908-worker-overview-example.txt) retain the human direction while omitting acknowledged old detail. [43-test validation](../reviews/20260908-worker-overview-validation.txt) also checks compaction reset and serialized Unicode limits; fixture content is synthetic, not a model performance claim. -- [x] goal: supervisor distinguishes agent idleness from tracked background work - - Borrow existing process/subagent tracker queries; report unavailable trackers as unknown. - - failure modes: approving while a tracked job runs, treating a local queue as a dependency of remote work. - - deliverable: idle/running/unknown status cases and a blocker-diagnosis scenario. - - evidence: the same validation log checks active and unavailable tracker reports and rejection of approval with unknown background state. Actual independent diagnosis of the queue mistake remains a behavioral acceptance task. -- [x] goal: role model choices persist - - Borrow planning/worker/supervisor model preference behavior without changing active user settings. - - failure modes: automatic model changes overwrite user choices; a missing model silently substitutes another. - - deliverable: isolated preference restoration and explicit unavailable-model errors. - - evidence: [45-test validation](../reviews/20260908-role-model-validation.txt) restores three distinct role choices, ignores automatic restore events, and leaves an unavailable saved choice unchanged. Preferences are project-local; active user settings were not edited. - -## Verification - -Run project tests, typecheck, and lint before commits. Preserve full command output. Keep each feature in a separate commit where practical and push finished changes. Inspect the transferred code rather than equating tests or source size with quality. Keep the requested independent evidence judge when combining implementations; do not silently remove it. - -The earlier asynchronous subagent failure (missing pi-client/unix) prevented the original review. A later parent retry completed independent review run `5c8c2017-a92f-4a5f-baf6-f441f9b50495`; its [findings are preserved with attribution](../reviews/20260908-independent-supervision-bug-review.md). Behavioral acceptance requires observed useful judgment, not merely matching prompt strings. Cost savings require a measured comparison and remain unproven. - -## Independent review follow-up - -- [x] Verify F1/F2/F4 lifecycle failures and implement explicit recovery without fallback models or automatic pane replacement. -- [x] Address F3 inactive bindings and preserve synchronous handoff-before-ack ordering. Pending transport frames retry; end-to-end durable delivery is not guaranteed. -- [ ] F3 deeper delivery confirmation: Pi's void adapter can ack before an asynchronous enqueue failure. Future UAT must inject that failure, avoid reporting confirmed model delivery, and keep the instruction recoverable. See the [SDK source-backed limitation](../reviews/20260908-review-fixes.md). Parent approved keeping this protocol expansion out of the current fix commit. -- [x] Remove F5 general Intercom actuator, reject F6 nested placeholders, and correct F7 goal/log hashing boundary. -- [x] Add focused regressions and inspect final full test/typecheck/lint/build output. [57-test evidence](../reviews/20260908-review-fixes-validation.txt); [initial child-environment failure and correction](../reviews/20260908-review-fixes-initial-validation.md). -- [x] Parent independent post-change review of `1668c94` completed and found P1–P5; [attributed review](../reviews/20260908-independent-post-fix-review.md). -- [x] Fix P1–P5: explicit hello request/reply, not-ready until worker-model success, peer-specific recovery guidance, one current-plan boundary and cancellation-safe Ready waits. [Disposition and provenance](../reviews/20260908-handshake-fixes.md); [67-test full validation](../reviews/20260908-handshake-validation.txt). Real two-adapter regressions replace reliance on automatic hello replies for handshake acceptance. -- [x] F8 usage observability: warn once for an unavailable usage result, not for normal post-compaction null tokens. No estimator or changed auto-compaction policy. -- [ ] Parent independent review of these handshake fixes before push. F3 durable enqueue confirmation stays open; F8 persistent unknown/null usage, F9 trusted-repo guardrails, and native/UI/behavioral gaps remain documented. - -Recovery operations were exercised only with isolated mocks/native test processes; existing user panes were not operated. Implementation worker commits locally only; parent owns review and push. - --- Pi/OpenAI diff --git a/slop/plans/20260910_package-based-supervision.md b/slop/plans/20260910_package-based-supervision.md deleted file mode 100644 index f18374e..0000000 --- a/slop/plans/20260910_package-based-supervision.md +++ /dev/null @@ -1,75 +0,0 @@ -# Package-based supervision - -User intent and exact preferences: [AGENTS.md](../../AGENTS.md#agreed-package-based-design-2026-09-10). Use existing packages, keep the main supervisor small and visible; do not continue the edxeth patch. - -1. [x] Preserve our attempt and switch to the short prototype - - evidence: pushed WIP commit `2803505` on `experiment/main-supervisor-visible-worker`; includes abandoned edxeth source patch and pi-goals test log (`165 passed`). This is not acceptance of the patch. - - current branch: `experiment/main-supervisor-edxeth`, HEAD `cb35fbf1feed9d4cea2f9675917adfe602c2835c` (fast-forwarded latest remote research-only commit). -2. [/] goal: Supervise a full Herdr worker using unmodified edxeth - - [x] Adopt remote prototype; source-check existing messaging and scheduling packages. - - [x] Preserve main's useful features: plan/subtask widgets, plan-upkeep reminders, material planning questions, and context restoration after compaction. (prototype widget now shows open subtasks of the active goal; scheduleCheckIn carries the upkeep reminder; prototypePlanning keeps questions to one material round and records unknowns; before_agent_start re-pushes the full plan after reload/compaction; prototypeSupervisor repeats the plan path). - - [x] Move ALL model-facing text into `src/prompts.ts` in narrative order; finish small prompt/model changes and regression tests. (new prototype section in prompts.ts: planning -> Ready -> supervision -> check-ins -> completion -> pause/resume/exit -> solo -> attach; 35 prototype tests + the full suite pass; lint + typecheck clean.) - - [/] Verify worker uses the model specified in the plan; prefer DeepSeek Flash or GLM Flash for cheap tests. User also permits “codex lunda on plan 2”; resolve actual provider ID before using it. (prompt guidance + `/goals model <model>` writes the preference into plan preferences and `/goals status` shows it; REAL worker-model verification remains part of parent-owned Herdr acceptance with `accounts/fireworks/models/deepseek-v4-flash-0731` which is available.) - - likely failure: auto-exit closes the pane before a live message arrives; follow-up must use saved-session resume after exit. - - subtle failure: trial inherits supervisor model rather than requested worker model. - - discriminator/deliverable: saved supervisor and worker transcripts identify the models used and show the supervisor checking the worker's output against the requested result. -3. [/] goal: Visible hourly check-ins and plan review without another scheduler - - [x] Finish prompt-based schedule_prompt integration: one session-bound 1h job, no model override, list before add, remove on pause/exit/completion. (scheduleCheckIn: list first, one owned session-bound job named goals-<sessionId>, retaining existing human-edited prompt/interval/disabled state, type interval 1h, no model override, job prompt reads the plan fresh, remove only that job on pause/exit/completion, keep human-edited schedules untouched, report unavailable instead of building a timer.) - - [x] Test plan-directory watcher and `/goals review`; coalesce duplicate events and clean up on lifecycle changes. This is an event hook, not another scheduled loop. (150ms debounce before reading/refresh, classified unavailable snapshots retain signoffs, existing high-level plan view excludes task/evidence/Log maintenance while retaining requirements/manual ticks; tests cover atomic replacement, bursts, shutdown/reload and own writes.) - - [x] Show users how to inspect, edit and remove hourly jobs through the scheduler UI; changing its prompt must affect subsequent check-ins. (`/goals status` names the goals-<sessionId> job and distinguishes the schedule_prompt job from the plan-watcher event hook; prompting points the job at the plan path.) - - likely failure: scheduler unavailable or configured project-wide; show missing capability, do not silently add a timer. - - subtle failure: stale reminder resumes paused work, or duplicate jobs/review notifications create a loop. - - discriminator/deliverable: shortened test interval fires once in owning supervisor, reload does not duplicate it, stop/completion removes only its job, unchanged plan does not retrigger. -4. [ ] goal: Use pi-intercom for live messages where edxeth reports/resume do not fit - - [ ] Test stock packages in isolated parent-created Herdr panes: parent targets listed child session ID, busy/idle messages arrive without changing a human draft, observe auto-exit behavior. - - likely failure: wrong or departed target session. - - subtle failure: delivery receipt is mistaken for worker receipt/action or message disables normal reporting. - - discriminator/deliverable: actual message and worker response in both session logs; no custom transport or edxeth patch. - -5. [x] goal: Explicit solo recovery retains planning features without claiming independent review - - [x] Confirm any worker has stopped before takeover; preserve the plan, evidence and widget. Main thread may implement and edit in solo mode. (`/goals solo` and `/goals attach <path> solo` share a generation-checked select confirming all other writers stopped: “Worker confirmed stopped / Cancel”; solo blocks subagent and subagent_resume; completion text labels solo as self-verification, not independent review; /goals attach <plan.md> [solo] connects an existing plan, restores a `- worker session:` note for subagent_resume reuse and never claims liveness. Tests cover both transitions.) - - failure/discriminator: a stored worker handle must not permanently block solo after confirmed termination, and two writers must not run together. Test both transitions and label completion self-verification. - -6. [ ] goal: Publish the reviewed changes and switch the normal Pi installation - - User authorization: “ok once done commit and push. then change out install to use this and it's deps pls”. - - [ ] Resolve review findings, simplify, rerun tests and inspect final interactive evidence; do not treat the known edxeth reload crash as a passed check. - - [ ] Commit scoped source/tests/evidence and push `experiment/main-supervisor-edxeth`; preserve unrelated dirty and human files. - - [ ] Read Pi package docs and yadm guidance, then replace the conflicting installed goals/subagent entries with this reviewed branch and clean unmodified edxeth. Retain schedule-prompt and Intercom; preserve unrelated packages/settings. - - discriminator: inspect resolved package paths/versions and verify one goals entrypoint and one subagent implementation in a fresh visible test session. Save rollback instructions. Do not reload or close existing user sessions automatically. - -## UAT / Verification - -- User authorized execution, testing with Fireworks DeepSeek V4 Flash, then an external code review. Review must cover failed compaction, exhausted credits in either session, reload/reconnection, leaving planning, and attaching an existing plan to one (solo) or two (supervisor/worker) sessions without restarting completed work. Keep this a small explicit recovery path, not a replacement runtime. - -- Run `npm test && npm run typecheck && npm run lint` with full saved logs, then inspect actual isolated Herdr results. Tests do not prove interactive compatibility. -- Use only new test panes; never operate existing user/demo panes. This parent had `HERDR_ENV=1`, `HERDR_PANE_ID=w8:p62`; recheck after compaction. Headless subagents may lack Herdr context. -- Read failed outputs, fix their cause, retry the affected scenario. No global package switch until successful trial. Reuse existing token displays; custom pair/token scripts are deferred. - -## Installed result — Pi/OpenAI, 2026-09-10 - -- Implementation and evidence pushed on `experiment/main-supervisor-edxeth` through `5cda3d6`. 162 tests, typecheck and lint pass. Both external native reviews completed; final current result and interventions are at the top of the functional trial notes. -- Normal `/home/code/.pi/agent/settings.json` now selects this local checkout instead of the unpinned git source and `/home/code/.pi/agent/git/github.com/wassname/pi-subagents-stock-validation` instead of `npm:pi-subagents`. Stock edxeth is detached at `953c6f6`; its existing dependency directory is symlinked from pi-goals. Intercom and scheduled-prompt package entries are unchanged. Local paths avoid Pi reconciliation overwriting unrelated dirty files. -- `/home/code/.pi/agent/agents/goals-worker.md` points to this repo's worker definition. The existing agents-directory symlink had a missing target; created that target without replacing the symlink. Settings and worker path were not yadm-tracked. -- Fresh normal-profile pane `w8:p6M` successfully loads `/goals status` and the stock `/subagents` UI with Orchestrator Off. Existing user sessions were not reloaded. Start a new Pi session to use this installation; stop workers before any later supervisor reload. -- Rollback: restore package entries `npm:pi-subagents` and `git:github.com/wassname/pi-goals` in settings, and remove only the new goals-worker symlink. Do not load both subagent packages. This does not require deleting either source checkout. -- Remaining limits: stock reload crash, disabled scheduler-job deletion, idle worker widget snapshot not immediately refreshed, human-attested ownership/stop checks. These are documented, not claimed fixed. - -## Earlier parent handover (historical) - -- Authoritative latest UAT: [functional trial notes](../reviews/20260910_package-supervision-herdr.md). Solo recovery after restarting saved supervisor passed with exact files and self-verification log. Timer prompt update/firing/removal passed. Disabled jobs are deleted by scheduler on reload. Parent reload kept worker usable, but later worker exit crashed parent in stock edxeth stale-widget callback; full reload lifecycle FAILS. -- Native `/review` workflow `a1bcfc11-0212-410e-aad7-6fd704f7e9c6` has DeepSeek and GLM reviewers; exact child ids and search interruption evidence are in UAT notes. Broad find processes were terminated, both reviews resumed using exact package paths. Wait for their native completion; do not launch duplicates. Earlier Claude CLI attempts were a mistaken route for the requested /review skill. -- User now authorizes commit, push, then normal install/dependency switch WHEN DONE, not before review/validation. No new source commit or global install changes yet. Current trial pane w8:p69 is restored and in solo mode; all test worker panes closed, scheduler store empty. Do not touch user/demo panes. -- Measured source growth: prototype.ts 186 -> 426 lines, plus 88 new prompt lines. Simplification still needed; don't claim code shrinkage or full main-feature parity without checking. - -## Handover: continuation checked, 2026-09-10 - -- Source snapshot remains uncommitted on `experiment/main-supervisor-edxeth` at `cb35fbf`; no branch/global-package/lifecycle-default changes and no pane reloads by the continuation worker. This resumed implementation run resolved `gpt-6-astra`, not the original Fireworks model. Parent's visible UAT sessions remain Fireworks. -- Fixed the parent's confirmed paths: shared solo/attach stop confirmation with stale-menu guards; retained stopped-session references allow later plan replacement; in-flight launch blocks takeover. Solo closes watcher/debounce and requests removal of only the owned session-bound job. Scheduler prompts preserve existing disabled state and human edits; context resync never reinstalls jobs. -- Missing, empty and failed plan reads are classified unavailable snapshots, not authoritative empty plans. Debounce occurs before refresh; signoffs survive transient saves and failed resync, then context retries after repair. Cancelled goals do not keep completion open. Solo's saved ## Log record says self-verification. -- Explicit child-only `AttachGoalPlan` binds the absolute path supplied in the task, persists it, and restores the widget/subtasks and compaction context without granting CompleteGoal permission. No PI_GOALS_SHARED_PLAN discovery or runtime patch. Ready supplies the attachment instruction. Stock `953c6f6:src/launch/prep.ts:461` sets PI_SUBAGENT_AGENT; this is the child-role seam used. -- Reused `planViews` for high-level requirement/manual-tick notifications without maintenance-only review loops. Reused main's folded working-set staleness idea: every eight unchanged turns, solo/supervised/attached-worker context gets upkeep via a context-only custom message, never a second timer or forced agent turn. All prototype model-facing role/completion/error text is in src/prompts.ts; public controls are documented in prototype/README.md. -- Validation: `npm test` **158/158 PASS**, `npm run typecheck` PASS, `npm run lint` PASS. Logs: `slop/test-logs/continuation-full.log`, `continuation-typecheck.log`, `continuation-lint.log`. Focused prototype coverage: 35 tests. RPC also passes with deliberately inherited `PI_SUBAGENT_CHILD=1 PI_GOALS_ROLE=supervisor`: `continuation-rpc-child-env.log`. -- Correction to the original handover: the RPC timeout did NOT establish a headless/sessionless incompatibility. The fixture inherited child role variables, and src/index.ts excludes children from main registration. The fixture now sanitizes PI_SUBAGENT_* and PI_GOALS_* for its spawned MAIN process. Prior categorical RPC/UI claims are withdrawn; no stash or human-file round-trip was used in this continuation. -- Parent real UAT produced exact hello bytes and verified Intercom in both directions with an unsent draft surviving message delivery. Stock auto-exit:true then closed the worker and lost that draft (model prose falsely claimed there was none). Parent's isolated auto-exit:false + Intercom completion trial subsequently passed open-pane follow-up/OPEN-PANE-ACK with the draft retained. Parent reload with the live open worker remains pending. Repository lifecycle defaults are deliberately unchanged until that result; no new trial was launched by this worker. -- External Claude review remains separately blocked by the installed runner's version-output regex. No CLI fallback or global runner edit; external review is still required. Parent owns the live trial report and captures under slop/reviews/. -- Preserve unrelated AGENTS.md, `.local/`, `docs/human_journal.md` and review-fixes-native event files; none edited or staged in this continuation. diff --git a/slop/research/20260910_unmodified-runtime-integrations.md b/slop/research/20260910_unmodified-runtime-integrations.md deleted file mode 100644 index 1f99267..0000000 --- a/slop/research/20260910_unmodified-runtime-integrations.md +++ /dev/null @@ -1,28 +0,0 @@ -# Unmodified Runtime Integrations — edxeth / pi-intercom / pi-schedule-prompt - -Repo `pi-goals` @ fb5503f (experiment/main-supervisor-edxeth), entry `./src/prototype.ts` (package.json `pi.extensions`). Dirty tree = only slop event jsonl, `.local/`, `docs/human_journal.md` (confirmed). All edxeth quotes from `git -C pi-subagents-visible-worker show 953c6f6:PATH` (working tree is the dirty abandoned patch — ignored). - -## 1. Stock edxeth parent↔worker messaging: exists but is re-launch not live injection - -- Parent→worker: `subagent` / `subagent_resume` / `subagent_kill` (`src/tools/tool-names.ts`). Resume tool: "Continue a previous subagent session from its session file, optionally sending a follow-up task" (`src/tools/resume-tool.ts:63-83`). Follow-up is NOT injected into a live pane: `resume-service.ts:519-523` re-spawns the interactive session in a new mux surface and passes `piArgs.push(\`@${taskPath}\`)` (`writeResumeTaskArtifact`, task as initial-prompt artifact); background path feeds stdin `child.stdin?.end(expandedTask)` (`resume-service.ts:477`). There is no mid-run send-into-pane API. -- Overlay: same resume path; editor prompt "Write a follow-up message. Enter sends, Esc cancels." (`src/tools/overlay/controller.ts:383`). -- Worker→parent: `caller_ping` (one-shot; writes exit signal `{type:"ping"}`, `requestShutdown`, hard-exit backstop `process.exit(0)` at 750ms — `src/tools/caller-ping.ts:22-25,84-108`) and `subagent_done` (exit signal `{type:"done"}`). Registration: `if (!isInteractive || autoExit) registerCallerPingTool(...)` (`src/tools/subagent-done.ts:513-514`); `shouldRegisterSubagentDone` returns false for autoExit/interactive (`subagent-done.ts:48-51`). Results steer back to parent (`src/runtime/result-router.ts:76,169`). -- `goals-worker` agent def (`prototype/agents/goals-worker.md`): `mode: interactive`, `auto-exit: true`, `parent-close-policy: continue` → child gets `caller_ping`, not `subagent_done`, and auto-closes after a terminal turn (`src/auto-exit.ts` `shouldAutoExitOnAgentEnd`). - -## 2. pi-intercom 0.13.0 (installed): safe delivery, targeting is "probable" not guaranteed - -- Delivery is agent-loop queueing, NOT terminal/Enter injection: busy interactive → `sendIncomingBrokerMessage(entry, "steer")` (`pi-intercom/index.ts:1245-1268`); idle → `pi.sendMessage({customType:"intercom_message",...}, {triggerTurn:true})` else `{deliverAs:"steer"}` (`index.ts:1191-1200`). Pi API: `deliverAs: "steer" | "followUp" | "nextTurn"` queue on the agent loop, drained at tool/turn boundaries (`pi-coding-agent/dist/core/agent-session.d.ts:401-422`; `pi-agent-core/dist/agent.d.ts:84-94` steer = "injected after the current assistant turn finishes"). Inline renderer only (`ui/inline-message.ts:6`). No human-draft interference. -- Targeting: `to` = name, full session ID, or ID prefix; `cwd` scoping via `resolveTargetInCwd` (`project-agent.ts:188-240`). Presence name = `pi.getSessionName()` else runtime alias `subagent-chat-<id[:18]>` (`index.ts:494-512`). **Stock edxeth never sets `PI_SUBAGENT_INTERCOM_SESSION_NAME`** (git grep empty) → child is only addressable by its list/status ID or whatever its Pi session name resolves to; runtime aliases are excluded from queued-mail reconnection (README). Compatibility is *probable from source, not asserted by edxeth*. -- Auto-exit effect: intercom has no auto-exit awareness (only PI_SUBAGENT_* orchestration envs, `index.ts:42-51`). A triggered turn in an `auto-exit: true` child ends with close (stopReason≠aborted); Escape/abort leaves it open. So intercom works only while the pane is live; follow-ups after close should use `subagent_resume`. -- pi-messenger/pi-messenger-bridge: listed in `settings.json:107` but NOT in installed `node_modules` → **unverified**. - -## 3. pi-schedule-prompt (installed): yes — tool-only add/list/remove suffices - -- `add` binds current session by default: `const session = getDefaultScope() === "session" ? ctx.sessionManager.getSessionId() : undefined;` (`src/tool.ts:88-89`; default `"session"` at `tool.ts:17`, `index.ts:79`). -- Single-session firing: `isLoadedFor = !job.session || job.session === sessionId` (`src/scheduler.ts:61-62`), re-checked at fire (`scheduler.ts:211`). -- No-model inline: `this.pi.sendUserMessage(job.prompt, { deliverAs: "followUp" })` (`scheduler.ts:243`) — wakes only the owning session. -- Cleanup: `session_shutdown` → `autoCleanupDisabledJobs` (own/unbound disabled only) + `cleanupSession` stops scheduler/hides widget (`index.ts:105-133,143-145`). No duplication on reload: `initializeSession` calls `cleanupSession(ctx)` first — "Without this, every `session_start` (fires on reload/resume/fork too…) leaks a live croner timer… accumulating duplicate fires" (`index.ts:86-90`); non-startup `session_start` also sweeps disabled (`index.ts:136-140`). Storage: project-local `.pi/schedule-prompts.json` (`storage.ts:12-19`). - -## Verdict / minimal integration - -All three are source-verified; nothing executed (report only — no messages/jobs/panes). Minimal test: (a) in a live stock-edxeth child, `intercom({action:"list"})` from parent and send to the child's listed ID while idle and while thinking; confirm no draft/Enter artifacts; (b) `schedule_prompt` add (inline, no model) → list → remove in the parent session, then close/reopen to confirm no double-fire and disabled-job sweep. Use intercom for live steering (child pane open), `subagent_resume` for follow-ups after close; prefer a small script/prototype over framework. diff --git a/slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md b/slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md deleted file mode 100644 index 0008068..0000000 --- a/slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -requested_model: deepseek/deepseek-v4-pro-0813 -mode: code review -input: src/worker.ts, src/supervisor-runtime.ts -trace: omitted from git (11 MB raw provider transcript) -generated: 2026-09-06T04:44:52.809370+00:00 ---- - -# MoA fragility review - - -Decision: reject the current fix and replace duplicate async lifecycle state with one synchronous worker tool. - -Strongest objection: if a truly synchronous worker RPC is unavailable, this simplification blocks the intended parallel supervision model. - -Next check: read the goal-worker tool implementation and the three failing test transcripts before deleting code. - -Smallest recommended architecture: - -The supervisor extension must not store worker lifecycle state. Lifecycle is owned by the subagent runtime. Move ownership into one tool boundary. - -1. Delete NESTED_STATE persistence, event listeners, pending reconciliation, CheckWorkerState, and the replacement guard from supervisor-runtime.ts. -2. Add a single supervisor tool: - - RunGoalWorker: starts and awaits a goal-worker synchronously, using the aggregate output as a tool result. - - Keep one in-memory boolean `workerRunning`, guarded at tool execute start, not relying on event ordering. -3. If that synchronous tool cannot be supported: - - StartGoalWorker returns a run ID as ordinary tool output. - - WaitGoalWorker(runId) blocks on terminal status check. - - ApproveGoal always calls bg_wait on the ID from StartGoalWorker or WaitGoalWorker; otherwise approval fails. - -Because existing failure 2 came from the runtime blocking on a mismatched ID, the important property is: -- an ID not produced by StartGoalWorker/WaitGoalWorker may not be used for bg_wait; -- a failed wait must clear any in-process guard immediately; -- an await cover failure must be treated as a terminal error, not as `pending`. - -Exact deletions/changes: - -In `src/supervisor-runtime.ts`: -- Remove `NESTED_STATE`, `NestedState`, `nested`, `persist`, `targetRun`, `completeNested`, all `subagent:async-*`, process-terminal listeners, and `retainedRunState` reconciliation. -- Remove `pi.events.on("tool_call")` blocks. Replace with allow/deny only: deny edit/write, allow read-only bash, allow RunGoalWorker, allow bg_wait, allow ApproveGoal, deny subagent action tools. -- Replace CheckWorkerState with nothing. State inspection is only through normal async progress updates. -- ApproveGoal asserts no active await cover currently exists from RunGoalWorker or WaitGoalWorker, processWorkState is idle, worktree is clean, and evidence inspection claims are backed by the actual tool result from RunGoalWorker. - -In `src/worker.ts`: -- Drop `retainedRunState` and any pending-closure logic. -- Keep `asyncSnapshot` only for processWorkState, if needed. - -Why this removes fragility: -- Duplicate state is gone. -- Lifecycle is only stored in the runtime’s tool execution stack. -- Revival cannot resurrect a wrong worker ID unless a new tool starts it. -- Race between event handler and spawn disappears because Start or Wait returns a result synchronously to the model. - -Why this may be worse: -- Synchronous wait loses the supervisor's ability to issue corrections inline during progress. -- Parallel instrumented runs cannot be sustained within one tool without exposing `bg_wait` to the model. -- If the model calls WaitGoalWorker with an incorrect ID, it will now fail directly, but the failure must not be caught and retried with a cached ID. - -Acceptance test to catch all observed failures: -- Send the supervisor script: `StartGoalWorker` → `WaitGoalWorker(id)` → `RunGoalWorker(correction)` → `ApproveGoal`, where a midway kill drops the terminal event and forces session revival, and then assert the code path stores no `NESTED_STATE`, does not even mention it in the extension memory, and either the worker returns a tool result or the revived session remains in the same `WaitGoalWorker` tool with no retry on an ID not yielded by that tool. - -## Completion - -- outcome: `completed_after_follow_up` -- trace: omitted from git (11 MB raw provider transcript); this file preserves the complete review answer diff --git a/slop/reviews/20260906_foreground-worker-review.md b/slop/reviews/20260906_foreground-worker-review.md deleted file mode 100644 index 7edda2c..0000000 --- a/slop/reviews/20260906_foreground-worker-review.md +++ /dev/null @@ -1,20 +0,0 @@ -## Review - -No issues found. - -- Correct: The packaged worker is discoverable in pi-subagents 0.65.1 child-safe fanout. `package.json` exposes `pi.subagents.agents`, which the installed discovery code consumes (`pi-subagents/src/agents/agents.ts:510-538,597-657`), while the child fanout executor uses normal `discoverAgents` (`pi-subagents/src/extension/fanout-child.ts:145-190`). -- Correct: The supervisor gate requires the exact packaged agent, nonempty task, `async:false`, `context:"fork"`, and the configured model with no extra fields (`src/supervisor-runtime.ts:83-108`). The installed executor honors explicit foreground mode (`pi-subagents/src/runs/foreground/subagent-executor.ts:6511-6515,6917-6920`). -- Correct: Foreground completion is tied to the real `tool_result`. `activeWorkerCalls` is removed only when that result arrives, successful completion is recorded, and approval requires a later turn (`src/supervisor-runtime.ts:75-115,132-138`). Same-message worker launch plus approval is independently rejected by inspecting the assistant message. -- Correct: Stale local launch reservations self-heal: errors clear on `tool_result`, and `turn_start` clears any reservation for which no result hook arrived (`src/supervisor-runtime.ts:75-115`). The tests cover duplicate launch, failed-result recovery, and next-turn recovery (`test/supervisor-runtime.test.ts:57-76`). -- Correct: `CompleteGoal` remains blocked while the retained supervisor is pending, while any subagent/process work is active or unknown, or until a matching approval checkpoint exists (`src/index.ts`, `CompleteGoal`). Foreground nested work therefore cannot race sign-off because its containing supervisor run remains pending. -- Correct: `supervisor-runtime.ts` does not perform runtime-agent registration. The main extension exits in child processes through `isSupervisorProcess`, while installed pi-subagents itself is inert when `PI_SUBAGENT_CHILD=1` (`src/index.ts`, `isSupervisorProcess`; installed `pi-subagents/index.ts:3-8`). -- Correct: The former nested async worker ID/pending lifecycle is absent. The remaining `workerRunId`/`workerPending` state belongs only to the retained supervisor lifecycle, matching the documented topology. - -Residual risks: -- `test/package-agent.test.ts` verifies packaging statically rather than launching the packaged worker through the real child-safe fanout runtime. The installed 0.65.1 source supports the configuration, but retaining an RPC integration check is advisable. -- The focused approval tests mock Pi’s `tool_call`/`tool_result` ordering. A real RPC test remains the strongest guard against upstream lifecycle-event changes. -- Tests were inspected but not executed in this review environment; the supervisor should run `npm test`, `npm run typecheck`, and `npm run lint`. - -- Merge verdict: **OK with residual test-environment risks.** - --- PI[reviewer/gpt-5.6-sol] diff --git a/slop/reviews/20260908-force-initial-validation.txt b/slop/reviews/20260908-force-initial-validation.txt deleted file mode 100644 index 6d79a90..0000000 --- a/slop/reviews/20260908-force-initial-validation.txt +++ /dev/null @@ -1,105 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --run test/force-approval-flow.test.ts test/approval.test.ts test/supervisor-session.test.ts test/goals-flow.test.ts --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/approval.test.ts > hashes only the current goal, excluding the log, interview, and their historical goal text 6ms -stdout | test/force-approval-flow.test.ts > force ApproveGoal -> CompleteGoal accepts only the reviewed dirty state without committing or modifying it -Force UAT: paired real handlers accepted unchanged tracked + untracked dirty content; Git HEAD and user files stayed unchanged. - - ✓ test/force-approval-flow.test.ts > force ApproveGoal -> CompleteGoal accepts only the reviewed dirty state without committing or modifying it 108ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 41ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 20ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 35ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed tracked content 99ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 24ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 25ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 23ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 64ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/goals-flow.test.ts > restores working linkage even when its remembered model is unavailable, and supports explicit recovery 24ms - ✓ test/goals-flow.test.ts > restores planning linkage even when its remembered model is unavailable, and supports explicit recovery 23ms - ✓ test/goals-flow.test.ts > shows a missing resumed supervisor, pauses writes, and automatically unpauses when that peer returns 25ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed untracked content 82ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed new untracked 83ms - ✓ test/goals-flow.test.ts > times out stale Ready retries in five seconds, without replacing the pane automatically 35ms - ✓ test/goals-flow.test.ts > explicitly restarts only the tracked pane, keeps the plan, and invalidates old approval binding 24ms - ✓ test/goals-flow.test.ts > does not persist startup results or launch work after session shutdown 21ms - ✓ test/goals-flow.test.ts > keeps a failed Ready model not-ready and recovers the same real supervisor binding 26ms - ✓ test/goals-flow.test.ts > points a present-but-paused peer recovery at the supervisor pane 17ms - ✓ test/goals-flow.test.ts > clear during the initial Ready wait cancels immediately and cannot resurrect the plan 21ms - ✓ test/goals-flow.test.ts > clear before the launcher resolves rejects late pane callbacks without restoring the binding 21ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed deleted untracked 62ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed index only 65ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed rename 74ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed HEAD 80ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed goal 79ms - ✓ test/force-approval-flow.test.ts > force does not bypass the evidence gate 44ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 10ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 43ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 7ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 2ms - ✓ test/force-approval-flow.test.ts > force does not bypass the verification gate 47ms - ✓ test/force-approval-flow.test.ts > force does not bypass the stopped view gate 29ms - ✓ test/force-approval-flow.test.ts > force does not bypass the tool call gate 21ms - ✓ test/force-approval-flow.test.ts > force does not bypass the unknown tracker gate 27ms - ✓ test/force-approval-flow.test.ts > force does not bypass the active tracker gate 28ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 98ms - ✓ test/supervisor-session.test.ts > blocks the general intercom actuator even if enabled after startup 2ms - ✓ test/supervisor-session.test.ts > keeps a supervisor unready after model restoration failure, then recovers explicitly without substituting a model 2ms - ✓ test/supervisor-session.test.ts > warns once on unavailable usage but stays quiet for Pi's post-compaction null token sample 3ms - - Test Files 4 passed (4) - Tests 45 passed (45) - Start at 06:14:52 - Duration 1.54s (transform 492ms, setup 0ms, import 2.27s, tests 1.58s, environment 0ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -src/approval.ts:42:7 lint/suspicious/noImplicitAnyLet ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × This variable implicitly has the any type. - - 40 │ const files = paths.map((path): WorktreeSnapshot["files"][number] => { - 41 │ const fullPath = join(repoRoot, path); - > 42 │ let stat; - │ ^^^^ - 43 │ try { stat = lstatSync(fullPath); } - 44 │ catch (error) { - - i Variable declarations without type annotation and initialization implicitly have the any type. Declare a type or initialize the variable with some value. - - -src/approval.ts:56:12 lint/suspicious/noAssignInExpressions ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × The assignment should not be in an expression. - - 54 │ const buffer = Buffer.alloc(256 * 1024); - 55 │ let bytes: number; - > 56 │ while ((bytes = readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, bytes)); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 57 │ } finally { closeSync(fd); } - 58 │ return { path, kind: "file", mode, contentHash: hash.digest("hex") }; - - i The use of assignments in expressions is confusing. - Expressions are often considered as side-effect free. - - -Checked 33 files in 39ms. No fixes applied. -Found 2 errors. -check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Some errors were emitted while running checks. - - diff --git a/slop/reviews/20260908-force-validation.txt b/slop/reviews/20260908-force-validation.txt deleted file mode 100644 index 74a5fd1..0000000 --- a/slop/reviews/20260908-force-validation.txt +++ /dev/null @@ -1,132 +0,0 @@ -Before validation: pre-existing dirty evidence checksums -a8f1facd16fd6ff2938195a7adbdc3b4b1e1dea61533e90a39e5eb18d201e190 slop/reviews/review-fixes-native/supervisor-events.jsonl -de5bb7171e71508c40e19f3929303a63f1fa0f2ea7789a2bcc8728ea038310d2 slop/reviews/review-fixes-native/worker-events.jsonl - -Command: env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE -u PI_GOALS_EVIDENCE_DIR npm test -- --reporter=verbose - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 3ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 2ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 1ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 0ms - ✓ test/worker-view.test.ts > keeps human direction and source location while sending only new messages 3ms - ✓ test/worker-view.test.ts > restarts after compaction and does not report historical tool calls as active 1ms - ✓ test/worker-view.test.ts > bounds serialized Unicode and quoted logs while marking omissions 3ms -stdout | test/role-models.test.ts > remembers each role without automatic switching overwriting another role -Role preferences restored: planning=planner, worker=small-worker, supervisor=astra; restore events did not overwrite the choice. - - ✓ test/role-models.test.ts > remembers each role without automatic switching overwriting another role 9ms - ✓ test/role-models.test.ts > fails on an unavailable remembered model without replacing the choice 4ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 15ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 5ms - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 2ms - ✓ test/intercom.test.ts > does not acknowledge a synchronous handoff failure, and retries the instruction 1ms - ✓ test/intercom.test.ts > detaches a completed binding and ignores its late advice without replay errors or false acceptance 1ms - ✓ test/background.test.ts > reports tracked running work, rather than equating idle agent with finished jobs 110ms - ✓ test/background.test.ts > distinguishes missing providers from an unavailable installed tracker 4ms - ✓ test/intercom-handshake.test.ts > re-handshakes unchanged peers in either direction without hello ping-pong or lost advice 11ms - ✓ test/intercom-handshake.test.ts > replays pending advice and views across either role's own readiness transition 1ms - ✓ test/intercom-handshake.test.ts > cancels pending waits immediately on detach or reconfiguration 2ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 8ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 55ms - ✓ test/approval.test.ts > fingerprints literal unusual paths, binary bytes, symlink targets, modes and deletions 177ms - ✓ test/approval.test.ts > hashes only the current goal, excluding the log, interview, and their historical goal text 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 0ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms - ✓ test/fold.test.ts > does not show historical Log subtasks under the last active goal 0ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 2ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 1ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ignores a historical duplicate below the Log and leaves it unchanged 0ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 51ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 25ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - -stdout | test/force-approval-flow.test.ts > force ApproveGoal -> CompleteGoal accepts only the reviewed dirty state without committing or modifying it -Force UAT: paired real handlers accepted unchanged tracked + untracked dirty content; Git HEAD and user files stayed unchanged. - - ✓ test/force-approval-flow.test.ts > force ApproveGoal -> CompleteGoal accepts only the reviewed dirty state without committing or modifying it 124ms - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 425ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 27ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 28ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 20ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 22ms - ✓ test/force-approval-flow.test.ts > keeps ordinary clean approval unchanged and rechecks runtime state at forced completion 104ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 74ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/goals-flow.test.ts > restores working linkage even when its remembered model is unavailable, and supports explicit recovery 27ms - ✓ test/goals-flow.test.ts > restores planning linkage even when its remembered model is unavailable, and supports explicit recovery 26ms - ✓ test/goals-flow.test.ts > shows a missing resumed supervisor, pauses writes, and automatically unpauses when that peer returns 25ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed tracked content 78ms - ✓ test/goals-flow.test.ts > times out stale Ready retries in five seconds, without replacing the pane automatically 32ms - ✓ test/goals-flow.test.ts > explicitly restarts only the tracked pane, keeps the plan, and invalidates old approval binding 31ms - ✓ test/goals-flow.test.ts > does not persist startup results or launch work after session shutdown 27ms - ✓ test/goals-flow.test.ts > keeps a failed Ready model not-ready and recovers the same real supervisor binding 32ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed untracked content 76ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed new untracked 70ms - ✓ test/goals-flow.test.ts > points a present-but-paused peer recovery at the supervisor pane 21ms - ✓ test/goals-flow.test.ts > clear during the initial Ready wait cancels immediately and cannot resurrect the plan 27ms - ✓ test/goals-flow.test.ts > clear before the launcher resolves rejects late pane callbacks without restoring the binding 19ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed deleted untracked 61ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed index only 63ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed index contents 122ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed rename 73ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed HEAD 67ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 9ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 26ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 77ms - ✓ test/supervisor-session.test.ts > blocks the general intercom actuator even if enabled after startup 2ms - ✓ test/supervisor-session.test.ts > keeps a supervisor unready after model restoration failure, then recovers explicitly without substituting a model 2ms - ✓ test/supervisor-session.test.ts > warns once on unavailable usage but stays quiet for Pi's post-compaction null token sample 3ms - ✓ test/force-approval-flow.test.ts > invalidates forced approval after changed goal 86ms - ✓ test/force-approval-flow.test.ts > force does not bypass the evidence gate 51ms - ✓ test/force-approval-flow.test.ts > force does not bypass the verification gate 42ms - ✓ test/force-approval-flow.test.ts > force does not bypass the stopped view gate 24ms - ✓ test/force-approval-flow.test.ts > force does not bypass the tool call gate 25ms - ✓ test/force-approval-flow.test.ts > force does not bypass the unknown tracker gate 25ms - ✓ test/force-approval-flow.test.ts > force does not bypass the active tracker gate 27ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1856ms -stdout | test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session -Native Pi pair: fork retained planning context; SteerWorker delivered exactly: Read the real outputs before declaring completion. - - ✓ test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session 2941ms - - Test Files 18 passed (18) - Tests 85 passed (85) - Start at 06:18:03 - Duration 3.48s (transform 3.77s, setup 0ms, import 7.89s, tests 7.45s, environment 2ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 33 files in 68ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - - -After validation: pre-existing dirty evidence checksums -a8f1facd16fd6ff2938195a7adbdc3b4b1e1dea61533e90a39e5eb18d201e190 slop/reviews/review-fixes-native/supervisor-events.jsonl -de5bb7171e71508c40e19f3929303a63f1fa0f2ea7789a2bcc8728ea038310d2 slop/reviews/review-fixes-native/worker-events.jsonl diff --git a/slop/reviews/20260908-handshake-boundary-initial-validation.txt b/slop/reviews/20260908-handshake-boundary-initial-validation.txt deleted file mode 100644 index a6b8212..0000000 --- a/slop/reviews/20260908-handshake-boundary-initial-validation.txt +++ /dev/null @@ -1,122 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 3ms -stdout | test/role-models.test.ts > remembers each role without automatic switching overwriting another role -Role preferences restored: planning=planner, worker=small-worker, supervisor=astra; restore events did not overwrite the choice. - - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 4ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 2ms - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 1ms - ✓ test/intercom.test.ts > does not acknowledge a synchronous handoff failure, and retries the instruction 1ms - ✓ test/intercom.test.ts > detaches a completed binding and ignores its late advice without replay errors or false acceptance 1ms - ✓ test/role-models.test.ts > remembers each role without automatic switching overwriting another role 32ms - ✓ test/role-models.test.ts > fails on an unavailable remembered model without replacing the choice 5ms - ✓ test/intercom-handshake.test.ts > re-handshakes unchanged peers in either direction without hello ping-pong or lost advice 9ms - ✓ test/intercom-handshake.test.ts > replays pending advice and views across either role's own readiness transition 2ms - ✓ test/intercom-handshake.test.ts > cancels pending waits immediately on detach or reconfiguration 2ms - ✓ test/worker-view.test.ts > keeps human direction and source location while sending only new messages 3ms - ✓ test/worker-view.test.ts > restarts after compaction and does not report historical tool calls as active 1ms - ✓ test/worker-view.test.ts > bounds serialized Unicode and quoted logs while marking omissions 3ms - ✓ test/approval.test.ts > hashes only the current goal, excluding the log, interview, and their historical goal text 8ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 2ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 2ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 0ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 3ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 51ms - ✓ test/background.test.ts > reports tracked running work, rather than equating idle agent with finished jobs 52ms - ✓ test/background.test.ts > distinguishes missing providers from an unavailable installed tracker 5ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 2ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms - ✓ test/fold.test.ts > does not show historical Log subtasks under the last active goal 0ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 2ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 0ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 0ms - × test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 5ms - → expected '# Plan\n\n## Goals\n\n1. [/] goal: Im…' to be null - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 254ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 59ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 33ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 27ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 38ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 41ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 27ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 61ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/goals-flow.test.ts > restores working linkage even when its remembered model is unavailable, and supports explicit recovery 21ms - ✓ test/goals-flow.test.ts > restores planning linkage even when its remembered model is unavailable, and supports explicit recovery 23ms - ✓ test/goals-flow.test.ts > shows a missing resumed supervisor, pauses writes, and automatically unpauses when that peer returns 26ms - ✓ test/goals-flow.test.ts > times out stale Ready retries in five seconds, without replacing the pane automatically 29ms - ✓ test/goals-flow.test.ts > explicitly restarts only the tracked pane, keeps the plan, and invalidates old approval binding 24ms - ✓ test/goals-flow.test.ts > does not persist startup results or launch work after session shutdown 23ms - ✓ test/goals-flow.test.ts > keeps a failed Ready model not-ready and recovers the same real supervisor binding 31ms - ✓ test/goals-flow.test.ts > points a present-but-paused peer recovery at the supervisor pane 21ms - ✓ test/goals-flow.test.ts > clear during the initial Ready wait cancels immediately and cannot resurrect the plan 25ms - ✓ test/goals-flow.test.ts > clear before the launcher resolves rejects late pane callbacks without restoring the binding 21ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 8ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 21ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 96ms - ✓ test/supervisor-session.test.ts > blocks the general intercom actuator even if enabled after startup 3ms - ✓ test/supervisor-session.test.ts > keeps a supervisor unready after model restoration failure, then recovers explicitly without substituting a model 2ms - ✓ test/supervisor-session.test.ts > warns once on unavailable usage but stays quiet for Pi's post-compaction null token sample 3ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1541ms -stdout | test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session -Native Pi pair: fork retained planning context; SteerWorker delivered exactly: Read the real outputs before declaring completion. - - ✓ test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session 2503ms - -⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ - - FAIL test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line -AssertionError: expected '# Plan\n\n## Goals\n\n1. [/] goal: Im…' to be null - -- Expected: -null - -+ Received: -"# Plan - -## Goals - -1. [/] goal: Implement the cache layer - - tasks: - 1. [x] wire client -2. [x] goal: Ship the docs - -## Log -3. [ ] goal: Ship the docs -" - - ❯ test/tick-goal.test.ts:30:42 - 28| it("returns null when the subject matches more than one goal line", (… - 29| const dup = `${plan}3. [ ] goal: Ship the docs\n`; - 30| expect(tickGoal(dup, "Ship the docs")).toBeNull(); - | ^ - 31| }); - 32| }); - -⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ - - - Test Files 1 failed | 16 passed (17) - Tests 1 failed | 65 passed (66) - Start at 19:22:13 - Duration 2.84s (transform 2.51s, setup 0ms, import 5.72s, tests 5.21s, environment 2ms) - diff --git a/slop/reviews/20260908-handshake-fixes.md b/slop/reviews/20260908-handshake-fixes.md deleted file mode 100644 index 3363eda..0000000 --- a/slop/reviews/20260908-handshake-fixes.md +++ /dev/null @@ -1,42 +0,0 @@ -# Post-review P1–P5 fixes — Pi/OpenAI implementation worker - -Baseline: `1668c94`. Independent review: [attributed, preserved source](20260908-independent-post-fix-review.md), reviewer run `bdb93a2e-52f4-4a6d-bac2-7c9eb48118b5`. - -Read AGENTS.md and installed Pi extension docs for lifecycle teardown, commands, model selection and compaction before editing. No user pane/session was operated; tests use isolated mocks/brokers/native Pi processes. No nested delegation or push. - -## Dispositions - -- **P1:** Replaced change-gated hello replies with an explicit request/reply bit. Every request receives one reply even when the peer state is unchanged; a reply never elicits another hello. Each successful exchange retries only still-pending instructions/current view, including after either side's own readiness transition. Repeated wire frames are allowed and deduplicated at the receiving adapter; this is not a new durable-delivery claim. `test/intercom-handshake.test.ts` wires **two real GoalIntercom adapters**, not an auto-ready peer. It checks repeated worker and supervisor reconfiguration, concurrent reset (four hello frames), single-sided reset (two frames), exact advice handoff, own-ready pause/resume, pending advice/view replay, and reconnect deduplication. Both peers must load the updated transport; mixed-version reconnect is not claimed supported. -- **P2:** The worker configures not-ready during startup/recovery. Startup can await peer readiness without claiming implementation readiness. It announces ready only after worker model restoration and the phase transition to working. Model failures remain not-ready. The Ready/retry flow test uses the same two real adapters and proves unavailable worker model → not-ready → `/model` + reconnect → Ready → working on the **same** binding/pane, followed by successful advice after healthy reconnect. Planning reconnect alone does not authorize implementation. -- **P3:** A known peer with incomplete readiness is distinguished from an absent peer. Guidance points to the supervisor pane's compaction/model diagnostics and `/model` + `/goals reconnect`, rather than treating every pause as disconnection. Regression checks widget and prompt guidance. -- **P4:** `src/plan.ts` now owns the single goal-line and Log-fold definitions. Widget scanning, subtasks, goal ticking and approval use the same current-plan boundary. Tick still rejects duplicate active-region matches; historical Log copies remain byte-for-byte unchanged. The existing approval/sign-off flow now includes duplicate/historical goal lines in the Log, succeeds and ends the active plan instead of reopening historical goals. -- **P5:** Detach/reconfigure immediately reject old readiness waiters. A small Ready-attempt identity plus plan-version guard invalidates asynchronous startup results on clear/recovery/replacement; stale menu/editor responses are also ignored. Regressions clear while the five-minute initial wait is pending (no clock advance needed to settle), then advance five minutes and verify no resurrection; a second test clears before the launcher callback resolves and verifies no late binding/pane persistence or work launch. No automatic late-pane kill was added. -- **F8 observability:** One warning per runtime if the entire usage result is unavailable at a settled check. No warning for Pi's ordinary post-compaction `tokens: null` sample. No speculative token estimator, new compaction policy, or change to Pi auto-compaction. Persistent null usage still cannot trigger the custom 100k check. - -## Verification and changed old assertions - -[Full successful commands/output](20260908-handshake-validation.txt): **67 tests passed in 17 files**, followed by successful typecheck, lint, build and `git diff --check`. The exact main-session test command unsets `PI_SUBAGENT_CHILD` and `PI_GOALS_ROLE` and sets a **fresh explicit** `PI_GOALS_EVIDENCE_DIR` to `slop/reviews/handshake-native`. - -Two intermediate failures are preserved, not counted as passes: - -1. [Handshake-focused run](20260908-handshake-initial-validation.txt): 26 passed/1 failed. The old assertion demanded exactly one outbound wire retry after configure+markReady. These now generate separate request/reply exchanges, which can retry the same still-unacked id more than once before its ack. Updated assertion requires at least one retry, every retry's exact id/text, and no further retries after ack. Two-real-adapter tests independently require exactly one user handoff/view callback after duplicate wire delivery. -2. [First full boundary run](20260908-handshake-boundary-initial-validation.txt): 65 passed/1 failed. Its old duplicate-goal fixture appended the duplicate **below `## Log`**, precisely the P4 behavior being corrected. The duplicate-rejection test now inserts the duplicate above the fold and still requires null; an added test requires historical copies below the fold to be ignored and unchanged. - -An intermediate typecheck passed; lint initially flagged import order and a nested assignment. Those were corrected; the full final lint passed with no fixes applied. - -[Native log inspection](20260908-handshake-log-inspection.txt) reads the final worker/supervisor event files, verifies zero error records and empty stderr, and matches the outgoing instruction, worker incoming instruction and adapter ack id `22068654-6e7c-4328-890f-9382b25c8ea7`. The worker user message is exactly `[supervisor] Read the real outputs before declaring completion.` This deterministic native test proves context retention/routing/tool exposure, not autonomous judgment or durable enqueue guarantees. - -## Evidence provenance caveat - -At the first inspection, these two tracked files were **already dirty**: - -- `slop/reviews/review-fixes-native/supervisor-events.jsonl` -- `slop/reviews/review-fixes-native/worker-events.jsonl` - -The inherited `PI_GOALS_EVIDENCE_DIR` pointed there. The first full test run accidentally refreshed them again. Their pre-task uncommitted bytes were not captured, so it is not established that all differences from HEAD were produced by this worker. They are left **unstaged and uncommitted**, not restored over unknown prior edits. The refreshed copies were separately preserved under this worker's output directory, `scratch-refreshed-prior-evidence/`. Final evidence uses only the fresh `handshake-native/` directory and is not mixed with these prior paths. - -## Still open / acceptance limits - -**F3 async enqueue confirmation remains open.** Pi's void `sendUserMessage` wrapper can return before a later async enqueue rejection. An adapter ack is not durable enqueue, model receipt, or execution confirmation. No correlated-receipt protocol was added; the future forced-async-rejection UAT remains required. A synchronous handoff failure still remains unacked for retry. - -Parent/reviewer owns independent post-change review and push. Rendered Herdr acceptance, the complete two-native-session ApproveGoal → CompleteGoal chain, fresh-shell role recovery without launcher environment, useful independent judgment and cost savings remain unproven. Planning/pause shell gates remain trusted-repo guardrails, not a security sandbox. If a pane split finishes after cancellation, it can remain untracked for human inspection; this change prevents stale state resurrection rather than operating a late pane automatically. diff --git a/slop/reviews/20260908-handshake-initial-validation.txt b/slop/reviews/20260908-handshake-initial-validation.txt deleted file mode 100644 index 5e12c54..0000000 --- a/slop/reviews/20260908-handshake-initial-validation.txt +++ /dev/null @@ -1,77 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --run test/intercom-handshake.test.ts test/intercom.test.ts test/goals-flow.test.ts --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/intercom-handshake.test.ts > re-handshakes unchanged peers in either direction without hello ping-pong or lost advice 7ms - ✓ test/intercom-handshake.test.ts > replays pending advice and views across either role's own readiness transition 1ms - ✓ test/intercom-handshake.test.ts > cancels pending waits immediately on detach or reconfiguration 1ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 4ms - × test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 7ms - → expected [ { binding: 'binding', …(4) }, …(1) ] to match object [ { …(2) } ] - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 1ms - ✓ test/intercom.test.ts > does not acknowledge a synchronous handoff failure, and retries the instruction 1ms - ✓ test/intercom.test.ts > detaches a completed binding and ignores its late advice without replay errors or false acceptance 3ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 34ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 18ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 24ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 22ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 18ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 23ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 47ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/goals-flow.test.ts > restores working linkage even when its remembered model is unavailable, and supports explicit recovery 18ms - ✓ test/goals-flow.test.ts > restores planning linkage even when its remembered model is unavailable, and supports explicit recovery 16ms - ✓ test/goals-flow.test.ts > shows a missing resumed supervisor, pauses writes, and automatically unpauses when that peer returns 17ms - ✓ test/goals-flow.test.ts > times out stale Ready retries in five seconds, without replacing the pane automatically 26ms - ✓ test/goals-flow.test.ts > explicitly restarts only the tracked pane, keeps the plan, and invalidates old approval binding 23ms - ✓ test/goals-flow.test.ts > does not persist startup results or launch work after session shutdown 23ms - ✓ test/goals-flow.test.ts > keeps a failed Ready model not-ready and recovers the same real supervisor binding 23ms - ✓ test/goals-flow.test.ts > points a present-but-paused peer recovery at the supervisor pane 18ms - ✓ test/goals-flow.test.ts > clear during the initial Ready wait cancels immediately and cannot resurrect the plan 21ms - ✓ test/goals-flow.test.ts > clear before the launcher resolves rejects late pane callbacks without restoring the binding 35ms - -⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ - - FAIL test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment -AssertionError: expected [ { binding: 'binding', …(4) }, …(1) ] to match object [ { …(2) } ] - -- Expected -+ Received - - [ - { -+ "binding": "binding", -+ "id": "7f4566ea-f2ef-4a14-ad75-e138fc3a26a0", -+ "kind": "steer", -+ "role": "supervisor", -+ "text": "Read the full output.", -+ }, -+ { -+ "binding": "binding", - "id": "7f4566ea-f2ef-4a14-ad75-e138fc3a26a0", -+ "kind": "steer", -+ "role": "supervisor", - "text": "Read the full output.", - }, - ] - - ❯ test/intercom.test.ts:45:76 - 43| resumed.link.markReady(); - 44| await resumed.link.waitReady(); - 45| expect(resumed.fixture.sent.filter(message => message.kind === "stee… - | ^ - 46| resumed.fixture.receive({ binding: "binding", role: "worker", kind: … - 47| resumed.fixture.connect(false); - -⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ - - - Test Files 1 failed | 2 passed (3) - Tests 1 failed | 26 passed (27) - Start at 19:20:05 - Duration 861ms (transform 365ms, setup 0ms, import 583ms, tests 437ms, environment 0ms) - diff --git a/slop/reviews/20260908-handshake-log-inspection.txt b/slop/reviews/20260908-handshake-log-inspection.txt deleted file mode 100644 index df15d8b..0000000 --- a/slop/reviews/20260908-handshake-log-inspection.txt +++ /dev/null @@ -1,8 +0,0 @@ -slop/reviews/handshake-native/worker-events.jsonl: 22 events; 0 error records; stderr 0 bytes -worker: ack received cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc -worker: ack received cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc -worker: in steer 22068654-6e7c-4328-890f-9382b25c8ea7 -worker: [{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}] -slop/reviews/handshake-native/supervisor-events.jsonl: 21 events; 0 error records; stderr 0 bytes -supervisor: out steer 22068654-6e7c-4328-890f-9382b25c8ea7 -supervisor: ack received 22068654-6e7c-4328-890f-9382b25c8ea7 diff --git a/slop/reviews/20260908-handshake-validation.txt b/slop/reviews/20260908-handshake-validation.txt deleted file mode 100644 index 410dbf5..0000000 --- a/slop/reviews/20260908-handshake-validation.txt +++ /dev/null @@ -1,107 +0,0 @@ -+ env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE PI_GOALS_EVIDENCE_DIR=/home/code/.pi/agent/git/github.com/wassname/pi-goals/slop/reviews/handshake-native npm test -- --reporter=verbose - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/background.test.ts > reports tracked running work, rather than equating idle agent with finished jobs 5ms - ✓ test/background.test.ts > distinguishes missing providers from an unavailable installed tracker 47ms - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 3ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 3ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 26ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 2ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 0ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 0ms - ✓ test/worker-view.test.ts > keeps human direction and source location while sending only new messages 3ms - ✓ test/worker-view.test.ts > restarts after compaction and does not report historical tool calls as active 1ms - ✓ test/worker-view.test.ts > bounds serialized Unicode and quoted logs while marking omissions 2ms -stdout | test/role-models.test.ts > remembers each role without automatic switching overwriting another role -Role preferences restored: planning=planner, worker=small-worker, supervisor=astra; restore events did not overwrite the choice. - - ✓ test/role-models.test.ts > remembers each role without automatic switching overwriting another role 14ms - ✓ test/role-models.test.ts > fails on an unavailable remembered model without replacing the choice 5ms - ✓ test/approval.test.ts > hashes only the current goal, excluding the log, interview, and their historical goal text 5ms - ✓ test/intercom-handshake.test.ts > re-handshakes unchanged peers in either direction without hello ping-pong or lost advice 9ms - ✓ test/intercom-handshake.test.ts > replays pending advice and views across either role's own readiness transition 1ms - ✓ test/intercom-handshake.test.ts > cancels pending waits immediately on detach or reconfiguration 2ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 7ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 4ms - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 1ms - ✓ test/intercom.test.ts > does not acknowledge a synchronous handoff failure, and retries the instruction 2ms - ✓ test/intercom.test.ts > detaches a completed binding and ignores its late advice without replay errors or false acceptance 1ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ignores a historical duplicate below the Log and leaves it unchanged 0ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 4ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 2ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms - ✓ test/fold.test.ts > does not show historical Log subtasks under the last active goal 0ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 47ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 24ms - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 236ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 32ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 29ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 26ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 20ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 62ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/goals-flow.test.ts > restores working linkage even when its remembered model is unavailable, and supports explicit recovery 33ms - ✓ test/goals-flow.test.ts > restores planning linkage even when its remembered model is unavailable, and supports explicit recovery 24ms - ✓ test/goals-flow.test.ts > shows a missing resumed supervisor, pauses writes, and automatically unpauses when that peer returns 24ms - ✓ test/goals-flow.test.ts > times out stale Ready retries in five seconds, without replacing the pane automatically 25ms - ✓ test/goals-flow.test.ts > explicitly restarts only the tracked pane, keeps the plan, and invalidates old approval binding 42ms - ✓ test/goals-flow.test.ts > does not persist startup results or launch work after session shutdown 36ms - ✓ test/goals-flow.test.ts > keeps a failed Ready model not-ready and recovers the same real supervisor binding 31ms - ✓ test/goals-flow.test.ts > points a present-but-paused peer recovery at the supervisor pane 17ms - ✓ test/goals-flow.test.ts > clear during the initial Ready wait cancels immediately and cannot resurrect the plan 25ms - ✓ test/goals-flow.test.ts > clear before the launcher resolves rejects late pane callbacks without restoring the binding 19ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 7ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 22ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 1ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1350ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 87ms - ✓ test/supervisor-session.test.ts > blocks the general intercom actuator even if enabled after startup 2ms - ✓ test/supervisor-session.test.ts > keeps a supervisor unready after model restoration failure, then recovers explicitly without substituting a model 2ms - ✓ test/supervisor-session.test.ts > warns once on unavailable usage but stays quiet for Pi's post-compaction null token sample 2ms -stdout | test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session -Native Pi pair: fork retained planning context; SteerWorker delivered exactly: Read the real outputs before declaring completion. - - ✓ test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session 2530ms - - Test Files 17 passed (17) - Tests 67 passed (67) - Start at 19:24:07 - Duration 2.84s (transform 2.36s, setup 0ms, import 6.39s, tests 4.95s, environment 3ms) - -+ npm run typecheck - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - -+ npm run lint - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 32 files in 91ms. No fixes applied. -+ npm run build - -> @wassname2/pi-goals@0.2.2 build -> tsc - -+ git diff --check diff --git a/slop/reviews/20260908-independent-post-fix-review.md b/slop/reviews/20260908-independent-post-fix-review.md deleted file mode 100644 index 6ec5186..0000000 --- a/slop/reviews/20260908-independent-post-fix-review.md +++ /dev/null @@ -1,100 +0,0 @@ -# Post-fix independent bug review — pi-goals supervision recovery - -Scope: changes `2824396..1668c94` (fix commit `325b939` + evidence commit `1668c94`). -Inputs: AGENTS.md, `slop/reviews/20260908-review-fixes.md` (dispositions), `slop/reviews/20260908-independent-supervision-bug-review.md` (original F1–F9), full current sources of `src/{index,intercom,supervisor-session,approval,role-models,herdr}.ts`, changed tests, native evidence logs. -Read-only: no repo edits, no live panes, no messaging. Repro artifacts lived in /tmp only. - -Labels: **[TESTED]** = executed/compiled and observed. **[CODE]** = unambiguous from source. **[INFERENCE]** = depends on runtime behavior I could not observe here. Test passage is treated as evidence of the asserted path only, not as design proof. - -Baseline check: `env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE npx vitest run` → 57/57 pass in 16 files, matching the worker's validation claim. **[TESTED]** - ---- - -## Fix verification F1–F7 - -**F1 (unavailable remembered model aborts restore) — verified fixed. [CODE + repo tests]** -`session_start` now sets `modelError` first, configures the binding and timers independent of model selection, and only then attempts `restoreModel` in try/catch (`src/index.ts:557-580`). On failure: no `setModel`, no ready hello (`configure(..., false)` at :562, `markReady` gated on `!modelError` at :572-573), paused widget (`pauseReason`/`updateWidget` :149-153, 271-276), write/sign-off gating (:453-456, 592-597), human input and read-only diagnosis retained. `/model` saves over the failed role choice because `RoleModels.enter` sets `this.role` before throwing (`src/role-models.ts:29-30`), so `/goals reconnect` then picks up the user's replacement — a deliberate, workable recovery chain. No fallback model is substituted anywhere. - -**F2 (resumed worker silently unsupervised) — verified fixed, with a residual in P3 below. [CODE + repo tests]** -`connected` now requires own-ready + binding + channel + peerReady (`src/intercom.ts:87`); `onConnectionChange` refreshes the widget immediately (:139, :145, :164); a resumed working worker hellos not-ready until its model restores, then `waitReady(5000)` and warns on failure (`src/index.ts:572-577`). Returning peers clear the pause automatically via the hello/`changed` path. Explicit `/goals restart` preserves plan/version and replaces only the tracked pane with a fresh approval binding (test asserts old checkpoint removed, plan unchanged, exactly one close). - -**F3 (inactive-plan steer throws/replays) — lifecycle part fixed; delivery gap honestly open. [CODE + repo tests]** -`detach()` (`src/intercom.ts:72-81`) clears the binding on completion/clear/restart; late steers for a dead binding are dropped at the binding check (:151) without invoking `onSteer` or acking — covered by the new intercom tests. Ack ordering is still handoff-before-ack (:186-193), so a synchronous `sendUserMessage` throw leaves the steer unrecorded/unacked and it retries (test proves retry succeeds). The documented residual is real and correctly **not** claimed fixed: Pi 0.84.1's `sendUserMessage` is a void wrapper over an async enqueue, so an ack can precede an asynchronous enqueue failure, after which the instruction is gone from the supervisor's pending set and never reaches the model — silently. This is a genuine gap a future correlated-receipt protocol should close; the recorded UAT requirement (force an async enqueue rejection) is the right acceptance test. Acceptable as an explicitly-open item, not as a resolved one. - -**F4 (stale pane → repeated 5-minute Ready waits) — verified fixed for the timeout itself. [CODE + repo tests]** -Existing-pane reconnect waits are 5s (`src/index.ts:186`, `:323`); first launch keeps the 300s compaction allowance (:213); failed `pane run` retains and reports the pane ID (:204-208); no automatic pane kill. **However, see P1: in one important sub-scenario the 5s retry fails deterministically even when the supervisor is healthy, so the fix's "retry" guidance does not actually recover there.** - -**F5 (supervisor gets general `intercom` tool) — verified fixed. [CODE + native test]** -`intercom` is in `BLOCKED_TOOLS` (`src/supervisor-session.ts:13`), filtered from active tools at start and bootstrap (:108-110, :146), and blocked at tool-call time even if re-enabled (:170-173). The native pair test asserts the real supervisor model request's tool list excludes `intercom` and `bash`. Note the tool gate uses `terminate: true`, so a blocked call ends the whole supervisor turn — a deliberate-looking but behaviorally stronger choice than the planning gate's plain block. - -**F6 (nested placeholder evidence) — verified fixed as far as claimed. [CODE + repo tests]** -Both the inline and child-bullet paths reject exactly `(empty until sign-off)` case-insensitively (`src/supervisor-session.ts:46,52`); the string matches the template in `src/prompts.ts:97`. Residual (documented as a presence floor): the child scan still accepts *any* deeper-indented nonblank bullet as evidence, and quoted variants like `"(empty until sign-off)"` pass. That is a semantic-judging limit, accurately disclaimed; supervisor judgment remains the real gate. Acceptable. - -**F7 (last-goal hash includes Log/Interview) — verified fixed. [CODE + TESTED via unit tests]** -`goalBlock` now truncates the plan at `## Log` before scanning and ends the block at the next goal line or any `#`/`##` heading, with `trimEnd` (`src/approval.ts:43-58`). `approval.test.ts` covers duplicate goal text in the log, Interview section, next-goal boundary, and real block edits invalidating the hash; the flow test proves a manual log line between ApproveGoal and CompleteGoal no longer invalidates. Fail-closed on upgrade (old hashes need re-approval) is the right direction. One residual asymmetry (P4 below). - ---- - -## New findings - -### P1 — Reconnect/Ready-retry can wedge in a half-open state: peer hello is only re-sent on *perceived* state change (Medium) [TESTED] - -`GoalIntercom.configure()` resets `peer`/`peerReady` (`src/intercom.ts:45-46`) and sends a hello, but the peer replies to a hello only when *its own* view changed (`src/intercom.ts:154-164`: `changed = !this.peer || this.peerReady !== message.ready; if (changed) this.hello()`). There is no periodic hello. If the peer's stored state already matches the incoming hello (same session id, same ready flag), it stays silent — so the side that reconfigured never learns the peer and `connected` stays false forever (until some unrelated broker `session_joined` at :148 happens to trigger a hello). - -Reproduced in isolation (compiled real `src/intercom.ts` from HEAD, two links cross-wired as broker peers, script in /tmp, no repo changes): - -``` -after initial link: worker.connected = true supervisor.connected = true -after worker reconfigure: worker.connected = false supervisor.connected = true -waitReady REJECTED after 201 ms: Supervisor did not become ready through pi-intercom; inspect its pane. -supervisor steer() succeeded (reports sent) -worker delivered steer: null -``` - -Consequences, all reachable through new/changed code paths: - -1. **`/goals reconnect` on a healthy link breaks it.** `src/index.ts:322-323` reconfigures the same binding; if nothing about readiness actually changed, every retry times out after 5s with "Goal recovery failed", and the only in-app escape is `/goals restart` — which closes the *healthy* pane and invalidates the approval binding (`beginReview` deletes checkpoints). -2. **The F1-on-Ready retry fails deterministically.** First Ready: `startSupervisor` completes the hello exchange (worker announced `ready=true` at `beginReview`/`configure`), then `restoreModel("worker")` throws → back to planning. After the user fixes the model, the next Ready takes the existing-pane path (`src/index.ts:184-186`): `configure` resets the worker's peer, hellos `ready=true`, the supervisor sees no change, stays silent, and `waitReady(5000)` times out — even though the supervisor is up and healthy. Every Ready/reconnect retry repeats this. This undercuts the F1/F4 recovery story on exactly the path those fixes target. -3. **Silent steer loss during the window.** While half-open, the supervisor's `connected` is true, so `SteerWorker` "succeeds", but the worker drops the message at `src/intercom.ts:166` (`event.fromSessionId !== this.peer`) with no notification; the steer sits in pending awaiting a changed hello that may never come. The SteerWorker result text ("Receipt and execution are not confirmed") softens but does not surface this. - -Why the tests miss it: the fixture auto-replies to **every** hello with `ready: true` (`test/intercom-fixture.ts:21`), which is precisely the behavior the real peer logic does not have. The new peer-return and reconnect tests therefore cannot observe the change-gated reply. - -Suggested direction (not implemented): reply to a hello whenever the sender may have lost state — e.g. have `configure` retain `peer`/`peerReady` when the binding is unchanged (re-handshake is only needed after a real transport/binding change), or include a monotonic hello generation and reply to any newer/unknown generation. An unconditional reply to every hello also works without ping-pong, because the *response* hello is still change-gated on the receiver's side. - -### P2 — Failed Ready leaves the worker announcing `ready=true` while paused (Low) [CODE] - -In the Ready flow, `beginReview` → `configure(approvalId, "worker", ctx)` defaults to `ready=true` (`src/intercom.ts:43`, `src/index.ts:171`). If `restoreModel("worker")` then throws (`src/index.ts:525` → catch at :537-544), the code rolls back `phase` to planning but never rolls back intercom readiness or detaches. The worker thereafter hellos `ready=true` on any broker event while `modelError` is set; the supervisor sees a ready, connected worker, `SteerWorker` sends successfully, and the worker's `onSteer` throws (`src/index.ts:113-116`) — unacked, error notification per replay. Self-consistent recovery exists (next Ready after `/model` works, modulo P1), so impact is confusing error noise and a misleading readiness signal, not loss. A `markNotReady`/detach in that catch would align the announced state with the pause. - -### P3 — Worker pause message misattributes a *supervisor-side* pause (Low) [CODE] - -`pauseReason()` (`src/index.ts:149-154`) collapses "peer absent" and "peer present but not ready" into one message: "Supervisor disconnected. Run /goals reconnect, or /goals restart…". When the supervisor pane is alive but paused on its own model restoration (`src/supervisor-session.ts:150-158` sets supervisor `modelError`, readiness stays false), the worker-side `/goals reconnect` will hello, get a `ready=false` reply, and time out after 5s — the advised action cannot work; the actual fix is `/model` + `/goals reconnect` in the supervisor pane. The supervisor pane does display its own error notification, and the design keeps both panes visible, so the user has the information — but the worker-side guidance points the wrong way. `peer` set + `peerReady` false is distinguishable from no peer; the message could be too. - -### P4 — `goalBlock` and `tickGoal` now scan different regions (Low) [CODE] - -Post-F7, `goalBlock` only sees the pre-`## Log` region (`src/approval.ts:44`), while `tickGoal` and `scanGoals` still scan the whole file (`src/index.ts:670-677`, :53-60). A goal-shaped line quoted inside the Log (e.g. a pasted checklist) yields: `goalBlock` finds a unique match → approval proceeds and hashes, but `CompleteGoal` → `tickGoal` finds two hits → returns null → sign-off blocked after a successful approval. Fail-closed, requires unusual plan content, and the same class of confusion pre-dates the fix (both sides failed before); noting it because the fix changed the boundary of only one of the two scanners. - -### P5 — Ready catch can resurrect a cleared plan phase after a concurrent `/goals clear` (Low) [CODE/INFERENCE] - -The Ready path awaits up to 300s inside `startSupervisor` (`src/index.ts:213`), and neither `detach()` nor `configure()` wakes `waitReady` waiters (only hellos/shutdown do; `detach`'s clearing of `binding` means its own not-ready hello is ignored by waiters, and the peer's reply is dropped by the now-empty binding check). If the user runs `/goals clear` during that wait, the plan state is cleared; when the wait later times out, the catch at `src/index.ts:537-544` unconditionally sets `phase: "planning"` and persists — yielding `phase: "planning"` with `planVersion: null`, a "drafting goals" widget over no plan, and "No active plan to disconnect" from `/goals clear`. Recoverable via a fresh `/goals <objective>`, and the interleaving requires issuing a command while the Ready select-loop is mid-wait, hence Low. I did not execute this interleaving; it follows from the unconditional catch and the waiter semantics. Guarding the catch on "state still belongs to this Ready attempt" (e.g. approvalId/planVersion unchanged) would close it. - ---- - -## Assessment of documented residuals (not accepted on documentation alone) - -- **F3 durable-delivery gap**: genuine and correctly scoped as open. Concretely, after an acked-but-async-failed enqueue, the supervisor waits indefinitely for a response to an instruction the worker model never saw, with no signal on either side; the recorded future UAT (inject an async enqueue rejection, assert no confirmed-delivery claim and recoverability) is the right bar. Fine to defer; not fine to call resolved — and it isn't. -- **F8 (unknown usage disables 100k compaction)**: the `?? 0` fallback (`src/supervisor-session.ts:187`) silently disables the custom compaction wherever `getContextUsage()` is unavailable, contradicting the AGENTS.md cost design with no user-visible signal. Mitigating factor I verified: the supervisor system prompt (with `planPath`) is re-appended every `before_agent_start` (:175), and approvals/checkpoints live on disk, so a fallback default-compaction does not lose the plan pointer or approval state — the consequence is cost/context-rot drift, not correctness. Still, a one-time "usage unknown; custom compaction inactive" notification would close the observability gap cheaply. Acceptable as a documented limitation; the silence is the weakest part. -- **F9 (planning/pause bash gate holes)**: accurately disclaimed as guardrail-not-sandbox. The paused-diagnostic gate (`src/index.ts:453-456`) intentionally inherits the same heuristic, including the `git diff --ext-diff` external-command hole that requires a pre-existing hostile `.git/config`. Given the threat model (trusted repo, trusted extensions), the README statement is sufficient; a hardened policy remains correctly out of scope. -- **Test-quality caveat**: beyond the fixture issue in P1, the native pair test drives only `SteerWorker`; the two-real-session `ApproveGoal → CompleteGoal` chain is still unexecuted end to end, so the F7 hash boundary and the view-freshness gate are verified only per-side (unit/flow tests) plus one real supervisor tool-list inspection. The disposition states this; I confirm it remains true at 1668c94. - -## What is solid - -- Shutdown/late-startup guards (`src/index.ts:180, 199-203, 210`) and the supervisor's `bootstrapping` `finally` fix are correct; the test proves no late persistence after shutdown. -- Queued pending steers when *own* readiness changes are handled correctly on both roles: republish is gated on `peerReady && this.ready` (`src/intercom.ts:158-162`), pause suppresses republication, and recovery replays exactly the unacked set from session entries. The only hole in this chain is P1's missing trigger. -- Approval binding safety (per-approvalId, head/tree/clean-worktree/goal-block-hash equality, restart invalidating old approvals) is preserved and extended by the restart flow. -- Human recovery availability while paused is real on both roles: input, read-only tools, `/model`, `/goals reconnect|restart|clear` all remain reachable; sign-off and writes fail closed. - -## Summary - -F1–F7 are fixed as claimed, with tests and native evidence matching the dispositions. The significant new finding is **P1**: the change-gated hello reply combined with `configure()`'s peer reset makes `/goals reconnect` and Ready-retry wedge half-open precisely when readiness did not change — including the F1-on-Ready retry scenario the fixes were built for — and steers are silently dropped in that window while `steer()` reports success. P2–P5 are low-severity consistency/guard gaps. Documented residuals F3/F8/F9 are honest; F3's delivery gap and F8's silent compaction-disable remain open items, not fixes. - -Attribution: independent reviewer, run bdb93a2e-52f4-4a6d-bac2-7c9eb48118b5; preserved verbatim by Pi/OpenAI implementation worker. diff --git a/slop/reviews/20260908-independent-supervision-bug-review.md b/slop/reviews/20260908-independent-supervision-bug-review.md deleted file mode 100644 index d4cbec9..0000000 --- a/slop/reviews/20260908-independent-supervision-bug-review.md +++ /dev/null @@ -1,151 +0,0 @@ -# pi-goals supervision bug review — HEAD 2824396 - -> Attribution: independent delegated reviewer, run `5c8c2017-a92f-4a5f-baf6-f441f9b50495`, artifact `supervision-bug-review.md`. Findings below are preserved from that reviewer, not authored by the implementation worker. Implementation dispositions are in `20260908-review-fixes.md`. - -Scope: AGENTS.md, src/{index,intercom,supervisor-session,background,role-models,approval,worker-view,herdr,prompts}.ts and tests. -Priorities: lifecycle/reload, delivery/reconnect, approval safety, autonomy failures. -Method: static review plus targeted checks against the installed `@earendil-works/pi-coding-agent` 0.84.1 and `pi-intercom` 0.13.0 sources, and one live reproduction (F1). Nothing in the repo was modified; repro scripts lived in /tmp. - -Labels: **[TESTED]** = demonstrated by execution or verified against dependency source. **[CODE]** = read directly from pi-goals source; control flow unambiguous. **[INFERENCE]** = depends on behavior I could not observe. - ---- - -## F1 — Working-phase session restore aborts halfway when the remembered role model is unavailable (Medium) [TESTED] - -`src/index.ts:486-495` (`session_start`): - -```ts -if (state.phase) await models.enter(...); // line 488 — can throw -planningContextPending = state.phase === "planning"; -resyncReason = state.phase === "working" ? "New session." : null; -if (state.phase === "working") { - intercom.configure(state.approvalId!, "worker", ctx); // line 492 — skipped on throw - startWorkerTimers(ctx); // line 493 — skipped -} -updateWidget(ctx); // line 495 — skipped -``` - -`RoleModels.enter` (`src/role-models.ts:30-40`) throws `worker model is unavailable...` when the saved `.pi/pi-goals/models/worker.json` names a model that `ctx.modelRegistry.find` can no longer resolve (provider removed, auth expired). That is a realistic state: the feature exists precisely to remember models across sessions, and model availability changes over time. - -Reproduction (executed): mock Pi host, persisted state `{phase: "working", approvalId: "appr-1", planVersion: 1}`, saved worker model `gone/expired`, `modelRegistry.find → undefined`. Result: - -``` -session_start handler threw: worker model is unavailable. Select an available model with /model, then retry. Saved choice was not replaced. -intercom.configure called during working-phase restore: false -``` - -Consequences after the throw: -- Intercom binding is never restored: no hello, `peerReady` stays false, every subsequent `publishWorkerView` silently records views with an empty binding and never publishes them. -- The hourly view timer never starts. -- Widget is not updated. -- On a planning-phase resume, `planningContextPending` is never set, so the planning snapshot is never re-injected. -- Pi catches per-handler errors (`ExtensionRunner.emit`, runner.js:587-601) and routes them to `emitError`, so the user sees at most an extension-error diagnostic. The error text says "then retry", but no code path retries the restore — `session_start` does not re-run when the user picks a new model. - -This overlaps F2: the session resumes looking normal while supervision is dead. - -## F2 — No supervisor liveness check on worker resume; dead supervisor pane is invisible (Medium) [CODE] - -`src/index.ts:486-495`: on resume with `phase === "working"`, the worker calls `intercom.configure` (which sends one hello) and starts timers. There is no `waitReady`, no Herdr pane probe, and no timeout. If the supervisor pane died while the worker session was closed: - -- `GoalIntercom.view()` (`src/intercom.ts:82-88`) records the view and skips publishing because `connected` is false — silently. No notify anywhere on this path. -- `updateWidget` still renders "· supervised" (`src/index.ts:249`). -- The worker system prompt tells the model to "Stop when a goal appears complete so the supervisor can inspect a settled worker view" — it will stop and wait for an approval that can never arrive. `CompleteGoal` then fails with "no matching supervisor approval checkpoint" with no hint that the supervisor is gone. -- The only reconnect wait (`waitReady`) lives in `startSupervisor`, which is unreachable from the working phase: the Ready menu only renders when `state.phase === "planning"` (`src/index.ts:417`). The only recovery is `/goals clear` (drops plan linkage) or `/goals <new objective>` (new plan version, old plan orphaned). There is no "restart supervisor" path that preserves the current plan. - -[INFERENCE] Whether the broker notices the dead pane while the worker is offline is irrelevant here — the worker has no handler for "peer never came back after resume" in either case. - -## F3 — `onSteer` rejection throws before ack: unacked steer replays forever, error notification each reconnect (Medium-low) [CODE] - -`src/index.ts:113-116`: - -```ts -intercom.onSteer = (instruction) => { - if (state.phase !== "working") throw new Error("Worker plan is not active; instruction rejected."); - pi.sendUserMessage(`[supervisor] ${instruction}`, { deliverAs: "steer" }); -}; -``` - -`src/intercom.ts:167-174` (worker steer branch): - -```ts -this.onSteer(message.text!); // throws → everything below skipped -this.received.add(message.id); -this.record("in", message); -this.publish({ ... kind: "received" ... }); -``` - -Because `onSteer` runs before dedupe/record/ack, a steer that arrives when the plan is not active (plan just completed — `publishWorkerView` sets `phase: null` at index.ts:213; or `/goals clear`; or a supervisor that ignores "stop issuing instructions") is: -1. never acked — the supervisor keeps it in `pending` and republishes it on every `changed` hello (`src/intercom.ts:143-149`), so each supervisor reconnect re-fires the throw; -2. never recorded — so the dedupe set can't suppress it; -3. surfaced only as `Goal Intercom error: ... instruction rejected.` notifications in the worker pane; the supervisor's `SteerWorker` result says only "Receipt and execution are not confirmed", so the supervisor model cannot distinguish "rejected" from "lost" and may re-send, producing one error notification per attempt. - -Related at-least-once window: a crash between `pi.sendUserMessage` (persisted) and `record("in", ...)` causes the same `[supervisor] ...` instruction to be delivered twice after resume. Narrow, but the fix is the same: record/ack before invoking `onSteer`, and add a rejection result back to the supervisor instead of throwing. - -## F4 — Stale `supervisorPaneId` makes every Ready retry block for 5 minutes (Medium-low) [CODE] - -`src/index.ts:163-166`: - -```ts -if (state.supervisorPaneId && state.approvalId) { - intercom.configure(state.approvalId, "worker", ctx); - await intercom.waitReady(); // default 300_000 ms, intercom.ts:73 - return; -} -``` - -This reconnect path is taken after a partial `startSupervisor` failure — e.g. `herdr pane split` succeeded (pane id persisted via the `onOpened` callback at index.ts:174-176) but `herdr pane run` failed (`src/herdr.ts:76-84`), or `models.enter("worker")` threw after the supervisor started. The pane is dead or the supervisor process exited, but the retry never asks Herdr whether the pane exists; it blocks the `agent_settled` handler (and therefore the planning menu) until the 5-minute `waitReady` timeout. Every subsequent Ready repeats the 5-minute hang. `/goals clear` recovers (`closeSupervisorPane` tolerates `NOT_FOUND`/`PANE_GONE`, herdr.ts:60-66), but the timeout error message ("inspect its pane") does not say so. - -Also note the same 5-minute blocking wait applies to the supervisor's first-time initial compaction (`supervisor-session.ts:133-160`); a slow compaction of a large fork produces the same opaque worker-side failure, though that path self-heals on retry. - -## F5 — The "read-only" supervisor gets pi-intercom's full `intercom` tool (Low-medium, approval/authority surface) [TESTED against pi-intercom source] - -The supervisor runs with `--no-extensions -e src/index.ts` (`src/herdr.ts:48-58`), so pi-intercom is never an installed extension in the supervisor session, so `GoalIntercom.loadIntercom` (`src/intercom.ts:189-200`) always dynamically imports it. `intercom(api)` executes pi-intercom's full default export, which registers: - -- the `intercom` tool: "Send a message to another pi session running on this machine" (pi-intercom/index.ts:2088), -- `/intercom`, `/intercom-id`, `/alias` commands (pi-intercom/index.ts:2802-2812). - -The supervisor's read-only enforcement filters only `WRITER_TOOLS` (`src/supervisor-session.ts:13`, applied at session_start line ~124 and in bootstrap), so `intercom` remains an active tool for the supervisor model. Effect: the supervisor — prompted as read-only with `SteerWorker`/`ApproveGoal` as its only actuators — can message arbitrary Pi sessions on the machine, including the user's other sessions, outside the auditable SteerWorker channel whose renders the tests assert are visible. Severity depends on how much you trust the supervisor model; the capability contradicts the stated design ("all supervisor thinking and messages should be visible", AGENTS.md). - -Mitigation would be filtering `intercom` (and any other messaging tools) out of the supervisor's active set, or passing a `registerTool` denylist through the `loadIntercom` proxy (it currently only wraps `on`). - -## F6 — `hasEvidenceEntry` accepts placeholder or unrelated nested bullets as evidence (Low) [CODE] - -`src/supervisor-session.ts:41-60`. The inline placeholder `(empty until sign-off)` is rejected, but when the inline value is empty the child scan returns true for *any* deeper-indented bullet with nonblank text — including `- (empty until sign-off)` written as a child bullet, or any stray nested line. So this block passes the gate: - -``` -1. [ ] goal: x - - evidence: - - (empty until sign-off) -``` - -The supervisor model is instructed to actually read the evidence, so this is a heuristic floor rather than the real defense; still, the placeholder check should apply to child bullets too. - -## F7 — Approval goal block for the last goal runs to EOF; tail edits spuriously invalidate approvals (Low) [CODE] - -`goalBlock` (`src/approval.ts:36-53`) slices from the goal line to the next goal line *or EOF*. For the last goal, the block includes `## Log`, `## Interview`, and the Appendix. `hashGoalBlock` therefore changes if the worker appends a manual `## Log` line (which the prompts encourage — `stamp()` exists for that) between `ApproveGoal` and `CompleteGoal`, producing "no matching supervisor approval checkpoint" and forcing a fresh review. Fail-closed, so not a safety bug; it is an availability/UX trap in the normal approve → log → sign-off rhythm. Consider ending the block at the fold (`## Log`) like `foldPlan` does. - -## F8 — Supervisor 100k compaction silently disabled when `getContextUsage` is unavailable (Low) [INFERENCE] - -`src/supervisor-session.ts:141-143`: `if (compacting || (ctx.getContextUsage()?.tokens ?? 0) < COMPACT_AT_TOKENS) return;`. If `getContextUsage()` returns undefined (RPC/print modes or any runtime where it isn't wired), tokens coerce to 0 and the supervisor never self-compacts, contradicting the AGENTS.md cost design ("compacts every 100k"). Pi's own auto-compaction will eventually fire without the custom instructions that protect the plan pointer and approval state. I could not confirm whether `getContextUsage` is ever undefined in the Herdr-pane interactive mode; in the test mock it is explicitly set. - -## F9 — Plan-mode bash gate: residual holes are narrow but worth noting (Low) [CODE/INFERENCE] - -`isPlanningReadOnlyCommand` (`src/index.ts:566-574`) is otherwise tight (blocks pipes/redirects/backticks/`$`, splits on `&&`/`;`, whitelists verbs, special-cases `--output`, `find -delete/-exec...`, mutating `git branch`). Residual issues: - -- The whitelist allows `git log/show/diff` with *arbitrary* flags. `git diff --ext-diff` / `git log --ext-diff` execute the command configured in `diff.external`/`GIT_EXTERNAL_DIFF`. Env-prefix assignments are blocked (the part must start with a whitelisted verb), so this requires a pre-existing malicious `.git/config` in the target repo. [INFERENCE] that any real repo would have this. -- Plan mode blocks only `edit`/`write` by name (`PLAN_MODE_BLOCKED_TOOLS`, index.ts:34). Any other extension's mutating tool (e.g. a `process`/background-task launcher, `apply_patch`-style tools — the supervisor's own `WRITER_TOOLS` list at supervisor-session.ts:13 acknowledges several) is not blocked in plan mode. In the worker session all user extensions are loaded, so this depends on the user's setup. [INFERENCE] - -## Non-findings (checked, working as intended) - -- **Shutdown latch**: `GoalIntercom.stopped = true` on `session_shutdown` looked dangerous for in-process `/resume`, but Pi tears down and re-creates the ExtensionRunner (and therefore all extension instances) on resume/new/fork/reload (agent-session-runtime.js:102-112, agent-session.js:2053-2073). [TESTED against pi source] -- **Handler ordering**: Pi runs handlers sequentially in registration order and awaits them (runner.js:579-604), so `GoalIntercom`'s constructor-registered `session_start` (channel load) completes before index.ts's `configure` → hello. Per-handler errors are swallowed into `emitError` (this is why F1 is silent). [TESTED against pi source] -- **ApproveGoal freshness gates**: the `view === newest.text` equality check is sound — `sendUserMessage` stores the text verbatim (agent-session.js:1106-1133), `latestView` is set before `onView` fires, and any newer queued view makes the comparison fail closed. Worker "stopped"/`backgroundQuiet` are extension-computed (`ctx.isIdle()`, `backgroundState`), not model-controlled, so the worker model cannot spoof a stopped/quiet view. `backgroundState` fails closed ("unknown" → not quiet) when an installed tracker doesn't answer, and distinguishes missing providers (background.ts, covered by test/background.test.ts). -- **Approval checkpoint binding**: head/tree/clean-worktree/goal-block-hash equality plus per-`approvalId` binding and `beginReview`'s deletion of prior approvals make approval replay across plans or commits infeasible; `.pi/plan`, approvals, and model prefs are correctly excluded from the dirtiness check relative to repo root (`approval.ts:20-34`). -- **Steer/view redelivery after restart**: pending steers and `received` dedupe survive restart via session-entry replay (`intercom.ts:54-68`) and are covered by test/intercom.test.ts; ack cursors are monotonic. -- **Two-peer guard**: the "Two peers claim this supervision binding" throw fires before any state mutation and repeats harmlessly; it can wedge only if the broker failed to emit `session_left` for the previous peer, which the broker does send on disconnect/reregister (broker.ts:327,540). - -## Test-coverage gaps relevant to the above - -- No test exercises F1 (resume with unavailable remembered model). -- No test exercises a resumed worker whose supervisor never answers (F2) — the intercom fixture auto-replies to hellos, so `connected` is always true in tests. -- The native end-to-end test (test/native-intercom.test.ts) drives `SteerWorker` only; the `ApproveGoal` → approval file → `CompleteGoal` chain has never run through two real Pi sessions, so F7-class friction and the `view === newest.text` gate are unverified end to end. diff --git a/slop/reviews/20260908-intercom-validation.txt b/slop/reviews/20260908-intercom-validation.txt deleted file mode 100644 index 4217d12..0000000 --- a/slop/reviews/20260908-intercom-validation.txt +++ /dev/null @@ -1,62 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 6ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 3ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 40ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 2ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 0ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 0ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 14ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 7ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 4ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 2ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 0ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 2ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 1ms - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 221ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 38ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 21ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 24ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 20ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 20ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 24ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 49ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 6ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 25ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 58ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1384ms - - Test Files 11 passed (11) - Tests 37 passed (37) - Start at 16:31:10 - Duration 1.67s (transform 1.31s, setup 0ms, import 3.59s, tests 2.00s, environment 1ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 21 files in 67ms. No fixes applied. diff --git a/slop/reviews/20260908-native-intercom-full-validation.txt b/slop/reviews/20260908-native-intercom-full-validation.txt deleted file mode 100644 index 520d269..0000000 --- a/slop/reviews/20260908-native-intercom-full-validation.txt +++ /dev/null @@ -1,81 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 2ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 2ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 1ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 0ms - ✓ test/background.test.ts > reports tracked running work, rather than equating idle agent with finished jobs 3ms - ✓ test/background.test.ts > distinguishes missing providers from an unavailable installed tracker 4ms -stdout | test/role-models.test.ts > remembers each role without automatic switching overwriting another role -Role preferences restored: planning=planner, worker=small-worker, supervisor=astra; restore events did not overwrite the choice. - - ✓ test/role-models.test.ts > remembers each role without automatic switching overwriting another role 7ms - ✓ test/role-models.test.ts > fails on an unavailable remembered model without replacing the choice 4ms - ✓ test/worker-view.test.ts > keeps human direction and source location while sending only new messages 5ms - ✓ test/worker-view.test.ts > restarts after compaction and does not report historical tool calls as active 1ms - ✓ test/worker-view.test.ts > bounds serialized Unicode and quoted logs while marking omissions 3ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 3ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 30ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 6ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 6ms - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 2ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 2ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 0ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 206ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 34ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 21ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 25ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 0ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 23ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 23ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 27ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 163ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 8ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 27ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 3ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 59ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1226ms -stdout | test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session -Native Pi pair: fork retained planning context; SteerWorker delivered exactly: Read the real outputs before declaring completion. - - ✓ test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session 2314ms - - Test Files 15 passed (15) - Tests 46 passed (46) - Start at 16:58:17 - Duration 2.73s (transform 2.47s, setup 0ms, import 5.82s, tests 4.28s, environment 2ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 28 files in 21ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - diff --git a/slop/reviews/20260908-native-intercom-validation.txt b/slop/reviews/20260908-native-intercom-validation.txt deleted file mode 100644 index a5a5c01..0000000 --- a/slop/reviews/20260908-native-intercom-validation.txt +++ /dev/null @@ -1,17 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --run test/native-intercom.test.ts --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - -stdout | test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session -Native Pi pair: fork retained planning context; SteerWorker delivered exactly: Read the real outputs before declaring completion. - - ✓ test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session 2264ms - - Test Files 1 passed (1) - Tests 1 passed (1) - Start at 16:52:40 - Duration 2.43s (transform 26ms, setup 0ms, import 56ms, tests 2.27s, environment 0ms) - diff --git a/slop/reviews/20260908-review-fixes-initial-validation.md b/slop/reviews/20260908-review-fixes-initial-validation.md deleted file mode 100644 index 1320986..0000000 --- a/slop/reviews/20260908-review-fixes-initial-validation.md +++ /dev/null @@ -1,24 +0,0 @@ -# Initial delegated-worker test failure — Pi/OpenAI - -The first implementation check ran `npm run typecheck && npm test` inside the delegated worker's inherited environment (`PI_SUBAGENT_CHILD=1`). Typecheck passed. At that point the suite reported: - -``` -Test Files 2 failed | 13 passed (15) - Tests 8 failed | 38 passed (46) -``` - -Representative actual output from that run (18:39:14): - -``` -FAIL test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only -TypeError: Cannot read properties of undefined (reading 'handler') - at flow.commands.get("goals").handler("first objective", flow.ctx) - -FAIL test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn -Error: Test timed out in 15000ms. -``` - -Diagnosis: the production `isMainSession()` deliberately excludes subagent children. Consequently the mock host never registered `/goals`, and the real RPC test process inherited the child flag and did not register it either. This was not treated as a passing test and no product guard was removed to conceal it. - -Exact corrected test command: `env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE npm test`. -That rerun passed all 46 then-existing tests. Subsequent added regressions also passed. The final complete command and unabridged final output are saved in `20260908-review-fixes-validation.txt`; it uses the same two-variable isolation. All role-specific tests still explicitly configure their intended role. No live user session's environment or settings were changed. diff --git a/slop/reviews/20260908-review-fixes-log-inspection.txt b/slop/reviews/20260908-review-fixes-log-inspection.txt deleted file mode 100644 index 16f67b7..0000000 --- a/slop/reviews/20260908-review-fixes-log-inspection.txt +++ /dev/null @@ -1,4 +0,0 @@ -worker: 21 events parsed; 0 errors; stderr empty -Worker user-message event contains exact instructed text. -supervisor: 21 events parsed; 0 errors; stderr empty -Supervisor outbound/ack ID matched: 780f7dac-e4e5-4351-b890-0684a42de681. This is adapter handling, not durable queue confirmation. diff --git a/slop/reviews/20260908-review-fixes-validation.txt b/slop/reviews/20260908-review-fixes-validation.txt deleted file mode 100644 index 09b72f6..0000000 --- a/slop/reviews/20260908-review-fixes-validation.txt +++ /dev/null @@ -1,93 +0,0 @@ -COMMAND: env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE PI_GOALS_EVIDENCE_DIR="$PWD/slop/reviews/review-fixes-native" npm test -- --reporter=verbose && npm run typecheck && npm run lint && npm run build && git diff --check - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 2ms - ✓ test/approval.test.ts > hashes only the current goal, excluding the log, interview, and their historical goal text 3ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 3ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 0ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 1ms -stdout | test/role-models.test.ts > remembers each role without automatic switching overwriting another role -Role preferences restored: planning=planner, worker=small-worker, supervisor=astra; restore events did not overwrite the choice. - - ✓ test/background.test.ts > reports tracked running work, rather than equating idle agent with finished jobs 3ms - ✓ test/background.test.ts > distinguishes missing providers from an unavailable installed tracker 6ms - ✓ test/role-models.test.ts > remembers each role without automatic switching overwriting another role 7ms - ✓ test/role-models.test.ts > fails on an unavailable remembered model without replacing the choice 4ms - ✓ test/worker-view.test.ts > keeps human direction and source location while sending only new messages 3ms - ✓ test/worker-view.test.ts > restarts after compaction and does not report historical tool calls as active 1ms - ✓ test/worker-view.test.ts > bounds serialized Unicode and quoted logs while marking omissions 3ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 3ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 25ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 5ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 3ms - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 1ms - ✓ test/intercom.test.ts > does not acknowledge a synchronous handoff failure, and retries the instruction 1ms - ✓ test/intercom.test.ts > detaches a completed binding and ignores its late advice without replay errors or false acceptance 1ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 2ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 0ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 2ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 226ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 45ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 21ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 26ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 19ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 22ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 23ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 42ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/goals-flow.test.ts > restores working linkage even when its remembered model is unavailable, and supports explicit recovery 19ms - ✓ test/goals-flow.test.ts > restores planning linkage even when its remembered model is unavailable, and supports explicit recovery 18ms - ✓ test/goals-flow.test.ts > shows a missing resumed supervisor, pauses writes, and automatically unpauses when that peer returns 19ms - ✓ test/goals-flow.test.ts > times out stale Ready retries in five seconds, without replacing the pane automatically 27ms - ✓ test/goals-flow.test.ts > explicitly restarts only the tracked pane, keeps the plan, and invalidates old approval binding 22ms - ✓ test/goals-flow.test.ts > does not persist startup results or launch work after session shutdown 19ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 8ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 28ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 19ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 89ms - ✓ test/supervisor-session.test.ts > blocks the general intercom actuator even if enabled after startup 2ms - ✓ test/supervisor-session.test.ts > keeps a supervisor unready after model restoration failure, then recovers explicitly without substituting a model 2ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1303ms -stdout | test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session -Native Pi pair: fork retained planning context; SteerWorker delivered exactly: Read the real outputs before declaring completion. - - ✓ test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session 2338ms - - Test Files 16 passed (16) - Tests 57 passed (57) - Start at 18:57:11 - Duration 2.57s (transform 1.87s, setup 0ms, import 4.26s, tests 4.46s, environment 2ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 29 files in 28ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - diff --git a/slop/reviews/20260908-review-fixes.md b/slop/reviews/20260908-review-fixes.md deleted file mode 100644 index 332fcd1..0000000 --- a/slop/reviews/20260908-review-fixes.md +++ /dev/null @@ -1,38 +0,0 @@ -# Independent review fixes — Pi/OpenAI implementation worker - -Baseline: `2824396a711ed56bd462742ab22620df44e5624f`. -Original independent review: [preserved review and attribution](20260908-independent-supervision-bug-review.md), run `5c8c2017-a92f-4a5f-baf6-f441f9b50495`. - -The parent approved explicit `/goals reconnect` and `/goals restart`, with human input and read-only diagnosis available while paused. No existing user pane/session was inspected, operated, restarted, or closed. All Herdr recovery operations in this work used mocks. No additional reviewer or subagent was launched by this worker. - -Read AGENTS.md and installed Pi extension documentation covering session replacement/shutdown, sequential events, model selection, input, command context, tool gates, compaction and context usage before editing extension lifecycle code. The native tests use repository-local Pi **0.84.1**; the parent host's 0.85.1 version is not the test executable's version. - -## Finding dispositions - -| Finding | Disposition and concrete proof | -| --- | --- | -| F1: unavailable remembered model aborts restore | Confirmed and fixed. Planning injection and worker binding/timers initialize independently of model selection. A failure leaves an explicit paused widget and gates implementation/sign-off, not a partially initialized normal worker. No fallback model is selected. The worker does not announce readiness until model restoration succeeds. `/model` followed by `/goals reconnect` retries the same plan. Added flow reproductions for both working/planning restoration and supervisor model recovery. Tests assert no `setModel` on unavailable lookup, no ready hello, preserved preferences/binding/version, retained human input, allowed diagnostic reads, blocked writes/sign-off and successful explicit recovery. | -| F2: resumed worker silently has no supervisor | Confirmed and fixed. Connection changes update the widget immediately; absence on restore warns after five seconds. Writes/sign-off are gated while disconnected; human prompts, diagnostic reads and recovery commands remain available. Returning peers clear the connection pause automatically. Explicit restart preserves the working plan and replaces only its tracked pane with a new approval binding. Tests exercise a never-answering peer, later hello, exact owned-pane close, unchanged plan/version, removed old checkpoint, and no automatic pane replacement. | -| F3: inactive-plan steer throws/replays | Confirmed; lifecycle cause addressed without premature acceptance. Completed, cancelled or cleared plans detach their binding and announce not-ready; stale advice is ignored without invoking delivery or acknowledging it. Tests cover completion/clear not accepting advice or restarting, transport detach, and a *synchronous handoff* failure remaining unrecorded/unacked until retry succeeds. **Delivery gap remains:** Pi 0.84.1 `dist/core/agent-session.js:1855–1862` implements ExtensionAPI.sendUserMessage as a void wrapper calling async `this.sendUserMessage(...).catch(emitError)`; `:1106–1133` awaits `prompt` internally. The adapter therefore cannot observe durable enqueue success. An async enqueue error may occur after the ack and suppress replay even though the model never received the instruction. A crash between handoff and recording can instead duplicate it. Pending transport messages retain at-least-once retry semantics, but end-to-end durable/at-least-once/exactly-once model delivery is NOT guaranteed. Recording/acking before even the synchronous handoff would worsen loss and was deliberately NOT implemented. Parent explicitly accepted documenting this deeper limitation rather than expanding the protocol in this commit. README and the code comment distinguish adapter handling from persistence/model receipt/execution. | -| F4: stale pane causes repeated five-minute Ready waits | Confirmed and fixed. Existing-pane Ready/reconnect waits are five seconds, with explicit reconnect/restart guidance. First-time launch retains the five-minute compaction allowance. Failed `pane run` retains the pane ID for inspection and reports it. No automatic probe-driven kill/replacement of a possibly healthy slow supervisor. Planning restart clears its failed tracked pane for the next Ready; working restart forks a replacement immediately. Test simulates split success/run failure and two Ready attempts, and proves one launch, zero automatic closes, and a five-second retry. Startup results arriving after shutdown are not persisted and do not launch worker work. | -| F5: general intercom tool available to supervisor | Confirmed and fixed. `intercom` is removed alongside writing tools before readiness and blocked at tool-call time even if later re-enabled. `SteerWorker` remains available. Both mocked gate tests and the real native pair test assert this; the latter inspects the actual supervisor model request's tool list. | -| F6: nested placeholder evidence accepted | Confirmed and fixed. The same exact `(empty until sign-off)` rejection applies to inline and child bullets. Tests exercise both through `ApproveGoal`, assert no approval file, then confirm actual nonblank evidence still passes. This is a presence floor, not a semantic evidence judge; unrelated nonblank prose still requires supervisor judgment. | -| F7: last goal hash includes Log/Interview | Confirmed and fixed. The canonical block ignores the Log and stops at the next goal or top-level section. Trailing section-separator whitespace is excluded. Unit tests cover log history containing duplicate goal text, interview, next goal, and actual goal edits still invalidating the hash. The flow test appends a manual Log entry between approval and CompleteGoal and still succeeds. Previously recorded hashes may require a fresh approval after upgrade (fail closed). | -| F8: unknown usage disables 100k compaction | Assessed; no speculative estimator added. Installed `agent-session.js:getContextUsage()` returns undefined without a usable model/window and explicitly returns `tokens: null` after compaction until a later assistant usage sample exists. Native RPC tests have valid usage; RPC mode itself is not evidence of permanent unavailability. Existing unknown handling avoids immediate repeat compaction. Persistent unavailable usage still prevents the custom 100k trigger; this remains a documented limitation, not claimed fixed. | -| F9: planning read-only guard residuals | Confirmed as a guardrail limitation, not fixed by broad tool/shell policy changes in this task. Planning still explicitly gates edit/write and selected bash commands, not every installed extension actuator. Git read commands can invoke configured external diff/textconv programs. Paused diagnostic bash uses this same heuristic, so trusted repository configuration and extensions are required; it is NOT a security sandbox. README now states this. A hardened shell policy or comprehensive planning tool allowlist needs separately scoped review; no claim of airtight isolation is made. | - -## Validation and inspected evidence - -- [Full final command/output](20260908-review-fixes-validation.txt): **57 tests passed in 16 files**, typecheck, lint, build and `git diff --check` passed. -- Exact test environment isolation: `env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE PI_GOALS_EVIDENCE_DIR="$PWD/slop/reviews/review-fixes-native" npm test -- --reporter=verbose`. This runs main-session tests as a main session, while explicit role tests still select their role. -- [Initial child-environment failure](20260908-review-fixes-initial-validation.md): recorded the original failure (8 failed/38 passed), cause and exact corrected command. It was not omitted or counted as a pass. -- Inspected [worker native events](review-fixes-native/worker-events.jsonl) and [supervisor native events](review-fixes-native/supervisor-events.jsonl): the worker received `[supervisor] Read the real outputs before declaring completion.`; supervisor out and ack records share the same instruction ID in the saved run. Both stderr files are empty; neither event log contains an error/failed-response/error-notification record. -- Tests preserve human recovery/input while paused, peer-return unpause, and clear/complete not receiving stale advice or reopening supervision. Shutdown guards prevent late startup results from writing the disposed runtime. - -## Remaining acceptance limits - -**Priority residual: F3 durable delivery confirmation is still open.** Required UAT for a future correlated-receipt design: force an asynchronous Pi enqueue rejection after the adapter returns; the supervisor must not report confirmed model delivery and the instruction must remain recoverable. The current receipt does not make that promise. - -These fixes still need the parent's independent post-change review. The native pair uses a deterministic local model and a transport-only worker fixture; it proves routing/context retention and supervisor tool exposure, not good judgment, cost savings, rendered Herdr behavior, or the full two-native-session ApproveGoal → CompleteGoal chain. Full fresh-shell supervisor role restoration without launcher environment remains unverified. An already-created pane can remain for inspection if shutdown interrupts startup; the fix avoids stale context use rather than silently operating such a pane. Background work is not killed by recovery, and unregistered detached work remains outside tracker coverage. - -No changes were pushed by this implementation worker. diff --git a/slop/reviews/20260908-role-model-validation.txt b/slop/reviews/20260908-role-model-validation.txt deleted file mode 100644 index 7fbda5c..0000000 --- a/slop/reviews/20260908-role-model-validation.txt +++ /dev/null @@ -1,73 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 3ms -stdout | test/role-models.test.ts > remembers each role without automatic switching overwriting another role -Role preferences restored: planning=planner, worker=small-worker, supervisor=astra; restore events did not overwrite the choice. - - ✓ test/background.test.ts > reports tracked running work, rather than equating idle agent with finished jobs 2ms - ✓ test/background.test.ts > distinguishes missing providers from an unavailable installed tracker 6ms - ✓ test/role-models.test.ts > remembers each role without automatic switching overwriting another role 7ms - ✓ test/role-models.test.ts > fails on an unavailable remembered model without replacing the choice 6ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 2ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 0ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 0ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 5ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 3ms - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 1ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 2ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 30ms - ✓ test/worker-view.test.ts > keeps human direction and source location while sending only new messages 2ms - ✓ test/worker-view.test.ts > restarts after compaction and does not report historical tool calls as active 0ms - ✓ test/worker-view.test.ts > bounds serialized Unicode and quoted logs while marking omissions 2ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 2ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 0ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 1ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 0ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 0ms - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 222ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 36ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 24ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 26ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 21ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 21ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 19ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 47ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 7ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 22ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 2ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 3ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 5ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 49ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1427ms - - Test Files 14 passed (14) - Tests 45 passed (45) - Start at 16:49:24 - Duration 1.79s (transform 1.51s, setup 0ms, import 4.33s, tests 2.03s, environment 1ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 26 files in 62ms. No fixes applied. diff --git a/slop/reviews/20260908-worker-overview-example.txt b/slop/reviews/20260908-worker-overview-example.txt deleted file mode 100644 index eb833c6..0000000 --- a/slop/reviews/20260908-worker-overview-example.txt +++ /dev/null @@ -1,27 +0,0 @@ -INITIAL VIEW -The worker stopped. - -review trigger: settled -source session: /isolated/worker.jsonl -worker model: test/worker -latest human direction: -Modal does not use our GPU. -tool calls with no result: none -tracked background work: processes: 0; subagents: 0; unregistered work is not tracked - -new worker transcript (initial or reset view): -I am waiting for the local queue. - -AFTER ACKNOWLEDGMENT -The worker stopped. - -review trigger: settled -source session: /isolated/worker.jsonl -worker model: test/worker -latest human direction: -Modal does not use our GPU. -tool calls with no result: none -tracked background work: processes: 0; subagents: 0; unregistered work is not tracked - -new worker transcript since the last acknowledged view: -The command launches a Modal remote GPU; local default stays paused. diff --git a/slop/reviews/20260908-worker-overview-validation.txt b/slop/reviews/20260908-worker-overview-validation.txt deleted file mode 100644 index b2bccd9..0000000 --- a/slop/reviews/20260908-worker-overview-validation.txt +++ /dev/null @@ -1,68 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run --reporter=verbose - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ✓ test/worker-view.test.ts > keeps human direction and source location while sending only new messages 6ms - ✓ test/worker-view.test.ts > restarts after compaction and does not report historical tool calls as active 0ms - ✓ test/worker-view.test.ts > bounds serialized Unicode and quoted logs while marking omissions 2ms - ✓ test/background.test.ts > reports tracked running work, rather than equating idle agent with finished jobs 2ms - ✓ test/background.test.ts > distinguishes missing providers from an unavailable installed tracker 5ms - ✓ test/package-agent.test.ts > package manifest > includes the extension without registering a packaged subagent 2ms - ✓ test/intercom.test.ts > pi-intercom transport > receives exact advice once, acknowledges it and rejects unrelated peers 5ms - ✓ test/intercom.test.ts > pi-intercom transport > restores an unacknowledged steer on reconnect and stops replay after acknowledgment 3ms - ✓ test/intercom.test.ts > pi-intercom transport > advances the incremental overview only after acknowledgment 1ms - ✓ test/intercom.test.ts > pi-intercom transport > cancels a readiness wait on shutdown 1ms - ✓ test/herdr.test.ts > supervisor pane command > forks the planning session with pi-goals owning its Intercom dependency 2ms - ✓ test/herdr.test.ts > supervisor pane command > accepts Herdr's text version output and stale pane cleanup 26ms - ✓ test/prompts.test.ts > planning prompt > requires fact finding or a focused question before a goal 2ms - ✓ test/prompts.test.ts > planning prompt > restores the same rule after compaction 1ms - ✓ test/prompts.test.ts > planning prompt > anchors work and sign-off to the user-visible result 0ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > creates ## Log at EOF when absent 1ms - ✓ test/append-log.test.ts > appendLog (the extension's only plan-file write) > appends after the last existing log line, before any following header 0ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > keeps the title, user voice and goals 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > drops the log, the learnings and the unlimited appendix 1ms - ✓ test/fold.test.ts > foldPlan (current goals are above ## Log; durable memory is below it) > returns the whole plan when there is no ## Log yet (a fresh draft) 0ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > lists the active goal's open and in-progress subtasks, stopping at the next goal 1ms - ✓ test/fold.test.ts > openSubtasks (the widget shows the next action, so the plan IS the task list) > does not leak subtasks from the goal below 1ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > ticks the exact-matching goal line, case-insensitive, leaving subtasks alone 2ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's) 1ms - ✓ test/tick-goal.test.ts > tickGoal (sign-off ticks the goal; agent only ticks on wording drift) > returns null when the subject matches more than one goal line 0ms -stdout | test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker -Intercom broker: readiness confirmed; exact worker view and supervisor advice received. - - ✓ test/intercom-broker.test.ts > exchanges readiness, views and exact advice over a real isolated pi-intercom broker 226ms - ✓ test/goals-flow.test.ts > /goals flow > reports actual idle state, invalidates stopped views on start, and stops completed plans 45ms - ✓ test/goals-flow.test.ts > /goals flow > preserves drafts, records the interview, and keeps planning read-only 17ms - ✓ test/goals-flow.test.ts > /goals flow > forks a visible supervisor on Ready and keeps the main session as worker 20ms - ✓ test/goals-flow.test.ts > /goals flow > starts work only after the supervisor launcher resolves 18ms - ✓ test/goals-flow.test.ts > /goals flow > delivers an Intercom instruction to the worker 22ms - ✓ test/goals-flow.test.ts > /goals flow > closes the supervisor on clear but keeps the plan file 20ms - ✓ test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block 44ms - ✓ test/goals-flow.test.ts > process role > keeps subagent children and visible supervisors out of the worker extension 0ms - ✓ test/supervisor-session.test.ts > visible supervisor session > restores monitoring and read-only tools without replaying persisted views 5ms - ✓ test/supervisor-session.test.ts > visible supervisor session > renders all advice in real Pi tool rows, including collapsed and restored rows 20ms - ✓ test/supervisor-session.test.ts > visible supervisor session > asks for judgment and useful recaps without inventing instructions 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes readiness only after removing writing tools 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > compacts a large planning fork before writing readiness 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > does not become ready when initial compaction fails 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > writes a durable worker instruction 1ms - ✓ test/supervisor-session.test.ts > visible supervisor session > records approval only from a stopped view with evidence and no active work 49ms - ✓ test/rpc-review.test.ts > RPC review flow > opens Refine's editor before it starts the revision turn 1197ms - - Test Files 13 passed (13) - Tests 43 passed (43) - Start at 16:41:18 - Duration 1.43s (transform 927ms, setup 0ms, import 3.00s, tests 1.77s, environment 1ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 24 files in 19ms. No fixes applied. diff --git a/slop/reviews/20260908_supervision-against-user-intent.md b/slop/reviews/20260908_supervision-against-user-intent.md deleted file mode 100644 index 0e68108..0000000 --- a/slop/reviews/20260908_supervision-against-user-intent.md +++ /dev/null @@ -1,72 +0,0 @@ -# Review against user intent - -## Follow-up: code fixes, full goal still unproven - -Implemented directly after the subagent runner failed and the user authorized direct work. The tests now exercise full advice in real Pi tool components (collapsed, expanded, restored, streaming arguments), emitted thinking/text display, resume without replay of persisted views, latest-view coalescing, actual idle/busy status, stale-view approval rejection, and stopping completed-plan timers. The supervisor prompt now asks for a brief evidence-based progress assessment and useful judgment instead of instruction-only reviews. Background job status is explicitly unmeasured; approval still requires the supervisor to inspect job evidence when relevant. - -[Saved validation output](20260908_supervision-fixes-validation.txt): - -> Tests 34 passed (34) -> resumed: deliveredViews=0, activeTools=read, readyReceipt=true -> interval view without any work: The worker stopped. - -Typecheck and lint also succeeded in that log. The reproduction script now asserts the corrected behavior; the original reproduction output below is retained as historical evidence. Readiness is cleared on startup and normal shutdown, but it is not a heartbeat or proof of worker receipt. The review here is my own source/diff review, not the independent review that failed to launch. Existing user panes and the separate pi-supervise worktree were not modified. - -Remaining acceptance: a real isolated two-pane run with the intended model pair, observed useful advice and worker response, plus measured token/cost totals. Prompt assertions do not establish judgment quality. No full-goal completion is claimed. - --- Pi/OpenAI - -## Original review - -Verdict at `06794bf`: not achieved. - -Reviewed `experiment/goals-owned-supervision` at `4ebb4d1` against [AGENTS.md](../../AGENTS.md#user-intent-for-this-branch). This is a source review and isolated runtime reproduction by Pi/OpenAI, not an independent model review or a real two-pane acceptance test. No existing session or pane was operated. - -The overnight `goals-supervisor-01a040d0` transcript used the older pi-supervise/intercom implementation. It is not runtime evidence for this mailbox branch. The uncommitted display patch in `/tmp/pi-supervise-visible-advice` is also separate from this branch and was not counted as completed work. - -## Findings - -1. **P1: the supervisor's actual advice is still hidden by the default tool-call display.** `src/supervisor-session.ts:157-169` registers `SteerWorker` without a call renderer and returns only a receipt. This fails the user's explicit visibility requirement. Normal emitted assistant text/thinking uses Pi's own display; the local setting already has `hideThinkingBlock: false`. That does not reveal advice inside unrendered tool arguments. - - Observed in the isolated harness: - > steer: renderCall=undefined, result=Worker instruction 1 recorded. - -2. **P1: supervisor resume loses monitoring and read-only tool selection.** `src/supervisor-session.ts:94-107` returns when it finds the persisted bootstrap marker, before starting the new process's polling timer or removing write tools. `ready.json` remains present, so the receipt does not identify this loss of supervision. The harness starts, shuts down, then registers a new extension instance using the saved entries and default tools: - - > fresh: deliveredViews=1, activeTools=read, readyReceipt=true - > resumed: deliveredViews=0, activeTools=read,write,bash, readyReceipt=true - - This reproduces a branch bug; it is not a diagnosis of the different overnight implementation. - -3. **P1: periodic views can tell the supervisor an idle worker is running.** `src/index.ts:201-203` publishes an interval view without checking idle state. `src/worker-view.ts:45` derives worker status from the review trigger, not actual execution. This undermines decisions about whether continuation is needed. - - > interval view without any work: The worker is still working. - -4. **The intended judgment and recap behavior is not established.** The current supervisor prompt (`src/supervisor-session.ts:70-73`) emphasizes: - > Use SteerWorker to give one concrete instruction when work is incomplete. - - Most of the remaining prompt concerns approval checks. It does not request the user's short assessment of progress, independent perspective, or explanation of a recommendation. This is a mismatch in emphasis, not proof that Astra cannot exercise judgment. Neither minimal thinking nor the requirement to use a steer tool establishes a cause of poor advice. - -5. **Low-cost, useful supervision remains untested.** `src/worker-view.ts:41-46` repeats the last compaction summary and up to 12,000 characters from recent messages; it does not make incremental views or measure usefulness/cost. The RPC test (`test/rpc-review.test.ts:51-52`) tests Refine/editor ordering with a deterministic model, not a cheaper worker benefiting from an expensive supervisor. Passing it cannot establish the intended economic or behavioral outcome. - -## What is present - -- Herdr two-pane launch with an explicit planning-session fork: `src/herdr.ts:49-86`. -- Separate supervisor model selection via `/goals model`; the worker keeps its model. The cheaper-worker/more-expensive-supervisor arrangement is possible but not established by default or validated on a task. -- Initial context compaction above 20k and subsequent compaction at 100k: `src/supervisor-session.ts:8-10,113-155`. This broadly meets the request for compaction around 150k or similar; the exact threshold is not the main gap. -- Direct canonical plan path in the supervisor prompt and worker resynchronization after compaction. -- Settle, 50-turn, and hourly review triggers. Reliable continuation is incomplete because of the resume/status defects above. - -## Acceptance still needed - -First make the advice visible and correct resume/status behavior. Then run one bounded task in separate test panes with the intended model pair. Save the rendered advice, worker receipt, a useful progress assessment or correction, continuation after compaction/resume, and measured token/cost totals. Judge the content of the advice, not the number of messages or merely successful delivery. Do not use the user's working panes for this test. - -The transport rewrite is an implementation choice, not the user's goal. - -## Reproduction - -The mailbox reproduction script is historical; retrieve it at commit `386305a`. The Intercom migration removes that implementation. Current transport checks are in `test/intercom.test.ts` and `test/intercom-broker.test.ts`. - -[Saved output](20260908_supervisor-intent-reproduction.txt) records the exact observations quoted above. The harness uses only temporary mailbox files and mocked Pi lifecycle events; it neither launches Pi nor contacts another session. It asserts the currently observed failure, not desired behavior. - --- Pi/OpenAI diff --git a/slop/reviews/20260908_supervision-fixes-validation.txt b/slop/reviews/20260908_supervision-fixes-validation.txt deleted file mode 100644 index 91031d9..0000000 --- a/slop/reviews/20260908_supervision-fixes-validation.txt +++ /dev/null @@ -1,26 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 9 passed (9) - Tests 34 passed (34) - Start at 10:49:05 - Duration 1.34s (transform 639ms, setup 0ms, import 2.76s, tests 1.68s, environment 1ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 17 files in 20ms. No fixes applied. -fresh: deliveredViews=1, activeTools=read, readyReceipt=true -steer: renderCall=function, result=Worker instruction 1 recorded. Worker receipt and execution are not confirmed. -resumed: deliveredViews=0, activeTools=read, readyReceipt=true -interval view without any work: The worker stopped. diff --git a/slop/reviews/20260908_supervisor-intent-reproduction.txt b/slop/reviews/20260908_supervisor-intent-reproduction.txt deleted file mode 100644 index 6ab5e5f..0000000 --- a/slop/reviews/20260908_supervisor-intent-reproduction.txt +++ /dev/null @@ -1,4 +0,0 @@ -fresh: deliveredViews=1, activeTools=read, readyReceipt=true -steer: renderCall=undefined, result=Worker instruction 1 recorded. -resumed: deliveredViews=0, activeTools=read,write,bash, readyReceipt=true -interval view without any work: The worker is still working. diff --git a/slop/reviews/20260909-herdr-functional-acceptance.md b/slop/reviews/20260909-herdr-functional-acceptance.md deleted file mode 100644 index 6e81df7..0000000 --- a/slop/reviews/20260909-herdr-functional-acceptance.md +++ /dev/null @@ -1,47 +0,0 @@ -# Real Herdr functional check - -Pi/OpenAI observed this interactive run. Code: HEAD `2b61440` plus uncommitted plan-watch/manual-claim, supervisor-prompt, planning-prompt and startup-compaction changes. These observations do not cover later changes. - -## Task and result - -Isolated repository: `/tmp/pi-goals-herdr-functional-task`. Worker pane `w8:p4V`; second supervisor pane `w8:p4Y`. Real model: openai-codex/gpt-5.6-terra. Parent selected Ready through the rendered menu. No research pane was operated during this check. - -Task: create `hello-again.txt` containing exactly `Hello again.\n`, save byte verification, and commit only the two new files. Commit observed: `1732acccb798e6edd9626fbfde38e3aca941ce4a`. - -Observed worker tool output: - -> CompleteGoal -> Sign-off accepted. Goal ticked [x] in -> .pi/plan/01a0831d-38ca-76db-b91a-ef4dd0bfecdd-v2.md. - -Observed final supervisor response: - -> Complete. Supervisor approval and CompleteGoal sign-off are recorded. - -Independent parent `od -An -tx1 hello-again.txt` output: - -```text -48 65 6c 6c 6f 20 61 67 61 69 6e 2e 0a -``` - -This is the requested text and final newline. Verification artifact: `/tmp/pi-goals-herdr-functional-task/verify-hello-again.log`. - -## Behavior observed - -The worker manually ticked the goal before sign-off. The widget displayed `claimed complete; awaiting supervisor review` and supervision remained connected. The supervisor read the actual artifact, verification log and plan, then directed the worker to reopen the goal checkbox. The worker reopened it, preserving evidence. The supervisor recorded approval and instructed CompleteGoal. The worker called it successfully. Parent did not supply these corrective instructions or perform the artifact work in this second run. - -## Earlier failures and interventions - -The first task stopped for three routine confirmations because the planning prompt required three questions. Parent answered and removed that quota with subsequent user approval. Ready opened a supervisor, but the separate 20k startup compaction threshold caused `Nothing to compact (session too small)`. Parent removed that lower threshold and reloaded the test supervisor. The first run produced its artifact but manual ticking detached supervision before approval; it was NOT successful workflow acceptance. - -The first run also showed approval attempts against older queued views. Error text now distinguishes a disconnected worker from a newer pending view and directs the supervisor to finish its response to receive the update rather than request another handoff. The second task reached Ready without routine confirmations and completed without parent repair after Ready. - -## Source records - -- Worker: `/home/code/.pi/agent/sessions/--tmp-pi-goals-herdr-functional-task--/2026-09-08T22-22-05-515Z_01a0831d-38ca-76db-b91a-ef4dd0bfecdd.jsonl` -- Successful supervisor: `/home/code/.pi/agent/sessions/--tmp-pi-goals-herdr-functional-task--/2026-09-08T22-57-20-349Z_01a0833d-7ddd-7523-92b3-0560f61dbc64.jsonl` -- Failed first supervisor: `/home/code/.pi/agent/sessions/--tmp-pi-goals-herdr-functional-task--/2026-09-08T22-26-54-633Z_01a08321-a229-70ad-9007-cba97aef7072.jsonl` - -## Limits - -This proves one real trivial workflow, including visible corrective supervision of a manual tick, artifact delivery and sign-off. It does not establish broad judgment quality or cost savings. Idle external-plan edits, active worker reload recovery, all-cancelled handling, and requested `/goals supervise` and `/goals noplan` still require acceptance. Those commands are not implemented yet. Test panes were left available for inspection. Two old dirty native-evidence files remain untouched and are unrelated to this evidence. diff --git a/slop/reviews/20260910_active-research-supervisor-feedback.md b/slop/reviews/20260910_active-research-supervisor-feedback.md deleted file mode 100644 index ec1b581..0000000 --- a/slop/reviews/20260910_active-research-supervisor-feedback.md +++ /dev/null @@ -1,53 +0,0 @@ -# Active research supervisor feedback - -Received from `goals-supervisor-01a08503`, reporting first-hand as supervisor `01a089da` in `/workspace/2026/suppressed-activations`, 2026-09-10T07:26:19Z. Intercom message `48ca3406-d19f-44f2-b75f-e0f895f00eb8`. Observations below are sender reports, not independently reproduced here. Suggestions only; no authorization to reload or modify the active research session. - -## Reported successes - -> explicit Ready approval preserved the user's revised goal; full user quotes helped repair drift; normal tools let me inspect actual research code/results rather than rubber-stamp worker summaries. The current single visible goals-worker attached the plan, reported through Intercom, and is doing evidence work on the requested model. - -## Follow-up issues, in sender priority order - -1. Identity/liveness metadata: - > Stock subagent launch returned only async child id ed63a4a9, not sessionFile/full Intercom UUID/resolved model. Yet reconnection instructions require those. I had to ask the child for /.../2026-09-10T07-01-11-345Z_b0f427f0-99a97788-abd16f05-9c74.jsonl and UUID 8cb793dd-ed52-4cdf-835c-8574dd2b2e98. - - > Suggest one authoritative lifecycle record/launch response containing all distinct identities + resolved model, never infer identity equality. - -2. Metadata-only review churn: - > My two metadata-only plan edits each triggered 'Plan changed' reviews, producing read/recap churn while no goal changed. Suggest semantic diff categories: requirements/discriminators, claims/evidence, metadata. Coalesce or silently acknowledge known metadata-only edits; keep substantive reviews. - -3. Scheduler ownership and disabled-job retention: - > schedule_prompt add does not expose session binding. I correctly had to read .pi/schedule-prompts.json to verify it. Suggest binding in tool return plus atomic validation. Current disabled-job deletion on reload makes the toggle surprisingly nonpersistent; preserve disabled jobs if feasible. - -4. Context-free language warnings: - > A final-pass language nudge flagged the fragment 'e rule across dog/ant naming, leg-count, and property questions' as metaphor and gave arm/ship/gate advice. Here 'leg-count' is literal animal anatomy. Suggest exact span + contextual classification, avoid blocking generic substring warnings. - -## Supervisor judgment, not a software diagnosis - -> Important epistemic failure was mine, not an implementation bug: earlier supervision invented a Yes-first-token success gate, interpreted missing r2 key via my own -1 default as missing logging, and overstated logit-lens evidence as causal localization. These caused extra sweeps and misleading confidence. The new plan explicitly corrects them. Suggest review support that surfaces changes between agreed discriminators and new supervisor thresholds, and labels worker reports vs directly inspected evidence. Do not solve this by restricting inspection tools. - -## Scope boundary - -> Priority: identity/liveness metadata, then reduce metadata-only event churn. Happy to provide exact tool messages or help with a bounded test; don't disrupt the active research worker. - -Tracked for follow-up, separate from the already-authorized command-menu and automatic plan-proposal fixes. No runtime patch or active-session intervention authorized by this feedback. Keep the supervisor's tools and judgment; do not add restrictions to compensate for research interpretation errors. - -## Two-hour follow-up - -Supervisor `01a089da`, message `04b9fb76-b02b-4b1b-a1fe-27067deef258`, 2026-09-10T09:30:48Z. First-person report; research artifacts were inspected by that supervisor, not independently rechecked here. - -> What worked: independent code/artifact inspection repeatedly caught genuine invalid comparisons before accepting completion. Example: worker called low cosine a decisive negative; I read analyze_bank.py and manifest and found it compared source final 'is'/space indices12/13 to dog 'and'/'is' rather than donor final19/20. CPU-only correction exposed donor token-pair structure hidden in all-token means, leading to a meaningful causal test. Later code review caught pair selection translating absolute indices back into prefix offsets, and QR on [1,hidden] returning a scalar random 'direction'. Worker repaired, preserved invalid995, and reran996. Actual996 JSON shows no coherent transfer; I inspected outputs myself, not just worker's verdict. We are now recovering the earlier candidate and preparing reliability evaluation under the user's explicit limited-reliability fallback. No goals approved. - -> Upkeep: I interpreted supervisor inspection-only as forbidding research implementation but allowing explicitly requested plan bookkeeping. I marked Goal1 and active tasks [/], then Goal2/task1 [/] when delegating manifest/rubric work. Evidence field says exactly which artifacts were inspected and what they establish; progress/invalid-run details go in dated Log entries. I did NOT tick tasks done just because child says 'family COMPLETE'. I continue to keep the worker off approved-goal editing; clearer division between parent-owned requirements/status and child evidence submissions would help. Existing workflow is usable with this interpretation. - -> Communication: single child remained reachable via stored Intercom UUID; no further identity discovery problem after initial manual registration. Each failure received a targeted correction with actual code/quoted values, rather than repeating a generic continue. Your read-only finding about the real model_change plus Wassname's 'I did the switch' resolved authorization cleanly; I updated preference/provenance and did not restore the old model. That worked. - -> Remaining friction: 'Plan upkeep ... eight turns without working-set changes' messages recur during active review/tool exchanges and sometimes inject while I am already inspecting evidence to update the plan. Multiple full working-set repeats + full mode reminders dominate the transcript. Then my upkeep log/status edits can produce another plan-change review. I cannot infer the actual event-counter implementation from this; observed effect is repeated reread/recap overhead, not missing work. Suggest diff summaries with a last-reviewed version/hash, coalescing while a review is active, and distinguish meaningful worker progress from side conversations (including this feedback). Keep the full current plan at role changes/compaction and always accessible by read; no need to repeatedly paste unchanged user voice to this extent. - -> Monitoring nuance: pq warned 'no follower' although child had a tracked `pueue wait 994` process, confirmed by child. I correctly treated that as unknown and asked, not proof of no waiter. More generally process existence/completion is not experimental success; wait exits0 even for failed tasks. Two launch jobs really failed; saved state/log inspection caught this despite a successful waiter. A standard structured child update (task IDs, watcher IDs, actual task results, artifact paths, next dependency) would reduce back-and-forth. - -> Scientific reasoning failures belong to us models, not necessarily harness: repeated 'low value => impossible' inferences and overly strong causal claims need actual evidence review. Keep the supervisor's normal tools. Also my detailed corrections are becoming a lot of prose; concise artifact-linked review diffs would help keep the user-facing recap small. - -Follow-up inference (Pi/OpenAI): initial identity registration needs improvement, but continued routing reportedly works. Prioritize reducing repeated unchanged plan context and metadata-only review events, without suppressing changed requirements or completion claims. No active-session intervention performed. - -— Pi/OpenAI diff --git a/slop/reviews/20260910_deepseek-package-supervision.md b/slop/reviews/20260910_deepseek-package-supervision.md deleted file mode 100644 index f0087ec..0000000 --- a/slop/reviews/20260910_deepseek-package-supervision.md +++ /dev/null @@ -1,76 +0,0 @@ -# DeepSeek reviewer — package-based supervision (cb35fbf + uncommitted patch) - -Scope: `src/prototype.ts` (426 vs 186 lines), `src/prompts.ts` (+88), `test/prototype.test.ts` (+397), `test/rpc-review.test.ts` (env sanitize), `AGENTS.md`, `prototype/README.md`, `prototype/agents/goals-worker.md` (repo default `auto-exit: true`). Evidence basis: full source reads, stock edxeth `953c6f6` reads, installed `pi-schedule-prompt/src` and `pi-intercom` reads at the exact provided paths, parent UAT captures `01–19`, and `slop/reviews/20260910_package-supervision-herdr.md`. Nothing executed beyond `vitest` logs already saved; no source edits, no panes, no messages. - -Tests: saved logs show 158/158 pass + typecheck/lint pass. The suite does exercise the new gates well (stale-menu invalidation, watcher debounce, unavailable snapshot signoff retention, upkeep fold, child attach). It does **not** cover: real auto-exit vs keep-open behavior, the stock stale-ctx crash on worker exit after reload, scheduler disabled-job deletion across reload, or intercom worker identification with multiple children — those are only covered by the parent's live captures, and the unit fake cannot prove UI/source acceptance. - ---- - -## Findings (max 8, prioritized) - -### 1. [Observed — HIGH] Repo default `auto-exit: true` discards unsent human drafts at every worker terminal turn; `auto-exit: false` + Intercom preserves the draft but does not fix the delayed reload crash -- Source: `prototype/agents/goals-worker.md` frontmatter `auto-exit: true` (repo default, unchanged); agent prose "Normal completion returns your report and closes this pane". -- Repro: parent captures `08` (auto-exit closed w8:p6E and lost `UNSENT-WORKER-DRAFT-KEEP`, while the worker's own tool result proved the Intercom reply had been delivered) and README "Known trial failure". With an isolated `auto-exit:false` profile, capture `09/11/13/14` show live Intercom steering + the unsent draft surviving send, ACK, and a parent `/reload`. -- Minimal fix (package config/prompts only): flip the agent def to `auto-exit: false`, update the agent prose and `prototypeSupervisor` (see finding on "Reports arrive automatically"), and make the child report completion via an Intercom message to the parent id it receives in `from.id` / discovers via `intercom list`. This is exactly the minimal-adoption route the plan asks for — but see finding 2 before declaring lifecycle acceptance. - -### 2. [Observed — HIGH] Delayed parent crash when a live open worker exits after a parent reload (stock edxeth, not pi-goals) -- Source/evidence: `slop/reviews/package-supervision-captures/17-delayed-reload-worker-exit-crash.txt`; stack `running-registry.ts:255 -> widget.ts:139` — stale ExtensionContext after reload, "This extension ctx is stale after session replacement or reload". -- Repro: parent reloaded with the keep-open worker alive (capture 14 passed for immediate re-targeting), then the worker exited normally (Ctrl+D) → the parent pane crashed on the stale callback. -- Impact: the whole "reload with a live worker" story (which `auto-exit:false` depends on) is not lifecycle-safe until this stock behavior is understood or avoided. `auto-exit:false` preserves the draft and steering but does not fix the eventual worker-exit crash (observed — this crash occurred in the keep-open configuration). -- Minimal mitigation within constraints: after a reload, do not rely on a normal worker exit; recover via saved-session restart + `/goals attach <plan> solo` (validated: capture `19` — exact bytes `solo-ok\n`, `Solo self-verification` log, original `hello.txt` preserved). README already bounds the claims; keep it that way and do not change repo lifecycle defaults until the crash trigger window is pinned. - -### 3. [Observed — MEDIUM] Disabled owned scheduler job is deleted by the installed scheduler, contradicting `scheduleCheckIn`'s "retain disabled" instruction; check-ins then silently stop -- Source: installed `pi-schedule-prompt/src/index.ts` `autoCleanupDisabledJobs` — deletes own/unbound disabled jobs on `session_shutdown` and on non-startup `session_start` (verified in the installed source; parent observed the deletion in the trial). `src/prompts.ts:328` `scheduleCheckIn` tells the model to "retain its human-edited prompt, interval and enabled/disabled state unchanged; never recreate, overwrite or re-enable", and `:332` "Do not reinstall a missing job from a scheduled check-in". -- Repro: create the owned job, disable it via `/schedule-prompts`, `/reload` → job gone; no ready/resume prompt is re-sent on reload, so nothing ever informs the model — hourly check-ins stop silently, and the "retain disabled" guidance is unachievable. -- Minimal fix (prompt-level): tell the model that a disabled owned job may be auto-cleaned at reload, and instruct it to surface that to the user (report-and-ask) instead of silently re-adding or silently ignoring. Optionally persist user intent (disabled) in the extension STATE entry so reload can restore it deliberately. - -### 4. [Inferred — MEDIUM] Job add/remove guidance mismatches the scheduler API: auto-named jobs, and removal by name while the API needs `jobId` -- Source: `pi-schedule-prompt/src/tool.ts:72` `const jobName = params.name || \`job-${nanoid(6)}\``; `:128-141` `remove` requires `jobId`; `src/prompts.ts:328` `scheduleCheckIn` never instructs the model to pass `name: goals-<sessionId>`; `src/prompts.ts:325` `removeGoalSchedule` says remove "the job named `goals-<sessionId>`". -- Repro: model adds the job without a name → it is `job-<nanoid>`; later removal guidance looks for a name that does not exist. List-first mitigates (and capture 19 shows the solo completion log correctly listing an empty job list), but a model that skips the list step can fail to remove the owned job, leaving a stale hourly prompt firing into a completed/paused session. -- Minimal fix: instruct add with the explicit owned name and removal by the listed `jobId`; keep "leave other jobs untouched". - -### 5. [Inferred — MEDIUM] `workerStopped` can never be set by an actual stop; the confirm menu is the only stop-recording path -- Source: `src/prototype.ts:291` records the worker on launch/resume success only (`subagent`/`subagent_resume`); there is no `subagent_kill` result hook, and the detached async completion is delivered as a steer message, not a `subagent` `tool_result` (stock `running-registry.ts` `getStartedSubagentResult` returns immediately for async). `workerStopped: true` is only assigned in `enterSolo` (`:214`) and in `attach ... solo` (`:345`). -- Repro: worker completes and auto-exits (current default) → the parent sees the completion steer but `state.workerStopped` stays false → `/goals attach <new-plan>` (non-solo) and any new objective stay blocked (`:340`, `:376`) until the user runs `/goals solo` and confirms. Safe (no duplicate writer), but the recorded handle is permanently "possibly live" and exit alone never unblocks plan replacement. -- Minimal fix: document in README/help that the solo-confirm menu is the only path that records a confirmed stop, or add an explicit "worker confirmed stopped" command that sets `workerStopped=true` after the user inspects `subagent_kill`/pane (still no liveness claim). - -### 6. [Inferred — LOW/MEDIUM] No persisted mapping from the worker to its Intercom session id; identification after reload is name/cwd matching, fragile with multiple children -- Source: `src/prototype.ts:291` stores the edxeth runtime `id` (e.g. `8317cac5`) + `sessionFile`; the Intercom id (`003c8e70-...`) is different and only discoverable via `intercom list` (presence name `[goals-worker] <title>` from the launch title, `cwd`, id prefix — verified in `pi-intercom/src/index.ts` `buildPresenceIdentity`/`list` and capture `14`). -- Repro: parent reload with one open worker → re-target by list works (capture 14: `RELOAD-LIVE-ACK` from same session `003c8e70`, draft intact). With two open `goals-worker` children (e.g. after a mistaken duplicate resume), name/cwd matching is ambiguous, and nothing records the observed Intercom id for reuse. -- Minimal fix: when the supervisor identifies the live child (from a received message `from.id` or `list`), persist `intercomId`/name in `state.worker` and surface it in `/goals status` and the steering prompts. - -### 7. [Inferred — LOW] `CompleteGoal` accepts a cancelled `[-]` goal and rewrites it to `[x]` with a recorded sign-off -- Source: `src/prototype.ts` `goals()` maps `-` to `"cancelled"`, the `CompleteGoal` match filter (`key(g.subject) === key(params.goal)`) does not exclude cancelled, and the tick replaces `[-]` with `[x]`; `prototypeMessages.cancelled` only guards signal abort. -- Repro: parent calls CompleteGoal on a goal the user (or plan) cancelled → the cancelled goal becomes done+reviewed, changing the widget and `remaining` accounting. -- Minimal fix: exclude `status === "cancelled"` from the `CompleteGoal` match (and from `ready`'s delegation scan). - -### 8. [Observed — LOW] Real-model planning trap (not a source defect): first draft misread the unquoted literal -- Evidence: parent review — first draft made `hello from the worker` six bytes, split file+verification into two goals, and called uncommitted deliverables a failure; one parent clarification fixed it, after which Ready → worker → byte-verified completion ran autonomously, and solo fallback (capture 19) also completed. -- Suggestion (untested): the drafting prompts already mandate quoting in `## User voice`; add one concrete literal-quoting example (an exact-byte file target) to `planDrafting`/`prototypePlanning` so future models see the expected precision. Not a code change. - ---- - -## Direct questions - -### Can no-autoexit + live Intercom replace the completion handoff safely? -- **Report/steer/draft path: yes, observed.** Capture `09/14`: Intercom delivery preserved an unsent draft, ACK round-tripped, parent reload re-targeted the same open worker (`003c8e70`), draft intact. -- **Full lifecycle: no, observed.** The keep-open worker's later normal exit after a reload crashed the parent on stock edxeth's stale ctx (finding 2). `auto-exit:false` does not fix that. So no-autoexit + Intercom is safe *as a completion handoff while the worker stays open*, but every eventual worker exit after a reload is a crash risk; plan for pane/session-restart termination instead of normal exit, and validate before claiming lifecycle acceptance. -- **One hard dependency of the no-autoexit configuration:** with `auto-exit:false` interactive, stock edxeth registers neither `caller_ping` (requires `!isInteractive || autoExit`) nor `subagent_done` (never for interactive). The child has no edxeth completion tool at all — completion must be an Intercom message, and `prototypeSupervisor` (`src/prompts.ts:308`) currently says "Reports arrive automatically", which is only true for the auto-exit default. Flip the default and this sentence becomes false; the child-side prompts (`childPlanRole`, agent prose, readyApproved) also never instruct the child to send a completion Intercom message or give it the parent's Intercom id (it can take it from `from.id` of the first parent message, or `intercom list`). The agent-def flip and these prompt changes must land together. - -### How to identify the existing worker after reload? -- Via `pi-intercom` presence list: `intercom({action:"list"})` shows the child's registered session — name `[goals-worker] <title>` (launch title), `cwd`, and a unique id prefix (`pi-intercom/src/index.ts` `list`; duplicate names are disambiguated with id prefixes). The child's Intercom session id is stable across the parent reload because the child process never reloaded. Target by the listed id prefix (capture 14: parent re-targeted `003c8e70`, received `RELOAD-LIVE-ACK`, no second worker launched). -- Pitfall: the stored edxeth runtime id (`state.worker.id`) is **not** the Intercom id; match by name/cwd, and persist the observed Intercom id once found (finding 6) to remove ambiguity with multiple children. - -## Minimal stock-package integration advice (no runtime patches/framework) -1. Agent def: `auto-exit: false`; update agent prose ("stay open; report completion via Intercom") — package config only. -2. `src/prompts.ts`: remove/qualify "Reports arrive automatically" under keep-open; readyApproved/childPlanRole instruct child completion via Intercom to the parent id it learns from `from.id`/`list`; keep "do not start a second writer" and the liveness caveats (already good). -3. Scheduler guidance: add with explicit name `goals-<sessionId>`, remove by listed `jobId` (finding 4), and surface the disabled-job auto-deletion reality (finding 3, observed in the installed scheduler). -4. Lifecycle claims: keep the README bound (no auto rediscovery / crash recovery); use saved-session restart + `/goals attach <plan> solo` as the validated recovery (capture 19); do not change repo default until the stale-ctx crash (finding 2) is scoped. -5. Keep the branch-entry STATE persistence and the AttachGoalPlan child seam — they are what makes saved-session restart and solo recovery work; the parent's "PI_GOALS_SHARED_PLAN never set" finding is resolved by the removal of that env read and the explicit attach tool (`src/prototype.ts:387`). - -## Remaining uncertainty (explicit, not re-explored) -- Exact trigger window/ordering of the stock stale-ctx crash (which callback, whether parent-reload-before-worker-exit is always fatal vs only certain timing) — untested, stock code, outside pi-goals. -- Whether the child's Pi session name reliably equals `[goals-worker] <title>` across stock edxeth launches (observed once) — naming could vary by edxeth version/overrides. -- Whether a model will reliably follow the list-first + jobId removal and the "report disabled-deletion" prompts — prompt-compliance only, no enforcement point exists. -- Real-runtime delivery of the upkeep message (`triggerTurn:false`, `src/prototype.ts:258`) — verified by unit mock only; actual Pi queueing behavior untested in UAT. -- The `- worker session:` plan note is only ever read (`src/prototype.ts:343`), never written by code or instructed by prompts; attach tests seed it manually — real-flow write path unverified. diff --git a/slop/reviews/20260910_glm-package-supervision.md b/slop/reviews/20260910_glm-package-supervision.md deleted file mode 100644 index 250e23a..0000000 --- a/slop/reviews/20260910_glm-package-supervision.md +++ /dev/null @@ -1,51 +0,0 @@ -# Review: package-based supervision prototype (uncommitted diff on `experiment/main-supervisor-edxeth` @ cb35fbf) - -Scope: full uncommitted diff (src/prototype.ts, src/prompts.ts, tests, AGENTS.md, prototype/README.md), read whole files plus src/plan.ts, src/plan-view.ts, src/index.ts, src/supervisor-session.ts; installed scheduler (`pi-schedule-prompt/src/tool.ts`, `scheduler.ts`, `settings.ts`, `types.ts`) and `pi-intercom/index.ts` read for verification; AGENTS.md, plan `slop/plans/20260910_package-based-supervision.md`, UAT `slop/reviews/20260910_package-supervision-herdr.md` and captures checked. Read-only review; no edits, panes, messages or installs. - -Prior author claims re-verified against source, not repeated: the earlier "parent source findings" list is mostly fixed in this diff — `PI_GOALS_SHARED_PLAN` is gone (AttachGoalPlan, prototype.ts:457-467), `attach solo` routes through `confirmSolo` with generation/workerRevision re-checks (prototype.ts:196-205, 352), signoff-invalidating `refresh` moved inside the debounce (prototype.ts:155-160), and the eight-turn upkeep reminder is ported and tested (prototype.ts:211-226; test/prototype.test.ts:416). The findings below are what remains. - -## Findings - -### 1. HIGH — scheduleCheckIn demands session-binding verification the schedule_prompt output cannot provide -`src/prompts.ts:328-329` (`scheduleCheckIn`): "add one session-bound interval '1h' job ... Verify the returned session is `<id>`; if the new job is unbound, remove that new job and report the scope error." -Installed `pi-schedule-prompt/src/tool.ts:111-125` (add result) prints only name/id/type/schedule/prompt/model line; `list` (tool.ts:300-325) also omits the `session` field. Binding is decided by `getDefaultScope()` (`settings.ts:9,29`, user setting `defaultJobScope: "workdir"`), and `tool.ts:88-89` omits `session` when scope is workdir. The `details.jobs` payload is not model-facing text. -Reachable failure: with `defaultJobScope: "workdir"` configured, the added job is unbound and every pi in the cwd (including worker panes) loads and fires it (scheduler.ts:60-62) — duplicated hourly wake-ups across sessions, and the model can never execute the instructed verification, so the scope error is never reported. The prompt-level contract is unverifiable by construction. -Minimal fix: reword to what the tool actually exposes — verify scope by checking the scheduler settings/jobs file or the `/schedule-prompts` UI, or state plainly: "if the tool result does not confirm session binding, report that binding could not be verified and point the user at /schedule-prompts" — instead of asserting the model can "verify the returned session". - -### 2. MEDIUM — scheduler `cleanup` destroys user-disabled jobs, contradicting the retention promise -`src/prompts.ts:329` promises to "retain ... enabled/disabled state unchanged; never recreate, overwrite or re-enable it"; `src/prompts.ts:326` and the plan doc claim human-edited schedules are untouched. But `pi-schedule-prompt/src/tool.ts:188-215` (`cleanup`) removes **all disabled jobs loaded for this session**, including a `goals-<sessionId>` job the user deliberately disabled ("Unbound disabled jobs are fair game" — and session-bound disabled jobs too). The completion prompt (`prompts.ts:352` → `removeGoalSchedule`) hands the model a schedule_prompt mandate right when tidying is likely. Parent UAT has now confirmed disabled-job deletion as a real gap. -Minimal fix: in `removeGoalSchedule`/`scheduleCheckIn` explicitly forbid the `cleanup` action ("remove only by jobId; never run cleanup — it deletes jobs the user disabled") and record the limitation in the plan/AGENTS.md claim. - -### 3. MEDIUM — two supervising sessions can attach the same plan; no cross-session writer guard outside solo -`src/prototype.ts:340` (attach guard) and `:376` (new-goal guard) only check *this session's* worker/mode. Nothing detects that another session is already supervising the same plan path. Session A supervises plan P (watcher live, scheduler job goals-A); user attaches P in session B via `/goals attach P` (non-solo → planning, no confirmation), B reaches Ready and launches worker 2. Result: two writers, two independent watchers both firing plan-change reviews at their own sessions, and duplicate `CompleteGoal` authority — signoffs live in per-session `appendEntry` state (prototype.ts:56), so each session signs off the same goal independently and both write competing Log entries. The "two writers must not run together" discriminator is tested only for the solo transition (test/prototype.test.ts:221,306). -Minimal fix: at attach (and again at Ready), inspect the plan note / scheduler job list for another bound supervisor (`- worker session:` note plus a live `goals-<otherSession>` job) and route through a `confirmSolo`-style explicit confirmation when found. Prose alone ("Do not start a second writer") is the same guard class the design elsewhere rejects as insufficient. - -### 4. MEDIUM(LOW) — the supervisor's own plan edits trigger a plan-change review of itself; observed in UAT -`src/prototype.ts:150-170` (`watchPlan` debounce → `send(planChangedReview(...))` at :168 with `triggerTurn:true`). Own-write suppression exists only for `CompleteGoal` (:447-449) and `/goals model` (:322). A supervisor editing a requirement/subtask above the fold with its normal edit tools changes the `planViews(...).short` hash and fires a full "Plan changed — inspect..." review turn at itself. UAT observed this ("One extra event-hook review followed the supervisor's own plan edits", herdr review doc). Each such edit costs a turn and an edit→review→edit loop is plausible while the model restructures the plan. -Minimal fix: in the same extension-owned write paths set a short-lived `selfWrite` stamp and skip the notification when the debounced read still matches the session's last written content; otherwise document that self-edits intentionally cost one review turn. - -### 5. LOW — pauseExitNotice instructs `subagent_kill` with a sessionFile when the worker handle has no id -`src/prompts.ts:357-358`: "stop the tracked worker ${worker.id ?? worker.sessionFile} through subagent_kill or its pane". After `attach` restores a noted worker, the handle is `{ sessionFile }` with no `id` (prototype.ts:345), and `subagent_kill` requires the edxeth runtime `id` (prototype.ts:203-206 checks that property exists). A path is not a valid kill id; the model will fail the kill before falling back to the pane. -Minimal fix: branch the notice on `id` presence — with only a sessionFile, say "inspect liveness and stop it in its pane; no runtime handle is recorded for subagent_kill". - -### 6. LOW — refresh() leaves a stale widget when the plan read fails -`src/prototype.ts:113-116`: on `readPlan()` failure the status bar shows the error but `setWidget` keeps the previous (possibly signed-off) lines, so during a transient unreadable/truncated-save window the widget presents stale completion claims as current. Minor, but the widget is the user's "task list" per the design note. -Minimal fix: on the error path also push/annotate the widget (e.g. `["plan unavailable — widget stale"]`) or clear it until the next successful read. - -### 7. LOW — watcher error path is dead-end: plan-change reviews silently stop until a mode transition -`src/prototype.ts:171-173`: `planWatcher.on("error")` only notifies; after a directory-level watcher error (e.g. `.pi/plan/` renamed/recreated by an editor or user) Node's watcher is finished, no re-arm occurs, and `planHash` goes stale — later real plan edits produce no review until stop/resume/ready recreates the watcher. -Minimal fix: on error, close and null the watcher, and attempt re-arm in the existing `agent_end`→`refresh` path when `state.mode === "supervising"` and no watcher is live. - -### 8. INFO — residual validation gaps (do not block, but the plan/UAT claims overstate coverage) -- Confirmed by parent UAT, not yet root-caused or fixed: delayed crash when the worker exits some time after a supervisor reload ("reload → worker-exit" sequence). Until reproduced, do not claim reload-lifecycle acceptance; the herdr doc itself says live-parent reload "remains pending"/crashed. -- No test or live trial of the hourly interval actually firing in the owning session ("No timer firing yet tested"); the plan doc's discriminator "shortened test interval fires once" is unmet. -- Worker-model verification (subtle failure "trial inherits supervisor model") is prompt guidance only; no test asserts the resolved model is surfaced anywhere the supervisor checks. -- Finding 3's two-supervisor scenario and findings 1/2's scope/cleanup behavior have no test coverage; the 158 passing tests exercise prompt text, not installed-package behavior. -- `refresh()` deletes signoffs inside the loop calling `save()` per deletion (prototype.ts:124-128) — benign today, but a batched save would avoid N appendEntries per refresh. - -## Verified OK (checked, not just claimed) -- Line-index safety of `CompleteGoal`'s tick edit: `goals()` folds at `## Log`, goal lines precede the fold, so indices into `foldPlan` match the full text; log entry is spliced below the fold and excluded from `planHash` via `planViews.short`. -- `tool_call` gate correctly still permits `subagent_kill` in paused/solo (only `subagent`/`subagent_resume` blocked, prototype.ts:240-244), matching the pause notice's instruction to stop the worker. -- `confirmSolo` invalidates on generation, workerRevision, and plan-content change after the await — the post-await race flagged in the earlier review is genuinely fixed. -- Intercom draft preservation and "receipt ≠ action" are consistent with `pi-intercom/index.ts:1181-1203` (injection receipt emitted at delivery; steer/trigger delivery only) — the prompts' skepticism is warranted and correctly worded. -- All model-facing strings for the prototype live in `src/prompts.ts` in the required narrative order; no timer/transport framework was built; repo lifecycle defaults unchanged. diff --git a/slop/reviews/20260910_package-supervision-herdr.md b/slop/reviews/20260910_package-supervision-herdr.md deleted file mode 100644 index 78381ad..0000000 --- a/slop/reviews/20260910_package-supervision-herdr.md +++ /dev/null @@ -1,95 +0,0 @@ -# Package-supervision functional trial - -Pi/OpenAI observations. Entries below preserve the chronological investigation; this current result supersedes earlier pending/failed-test status. - -## Final result — 2026-09-10, Pi/OpenAI - -- Final source checks: 162 tests pass, typecheck/lint/diff checks pass (`slop/test-logs/final-review-fixes-*.log`). Both native external reviews completed; reports are saved alongside this file. Scheduler scope/removal guidance, attachment ownership confirmation, cancelled-goal rejection, read-error handling and stay-open completion prompts were corrected. Stock dependency limitations below remain unfixed. -- Fresh Fireworks DeepSeek V4 Flash trial: `/tmp/goals-edxeth-trial-X3Y79U`, supervisor `w8:p6H`, worker `w8:p6K`. Current revised prompts/default produced `verified.txt` exactly `verified\n` (9 bytes), real `evidence/verified.log`, Intercom completion, independent supervisor byte/log inspection, CompleteGoal signoff and an empty scheduler store. Worker stayed open. Parent separately asserted bytes and saved log. Captures 20–23 preserve menu, panes and parent verification. -- Interventions: redirected needless package research during planning; selected Ready; repaired missing temporary stock-edxeth source and instructed same-session resume. The missing directory caused child startup exit 1 before any model turn. Its deletion cause is unknown. Restored the identical stock commit in `/home/code/.pi/agent/git/github.com/wassname/pi-subagents-stock-validation`, with the former temporary path pointing there. This is recovery evidence, not an uninterrupted autonomous run. -- Idle worker widget retained its earlier plan snapshot after supervisor completion; supervisor widget updated. Do not claim idle cross-session widget synchronization. -- Known limits: stop workers before supervisor reload (stock stale-context crash); disabled scheduler jobs are deleted on reload; stop/ownership confirmation is human attestation. Saved-parent restart and solo completion passed in the earlier trial. No runtime patch. - -## Earlier chronological observations - -Latest critical finding: **parent reload with a live worker FAILS later when that worker exits**. Stock edxeth old callback crashes the parent with stale ctx (`running-registry.ts:255 -> widget.ts:139`). Actual capture `17-delayed-reload-worker-exit-crash.txt`. Immediate pane survival/reconnection did NOT establish full lifecycle recovery. No runtime patch; parent is testing saved-session restart then solo takeover. - -The disabled scheduler job was deleted by the scheduler on reload, NOT preserved. Installed pi-schedule-prompt `src/index.ts:117-144` deliberately auto-cleans loaded disabled jobs at shutdown and non-startup session changes. Parent's earlier statement that it remained disabled was premature. Live-worker/draft preservation and Intercom reconnection after reload DID pass (capture 14). Native different-family /review is now running as requested; earlier Claude CLI route was a parent mistake, not the `/review` skill's prescribed route. - -- Code: branch `experiment/main-supervisor-edxeth`, HEAD `cb35fbf1feed9d4cea2f9675917adfe602c2835c` plus uncommitted implementation from Fireworks worker `f19ee4ad-f06b-47a5-8709-51f9e360f2f4`. -- Current diff/status saved privately in `.local/recovery/external-review-5d2fd4e4/`; no commit because full tests are not passing. -- Parent inspected `slop/test-logs/full-20260910-2.log`: 142 passed, 1 failed. `test/rpc-review.test.ts` times out after 15000ms. Worker claims it reproduces at unchanged HEAD; parent has not verified that claim. Its claim that interactive use is unaffected is not established. -- External review skipped in original workflow `4bea1cc4` because parent checked `implementation.status` rather than result `ok`. Explicit external review run `5d2fd4e4-757d-40c5-a3de-1af5d5e4089e` then failed before launch: `External CLI version preflight failed: spawnSync /workspace/.tweakcc/DO_NOT_DELETE_patched_binaries/2.1.231/native/patched ETIMEDOUT`. No reviewer findings. No executor fallback used. - -External-review retry: exact configured binary `--version` succeeded in 0.705s, output `2.1.231 (Claude Code)\n4.3.3 (tweakcc)`. Same-protocol retry `0a0d900d-4b8f-41d6-b0b1-1435996c1b33` then failed `Unsupported Claude Code version response`. Root cause located: installed nicobailon pi-subagents `src/runs/shared/claude-code-adapter.ts:125` anchors its version regex across the entire response and accepts one line only. No global package modifications or executor substitutions made. External review remains blocked, no findings. Checkout remains cb35fbf plus preserved diff; no change of worktree/branch. - -## Visible trial - -- Confirmed `HERDR_ENV=1`, parent `w8:p62`. Read installed `herdr --skill`. -- Created ONLY test pane `w8:p69`, no focus, right of parent. Label `goals trial — Fireworks`. -- Isolated root `/tmp/goals-edxeth-trial-HSahSm`, project `project/`, private profile `agent/`, launch script `start.zsh`, preparation manifest `manifest.json`. Private auth copied by existing `prototype/prepare.mjs`; do not commit raw profile files. -- Stock edxeth detached worktree `/tmp/pi-goals-stock-edxeth-LQFtq2`, revision `953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4`, dependencies symlinked to existing repo node_modules. Dirty abandoned runtime was NOT loaded. -- Startup UI confirms Fireworks `accounts/fireworks/models/deepseek-v4-flash-0731`. Retained normal extensions including Intercom and schedule-prompt; only conflicting goals/subagent packages replaced. Startup warned of pre-existing malformed `~/.agents/skills/self-verify/SKILL.md` frontmatter; trial otherwise reached input UI. -- Submitted `/goals` task: exact `hello.txt` bytes `hello from the worker\n`, saved verification `evidence/hello.log`, worker model explicitly Fireworks DeepSeek V4 Flash. No implementation has been performed by parent. -- Planning readiness observer: process `proc_b6c5`, waits for rendered review/ready/error signals. Inspect actual pane after signal; signal alone is not acceptance. After draft: invoke `/goals review`, select Ready through actual UI, inspect worker pane/model, scheduler and resulting artifact/evidence. Then exercise reload, Intercom delivery and recovery. Do not operate other existing user panes. - -## Planning observation and intervention - -The first draft interpreted the unquoted phrase `hello from the worker` as just `hello` written by a worker, specified six bytes, and split the file and its verification into two goals. It also called uncommitted side effects a failure despite the no-commit request. Parent read both rendered conversation and plan; widget updated to 0/2 at turn end. Parent clarified the ENTIRE literal line is `hello from the worker\n` (22 bytes), asked for one goal and allowed uncommitted deliverables. This is a manual planning clarification, not an autonomous pass. Parent subsequently read the corrected plan: one goal, exact 22-byte content. Ran `/goals review`, captured actual Ready menu in `package-supervision-captures/01-ready-menu.txt`, selected Ready with Enter. Process `proc_de96` observes worker launch. Herdr agent state reported idle while rendered Pi was still working, so `agent wait` returned too early; inspect rendered pane/plan rather than trusting that state alone. Raw `pane send-keys end` was rejected as unsupported (no input sent). - -## Verified completed task and follow-up - -- Parent full `npm test` rerun: **143/143 passed**, 23 files, 4.51s. Saved log `slop/test-logs/parent-full-20260910.log`. Likely explanation for worker-only RPC timeout: its inherited `PI_SUBAGENT_CHILD=1`; `src/index.ts:86,108` disables main registration in a child. Sent to implementation worker for confirmation and fixture isolation. -- Inspected both actual JSONL sessions. Worker `2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl` model_change confirms requested Fireworks DeepSeek V4 Flash. Stock tool result reports interactive mode, autoExit true, completed in 50s, id `3cbbafbe`, saved session path. Parent missed its rendered pane, so this is NOT yet worker-UI acceptance. -- Actual hello.txt equals `Buffer.from("hello from the worker\\n")` via Node assert: 22 bytes. Worker wrote file/log via commands, supervisor read both and independently ran wc/od, then CompleteGoal. No intervention after Ready. Saved sanitized tool/model evidence in `package-supervision-captures/04-completion-events.json`, copy of log/plan, parent byte-check in `05-parent-byte-check.log`. -- Supervisor created scheduler job `pjGJoy_etU` with session `01a0898a-3dde-7015-b029-ced69dc7b553`, interval 1h, no model. Removed it after completion; actual scheduler file is `{jobs: [], version: 1}`. No timer firing yet tested. Job was created AFTER worker finished because subagent tool result was awaited. One extra event-hook review followed the supervisor's own plan edits. -- Ran `/reload` on test parent after completion, then `/goals status`: retained supervising mode, same plan, same saved worker session and 1/1 reviewed widget; no crash observed. Does NOT prove reload while worker active. -- Raw End escape (`pane send-text` with ESC[F) restored latest viewport; read had been pinned to old scroll position. This explains missed output match despite task completion. The wait command's exit 0 came from later pane-list, not wait-output (which timed out); timeout was not trial failure. -- Submitted read-only follow-up through supervisor: resume SAME saved worker after reload, verify unchanged file/log, no writes/new implementation/scheduler. Process `proc_5d3a` watches a worker session write, then captures matching trial panes to allow inspecting actual UI. Test parent remains `w8:p69`. -- Resumed Fireworks implementation worker through original native protocol: run `ffeed828-fa87-40ae-849b-7968487e34c3`, owns source/tests fixes for below findings; must not reload trial panes. Parent owns isolated functional trial. External review remains separately blocked; this implementation continuation is not its executor substitute. - -## Intercom observations - -- First live probe passed worker -> supervisor. Parent inspected rendered source-labelled incoming `INTERCOM-PROBE-20260910` and supervisor text `INTERCOM-PROBE-RECEIVED`. Editor retained exact unsent `UNSENT-DRAFT-KEEP-20260910`. Capture: `package-supervision-captures/07-intercom-supervisor-draft.txt`. Worker auto-exited normally afterward. -- Test supervisor incorrectly claimed “Both directions of the probe are confirmed”; local acknowledgment is NOT return-path delivery. Parent corrected that claim in the next test prompt. -- Return-path test now requested: resume same worker, worker `intercom ask` sends `INTERCOM-RETURN-PROBE` to exact test supervisor, supervisor `intercom reply` returns `INTERCOM-RETURN-ACK`. No file/plan changes or other recipients. Cleared ONLY parent's own previous sentinel with Ctrl+U before sending this next instruction. -- Process `proc_a69a` watches the existing worker session for the new ask tool call and inserts `UNSENT-WORKER-DRAFT-KEEP-20260910` into the unique test worker pane without submitting. It saves that pane ID to `/tmp/goals-edxeth-trial-HSahSm/return-probe-pane.txt`. Inspect rendered reply and surviving draft, plus auto-exit behavior. No terminal input was sent to user/demo panes. - -Return-path attempt 1 did NOT send an ask: worker's final text said “Now sending the ask and waiting for the reply,” but session contains only intercom list then final text; stock auto-exit closed it. Observer `proc_a69a` timed out without injecting a draft. The test supervisor independently inspected its pending asks and worker transcript, rejected the claimed action, and resumed the same session again without parent intervention. This is useful observed supervisor recovery, not successful reply delivery. On second attempt parent observed active pane `w8:p6E` and manually typed ONLY the test sentinel `UNSENT-WORKER-DRAFT-KEEP-20260910`, no Enter. Observer `proc_1403` awaits reply result and captures both panes; inspect outputs, don't infer success from process exit status. - -Return-path attempt 2 succeeded: actual worker tool result says `Reply from 01a0898a: INTERCOM-RETURN-ACK`; worker pane still showed exact unsent draft. Saved both panes in `package-supervision-captures/08-intercom-return-worker-draft.txt`. HOWEVER stock auto-exit then closed w8:p6E and lost the draft. Supervisor quoted worker claim that no draft existed; this contradicts captured UI. Do not accept model-context absence as editor-state evidence. - -Testing minimal stock alternative, NOT editing runtime or repo defaults yet: temporary profile `/tmp/goals-edxeth-trial-HSahSm/agent/agents/goals-worker.md` changed to `auto-exit:false` and completion via Intercom, no caller_ping/exit. Existing saved-session resume retains pinned invocation autoExit (`resume-service.ts:427-429`), so supervisor asked to launch one fresh read-only `draft-safe-trial` after previous worker ended; goal remains complete. Parent already observed old pane closed. Supervisor's mere absence-of-Intercom-peers check would not itself prove termination. New worker should send `DRAFT-SAFE-DONE` and remain open. Observer `proc_b063` watches actual incoming custom-message entry, not text in prompt, then captures trial panes. Await actual outcome before changing defaults. No global configuration changes. - -Open-pane configuration initial observation: fresh worker `draft-safe-trial` (runtime id `8317cac5`, Intercom session `003c8e70-9999-4920-8a5c-064172010188`) sent actual `DRAFT-SAFE-DONE`; supervisor acknowledged it while subagent remained open. Rendered worker `w8:p6F` completed its read-only report and stayed idle/open, no caller_ping or exit. This avoids relying on automatic child-exit report. Parent then typed unsent `UNSENT-OPEN-WORKER-DRAFT-KEEP` into worker editor and asked supervisor to use Intercom send for a fresh OPEN-PANE-PROBE, with actual Intercom OPEN-PANE-ACK response. Observer `proc_3aef` watches exact incoming message and captures both panes. Reload while this worker remains open is still untested; do not claim lifecycle acceptance yet. - -## Scheduler UI and review status - -- Actual `/schedule-prompt` -> Jobs overlay displays named jobs and offers add, toggle, scope, remove and cleanup; it does NOT offer direct prompt editing in this installed version. Prompt editing is available through `schedule_prompt update`. Captured `10-scheduler-jobs-ui.txt`. -- Toggled `goals-ui-probe` disabled through actual UI, confirmed storage `enabled:false` before reload. `/reload` preserved live worker w8:p6F and its unsent draft, plan and reviewed status. Actual subsequent Intercom `RELOAD-LIVE-ACK` came from same session 003c8e70; capture 14. BUT scheduler's automatic disabled-job cleanup removed the test job, so updating its old ID correctly failed. This is a user-control persistence gap, not all session-bound jobs disappearing. -- Authorized one fresh test job, create 1h/old prompt then update SAME new ID to 10s/new prompt, let actual scheduler fire and remove itself. Observer `proc_4f83` requires actual scheduled_prompt marker AND schedule_prompt removal result, not rendered setup prompt. -- Stable implementation continuation from worker: 158 tests pass, typecheck/lint pass; parent inspected saved logs. Actual resumed implementation model gpt-6-astra (resume inherited parent default), not Fireworks. All visible trials use Fireworks. Source not committed yet. -- Parent finally read requested `/review` skill at `/home/code/.agents/skills/review/SKILL.md`: Pi route is native read-only different-family reviewers, explicitly NOT Claude/Codex CLI. Corrected original routing; workflow `a1bcfc11-0212-410e-aad7-6fd704f7e9c6` runs DeepSeek V4 Flash and GLM 5p3 Flash (exact registered Fireworks IDs), two read-only reviewers. No active implementation writer. Workflow steering says no live foreground route; new scheduler finding recorded here for parent triage even if reviewers have already read older evidence. Do not start duplicate reviews. - -## Timer result and delayed reload failure - -- Fresh timer edit test passed: one scheduled_prompt event at 04:47:38Z contained NEW prompt for job `np-gHnDsp_`, removal result 04:47:44Z, actual store empty. Capture `15-edited-timer-fired-once.json`, rendered response `16-timer-visible-response.txt`. Old prompt never fired. Updating through public tool affects real timer delivery, without extra model override. -- For solo recovery test, parent cleared ONLY its own unsent worker sentinel with Ctrl+U and pressed Ctrl+D in test worker w8:p6F. This normal worker exit triggered delayed parent crash after earlier reload. Source stack in capture 17 points to stock edxeth, not pi-goals. Parent did not touch user panes or patch runtime. Previous 'reload passed' claim is withdrawn for full lifecycle; only immediate re-targeting worked. -- Parent restarted Pi in SAME test pane w8:p69 with same isolated profile/model and explicit `--session .../2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl`; no new supervisor context or repeated completed task. Worker w8:p6F is closed. Next: inspect restored UI, `/goals attach /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/solo-recovery.md solo`, confirm stopped writers in actual menu and let main session implement fixture's solo.txt+log. Parent created only plan fixture, not deliverable. -- Both active native reviewers received these corrections through exact listed Intercom sessions (`01a089a2-ebf3` DeepSeek, `01a089a2-ebf0` GLM); workflow had no foreground steering route. No duplicate review started. - -## Solo result and review recovery - -- Solo recovery PASSED after restarting saved supervisor and selecting explicit stopped-worker confirmation. Main session produced `solo.txt` with exact 8 bytes `solo-ok\n`, saved real `evidence/solo.log`, and CompleteGoal recorded `Solo self-verification`. Parent independently asserted both new bytes and original hello bytes unchanged. Capture `19-solo-recovery-completed.log`. Test pane w8:p69 remains open in solo mode; no active test worker and no scheduler jobs. -- Both native reviewers hung on broad `find /` commands, despite exact package locations being available. Parent inspected child transcripts and exact process ancestry: GLM shell group 723570/find 723573, DeepSeek shell group 726281/find 726285. Kernel wait channels were `__wait_on_buffer` and `d_alloc_parallel`. Sent SIGTERM only to those review-owned process groups, then native steer with exact installed package paths and instruction to finalize bounded reviews. No global filesystem/mount change or executor substitution. Diff snapshot `.local/recovery/review-search-interruption.patch`. -- Source counts measured: prototype.ts 426 lines vs 186 original; added prompts 88 lines; total source increase 328 lines. Total src tree still includes inactive older implementation. Do not claim smaller until actual simplification and behavior validation. - -## Parent source findings to investigate - -- Worker widget reads `PI_GOALS_SHARED_PLAN`, but no setting of that environment variable has yet been found. A lineage-only child may show an empty widget. Test actual child, not just unit mocks. -- `/goals attach <path> solo` restores a noted worker but enters solo without the explicit stop confirmation used by `/goals solo`. -- `/goals solo` retains a recorded worker handle, which still blocks attaching a different plan. Its confirmation menu lacks a generation check after await. Watcher/scheduler cleanup on this transition needs verification. -- Watch callback calls refresh before debounce; transient unreadable/empty plan can invalidate persisted signoffs. Completion considers cancelled goals unfinished. -- Main's staleness-based plan-upkeep reminder has not visibly been ported for ordinary solo turns. Scheduler-only guidance is not equivalent. - -These are source observations and plausible failure paths, not yet reproduced trial outcomes. Fix minimal confirmed issues; do not build another runtime. diff --git a/slop/reviews/full-profile-supervisor/review.md b/slop/reviews/full-profile-supervisor/review.md deleted file mode 100644 index 9fa644c..0000000 --- a/slop/reviews/full-profile-supervisor/review.md +++ /dev/null @@ -1,39 +0,0 @@ -# Full-profile supervisor: focused implementation - -## Approved scope - -The user explicitly chose normal Pi extensions and tools, including bash/edit/write and custom actions, with the division of work enforced by role instructions rather than a tool denylist. This change does not grant the supervisor implementation authority: the repeated short opening directs inspection/diagnosis and delegates changes through SteerWorker. The long prompt explicitly states that this is not an enforced sandbox. - -## Changes - -- `src/herdr.ts`: remove only `--no-extensions`; retain explicit source extension, fork, role/binding environment, name and selected model. The inherited environment and normal Pi discovery remain intact. -- `src/supervisor-session.ts`: remove the supervisor BLOCKED_TOOLS constant, both active-tool filters, and tool_call denylist hook. No replacement hooks, per-tool reminders, approval changes or lifecycle repair. -- `src/prompts.ts`: centralize the concise instruction in the already-repeated opening, and clarify the trust boundary in long orientation. -- README/AGENTS: describe normal-profile discovery and instruction-only inspection; avoid claiming hard read-only enforcement or full lifecycle recovery. -- Tests assert bash/edit/write/intercom and custom tools survive startup, simulated reload and reconnect without resetting extension selections. The launcher retains normal discovery. The real native Pi RPC test now enables normal discovery in an isolated agent directory, auto-loads a custom inspection tool without `-e`, verifies it reaches the supervisor's model tool schema, and still observes exact SteerWorker delivery. Its worker remains deliberately isolated with `--no-extensions`. - -## Sources inspected - -Installed Pi documentation: `docs/usage.md` extension/resource discovery flags, `docs/extensions.md` active-tool APIs and loading, `docs/packages.md` profile scope/deduplication. Read applicable local `recommending-pi-extensions` skill for the full-permission trust boundary. No packages installed or fetched. Existing Intercom reuse/fallback code is unchanged; broker/native tests pass. - -## Validation - -`validation.txt` records final successful run: - -``` -env -u PI_GOALS_EVIDENCE_DIR -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE npm test -npm run typecheck -npm run lint -npm run build -git diff --check -``` - -108/108 tests in 19 files, typecheck, lint (37 files), build and diff check passed. No changes to `src/index.ts` or `src/approval.ts`; worker planning restrictions and approval checks remain intact. No supervisor BLOCKED_TOOLS, setActiveTools or tool_call enforcement remains. - -## Limits and remaining acceptance - -This verifies normal discovery using a deterministic local model and an isolated custom extension. It does not prove a real user's complete profile respects the role instruction. Arbitrary extensions retain their own hooks/side effects/tool policies; tools can still write if the model disregards its task. The parent still needs to run real full-profile Herdr acceptance and obtain an independent review. No panes were opened, reloaded or operated. - -Issue #6 cancellation, Ready content drift, compaction delivery, fresh-shell role restoration and other lifecycle bugs are intentionally not fixed in this scoped task. Existing running supervisors retain their already-loaded profile until appropriately restarted/reloaded by their owner. - -Pre-existing dirty `slop/reviews/review-fixes-native/supervisor-events.jsonl`, `worker-events.jsonl` and untracked `docs/human_journal.md` were neither modified nor staged by this task. No commits include them. diff --git a/slop/reviews/full-profile-supervisor/validation.txt b/slop/reviews/full-profile-supervisor/validation.txt deleted file mode 100644 index 2fb5fd0..0000000 --- a/slop/reviews/full-profile-supervisor/validation.txt +++ /dev/null @@ -1,26 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 19 passed (19) - Tests 108 passed (108) - Start at 12:24:25 - Duration 4.69s (transform 4.90s, setup 0ms, import 9.57s, tests 12.54s, environment 3ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 37 files in 109ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - diff --git a/slop/reviews/handshake-native/supervisor-events.jsonl b/slop/reviews/handshake-native/supervisor-events.jsonl deleted file mode 100644 index 2f97afd..0000000 --- a/slop/reviews/handshake-native/supervisor-events.jsonl +++ /dev/null @@ -1,21 +0,0 @@ -{"type":"extension_ui_request","id":"1b7b765d-82f4-4ac3-bd12-011f1f3227af","method":"notify","message":"supervisor model: offline/test","notifyType":"info"} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-visible-supervisor-v2","data":{"version":2,"workerSessionId":"01a080c2-dc16-705a-b544-e383580d9ac7","planPath":"/tmp/goals-native-pair-vfPu3e/plan.md"},"id":"e0a271f9","parentId":"d768c1fd","timestamp":"2026-09-08T11:24:10.287Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"in","message":{"binding":"native-pair-test","role":"worker","kind":"view","id":"cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc","reason":"settled","backgroundQuiet":true}},"id":"848cb24d","parentId":"e0a271f9","timestamp":"2026-09-08T11:24:10.295Z"}} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc"}],"timestamp":1788866650296}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc"}],"timestamp":1788866650296}} -{"type":"message_start","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."},"partialArgs":"{\"instruction\":\"Read the real outputs before declaring completion.\"}","streamIndex":0}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"pending","timestamp":1788866650324}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_start","contentIndex":0}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_delta","contentIndex":0,"delta":"{\"instruction\":\"Read the real outputs before declaring completion.\"}"}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_end","contentIndex":0,"toolCall":{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}}} -{"type":"message_end","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1788866650324,"rawStopReason":"tool_calls"}} -{"type":"tool_execution_start","toolCallId":"test-steer","toolName":"SteerWorker","args":{"instruction":"Read the real outputs before declaring completion."}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"out","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"22068654-6e7c-4328-890f-9382b25c8ea7","text":"Read the real outputs before declaring completion."}},"id":"0c0166ea","parentId":"ed2e1ca2","timestamp":"2026-09-08T11:24:10.345Z"}} -{"type":"tool_execution_end","toolCallId":"test-steer","toolName":"SteerWorker","result":{"content":[{"type":"text","text":"Worker instruction 22068654-6e7c-4328-890f-9382b25c8ea7 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false},"isError":false} -{"type":"message_start","message":{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 22068654-6e7c-4328-890f-9382b25c8ea7 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788866650346}} -{"type":"message_end","message":{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 22068654-6e7c-4328-890f-9382b25c8ea7 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788866650346}} -{"type":"turn_end","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1788866650324,"rawStopReason":"tool_calls"},"toolResults":[{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 22068654-6e7c-4328-890f-9382b25c8ea7 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788866650346}]} -{"type":"turn_start"} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"worker","kind":"received","id":"22068654-6e7c-4328-890f-9382b25c8ea7"}},"id":"6b63b93d","parentId":"8ce5aa46","timestamp":"2026-09-08T11:24:10.348Z"}} -{"id":"supervisor-state","type":"response","command":"get_state","success":true,"data":{"model":{"id":"test","name":"Offline test model","reasoning":false,"input":["text"],"contextWindow":16000,"maxTokens":1000,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"api":"openai-completions","provider":"offline","baseUrl":"http://127.0.0.1:37773"},"thinkingLevel":"off","isStreaming":true,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/tmp/goals-native-pair-vfPu3e/agent/sessions/--tmp-goals-native-pair-vfPu3e--/2026-09-08T11-24-10-111Z_01a080c2-dfff-7290-9118-66b87ab29080.jsonl","sessionId":"01a080c2-dfff-7290-9118-66b87ab29080","autoCompactionEnabled":true,"messageCount":5,"pendingMessageCount":0}} \ No newline at end of file diff --git a/slop/reviews/handshake-native/supervisor-stderr.txt b/slop/reviews/handshake-native/supervisor-stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/slop/reviews/handshake-native/worker-events.jsonl b/slop/reviews/handshake-native/worker-events.jsonl deleted file mode 100644 index 7ce503f..0000000 --- a/slop/reviews/handshake-native/worker-events.jsonl +++ /dev/null @@ -1,22 +0,0 @@ -{"id":"planning","type":"response","command":"prompt","success":true} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788866649278}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788866649278}} -{"type":"message_start","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"pending","timestamp":1788866649355}} -{"type":"message_update","assistantMessageEvent":{"type":"text_start","contentIndex":0}} -{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Test context retained. Actual outputs still need inspection."}} -{"type":"message_update","assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Test context retained. Actual outputs still need inspection."}} -{"type":"message_end","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788866649355,"rawStopReason":"stop"}} -{"type":"turn_end","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788866649355,"rawStopReason":"stop"},"toolResults":[]} -{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788866649278},{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788866649355,"rawStopReason":"stop"}],"willRetry":false} -{"type":"agent_settled"} -{"id":"worker-state","type":"response","command":"get_state","success":true,"data":{"model":{"id":"test","name":"Offline test model","reasoning":false,"input":["text"],"contextWindow":16000,"maxTokens":1000,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"api":"openai-completions","provider":"offline","baseUrl":"http://127.0.0.1:37773"},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/tmp/goals-native-pair-vfPu3e/agent/sessions/--tmp-goals-native-pair-vfPu3e--/2026-09-08T11-24-09-110Z_01a080c2-dc16-705a-b544-e383580d9ac7.jsonl","sessionId":"01a080c2-dc16-705a-b544-e383580d9ac7","autoCompactionEnabled":true,"messageCount":2,"pendingMessageCount":0}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"out","message":{"binding":"native-pair-test","role":"worker","kind":"view","id":"cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc","reason":"settled","backgroundQuiet":true}},"id":"57ac2295","parentId":"21d6e68a","timestamp":"2026-09-08T11:24:10.294Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"supervisor","kind":"received","id":"cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc"}},"id":"7ffd87dc","parentId":"57ac2295","timestamp":"2026-09-08T11:24:10.295Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"supervisor","kind":"received","id":"cf656aef-1404-4d5c-9a11-4fbd4ed5b5dc"}},"id":"b2ffeed1","parentId":"7ffd87dc","timestamp":"2026-09-08T11:24:10.335Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"in","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"22068654-6e7c-4328-890f-9382b25c8ea7","text":"Read the real outputs before declaring completion."}},"id":"34521c62","parentId":"b2ffeed1","timestamp":"2026-09-08T11:24:10.346Z"}} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788866650346}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788866650346}} \ No newline at end of file diff --git a/slop/reviews/handshake-native/worker-stderr.txt b/slop/reviews/handshake-native/worker-stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/slop/reviews/lifecycle-recovery/cancellation-before.txt b/slop/reviews/lifecycle-recovery/cancellation-before.txt deleted file mode 100644 index f1c03ec..0000000 --- a/slop/reviews/lifecycle-recovery/cancellation-before.txt +++ /dev/null @@ -1,33 +0,0 @@ - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - ❯ test/goals-flow.test.ts (21 tests | 1 failed | 20 skipped) 71ms - × accepts only an approval for the exact clean commit and goal block 70ms - -⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ - - FAIL test/goals-flow.test.ts > /goals flow > accepts only an approval for the exact clean commit and goal block -AssertionError: expected false to be true // Object.is equality - -- Expected -+ Received - -- true -+ false - - ❯ test/goals-flow.test.ts:318:38 - 316| const cancelled = flow.tools.get("CompleteGoal").execute("cancelled… - 317| controller.abort(); // Cancel while the background-state lookup yie… - 318| expect((await cancelled).isError).toBe(true); - | ^ - 319| expect(readFileSync(planPath, "utf8")).toBe(beforeCancel); - 320| expect((await flow.tools.get("CompleteGoal").execute("already-cance… - -⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ - - - Test Files 1 failed (1) - Tests 1 failed | 20 skipped (21) - Start at 12:28:54 - Duration 734ms (transform 273ms, setup 0ms, import 544ms, tests 71ms, environment 0ms) - diff --git a/slop/reviews/lifecycle-recovery/checkpoint-validation.txt b/slop/reviews/lifecycle-recovery/checkpoint-validation.txt deleted file mode 100644 index 5de9051..0000000 --- a/slop/reviews/lifecycle-recovery/checkpoint-validation.txt +++ /dev/null @@ -1,26 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 19 passed (19) - Tests 110 passed (110) - Start at 12:30:42 - Duration 3.60s (transform 4.23s, setup 0ms, import 8.38s, tests 8.31s, environment 2ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 37 files in 34ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - diff --git a/slop/reviews/lifecycle-recovery/native-validation.txt b/slop/reviews/lifecycle-recovery/native-validation.txt deleted file mode 100644 index c3cec9c..0000000 --- a/slop/reviews/lifecycle-recovery/native-validation.txt +++ /dev/null @@ -1,33 +0,0 @@ - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - -stdout | test/native-compaction-delivery.test.ts > real Pi preserves worker delivery through compaction success, failure and cancellation -real Pi worker/success: retained message presented once and saved, no extension errors - -stdout | test/native-compaction-delivery.test.ts > real Pi preserves worker delivery through compaction success, failure and cancellation -real Pi worker/failure: retained message presented once and saved, no extension errors - -stdout | test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session -Native Pi pair: fork retained planning context; SteerWorker delivered exactly: Read the real outputs before declaring completion. - - ✓ test/native-intercom.test.ts > runs a forked Pi supervisor and receives its exact instruction in another Pi session 2071ms -stdout | test/native-compaction-delivery.test.ts > real Pi preserves worker delivery through compaction success, failure and cancellation -real Pi worker/cancel: retained message presented once and saved, no extension errors - - ✓ test/native-compaction-delivery.test.ts > real Pi preserves worker delivery through compaction success, failure and cancellation 2239ms -stdout | test/native-compaction-delivery.test.ts > real Pi preserves supervisor delivery through compaction success, failure and cancellation -real Pi supervisor/success: retained message presented once and saved, no extension errors - -stdout | test/native-compaction-delivery.test.ts > real Pi preserves supervisor delivery through compaction success, failure and cancellation -real Pi supervisor/failure: retained message presented once and saved, no extension errors - -stdout | test/native-compaction-delivery.test.ts > real Pi preserves supervisor delivery through compaction success, failure and cancellation -real Pi supervisor/cancel: retained message presented once and saved, no extension errors - - ✓ test/native-compaction-delivery.test.ts > real Pi preserves supervisor delivery through compaction success, failure and cancellation 2176ms - - Test Files 2 passed (2) - Tests 3 passed (3) - Start at 12:59:23 - Duration 4.60s (transform 52ms, setup 0ms, import 133ms, tests 6.49s, environment 0ms) - diff --git a/slop/reviews/lifecycle-recovery/review.md b/slop/reviews/lifecycle-recovery/review.md deleted file mode 100644 index 5cda22f..0000000 --- a/slop/reviews/lifecycle-recovery/review.md +++ /dev/null @@ -1,44 +0,0 @@ -# Lifecycle recovery and role-aware commands - -## Scope and decisions - -Continue issue #6 and the independent lifecycle review after full-profile commit `1d52857`. Keep normal extensions/tools; do not alter Git/evidence approval policy. The supervisor approved delayed role selection at session_start, a durable inbound-message record, and requiring Pi 0.85.1 instead of maintaining a 0.84 compatibility retry layer. The user additionally authorized `work`, `supervise`, and `noplan` recovery commands. - -`138bde5` already committed/pushed the first increment: CompleteGoal cancellation and Ready content stability. Remaining changes build on it, not replace it. - -## Fixes - -- CompleteGoal checks its signal before/after the asynchronous background lookup and before persistence; stale binding/version/phase or a disposed runtime also rejects. Cancelling one call leaves the plan active. `cancellation-before.txt` records a regression failure against the earlier implementation (it returned success after abort); the updated test verifies no tick/sign-off marker is written and a later valid call still works. -- Ready checks the displayed plan content before launch, after launch, after worker-model restoration and after the first view. A changed plan returns to planning using the existing pane; it does not start the changed implementation. Tests mutate content at launch and model restoration, then select Ready again without another pane. -- Real supervisor and worker sessions now select their role when session entries are available, before registering either role's handlers. A complete supervisor marker (worker, owner, plan path, approval binding) is written before model restoration/compaction. Persisted identity wins over launch environment. Legacy bootstrap markers migrate only with an existing pairing; incomplete records throw visibly without enabling worker mode. Stopped supervisor forks retain their identity. No tool denylist was reintroduced. -- Intercom saves each incoming non-started view/steer in a bounded inbox before calling Pi. It acknowledges only a matching user message_start. That is **session acceptance**, not model judgment or action execution. Unconfirmed payloads are restored from the active branch on reload; separate incremental views are retained rather than overwritten. A pending Pi queue is not re-enqueued while waiting for presentation. Completed/detached bindings and shutdown cancel delivery timers. -- Pi 0.85.1 has a public session_compact_failed event and fixes isIdle to include manual compaction; repo 0.84.1 did not. The minimum peer and exact dev version are now 0.85.1. Installation used `--ignore-scripts --no-audit --no-fund`; no global Pi change. Success/failure/cancel events resume retained context. Since session_compact precedes final controller cleanup, delivery waits for compaction-aware idle state; it never probes by sending a prompt during compaction. Waiting is bounded to 300 one-second idle checks with retained payload and a visible reconnect instruction on exhaustion, not a deadline that interrupts the model. -- Readiness/reconnect paths allow five minutes rather than five seconds. Reload while an inherited compaction is active waits for it rather than starting a second one. An existing last compaction or Pi's Already compacted/Nothing to compact result can proceed to bootstrap. Startup model/compaction failure is communicated through the existing hello so the worker sees the cause promptly. A later ready hello clears the failure. -- An established worker pairing republishes one fresh current view when disconnected→connected, including after supervisor-only reload where the old stopped view had already been accepted. This reuses the connection callback; Ready retains its own initial publication and session_start/reconnect no longer separately publish duplicates. Cleared/completed sessions do not restart monitoring. -- A started-worker view now reports that work is running instead of falsely claiming a newer review is queued for delivery. - -## Command meanings - -- `/goals work`: existing approved worker session reconnects its saved pairing/model; no new plan, pairing or model fallback. Missing/unapproved pairing is rejected. -- `/goals supervise`: existing saved supervisor reconnects its role/model/pairing. Running it in a worker session is rejected rather than converting the role. -- `/goals noplan`: leave planning restrictions and preserve the draft/history without Ready, implementation, supervisor launch or file deletion. In-flight Ready is invalidated. It does not claim the retained draft was approved. -- `/goals reconnect` remains generic recovery; `/goals restart` explicitly replaces only the tracked pane and invalidates the prior binding; `/goals clear` closes/disconnects while retaining the plan file. - -## Runtime validation actually observed - -`native-validation.txt` is fresh verbose output from installed Pi 0.85.1 with a local deterministic HTTP model; no credentials or model credits used. - -1. Real Pi worker and supervisor delivery during manual compaction: success, local model failure and cancellation, six cases total. Each retained payload is presented exactly once and saved in the session; no extension_error events. The transport in this fixture is deterministic, while Pi owns the real compaction and prompt lifecycle. Success uses an extension-provided summary; failure exercises Pi's HTTP summarization failure. These are not rendered Herdr sessions or 60-second real-model runs. -2. Real native Pi/Intercom pair: full-profile discovery in an isolated agent directory, exact steering delivery, then supervisor termination and fresh-shell `--session` resume with role/binding launcher environment removed. The resumed model sees SteerWorker/ApproveGoal and the discovered profile tool, not CompleteGoal, and retains the supervisor opening. No second supervisor pairing is constructed. - -Hook tests additionally cover a simulated 60-second inherited compaction without competing compaction, five-minute Ready/reconnect patience, immediate reported failure plus rejoin, retained distinct deltas through reload, delayed presentation without duplicate enqueue, role migration/incomplete identity, cancelled completion, Ready content drift, and command semantics. Existing paired tests cover symmetric reconnect/model restoration. The accepted-view reconnect regression checks one new view ID and unchanged-payload replay deduplication separately; cleared/completed pairings produce no new view. The tests model `/reload` with new extension instances or saved state; no real interactive `/reload` command was exercised in this task. - -Final `validation.txt`: 123 tests in 22 files, typecheck, lint, build, and diff check pass. Native fixture initially could not compact a single retained turn; it now seeds two sufficiently sized turns. This corrected fixture setup is not counted as a product failure. No test processes from earlier runs remained when resuming after timeout; all processes started by these tests were shut down. - -## Limits and remaining acceptance - -The parent must still run full-profile Herdr acceptance: actual reloads in both orders, drafting/Ready/checkpoint interruption, stopped pair resume, real-model long compaction, and an unmet-outcome correction followed by both sign-offs. Automated session acceptance does not establish judgment quality or cheaper-worker success. - -The inbox holds at most 64 messages; overflow is visible and unacknowledged. Arbitrary extensions that rewrite or consume injected user messages can defeat exact-text acceptance matching; crashes between message_start and message persistence can require review/replay. This is not an exactly-once execution guarantee or a general durable model queue. Role instructions remain the only prohibition on supervisor writes. Existing all-cancelled completion behavior and Git-tracked verification policy are unchanged. - -No user or test Herdr panes, research sessions, human journal, or pre-existing dirty native evidence logs were operated/read/edited by this task. Changes to package-lock reflect the approved local Pi dependency upgrade. This report does not claim all issue #6 behavioral acceptance is complete. diff --git a/slop/reviews/lifecycle-recovery/validation.txt b/slop/reviews/lifecycle-recovery/validation.txt deleted file mode 100644 index ada57fe..0000000 --- a/slop/reviews/lifecycle-recovery/validation.txt +++ /dev/null @@ -1,26 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 22 passed (22) - Tests 123 passed (123) - Start at 13:08:28 - Duration 4.90s (transform 5.07s, setup 0ms, import 11.10s, tests 11.14s, environment 4ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 41 files in 77ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - diff --git a/slop/reviews/native-intercom/supervisor-events.jsonl b/slop/reviews/native-intercom/supervisor-events.jsonl deleted file mode 100644 index 844fdfa..0000000 --- a/slop/reviews/native-intercom/supervisor-events.jsonl +++ /dev/null @@ -1,21 +0,0 @@ -{"type":"extension_ui_request","id":"bd32f722-e23e-418f-9c7d-ad1c95622d41","method":"notify","message":"supervisor model: offline/test","notifyType":"info"} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-visible-supervisor-v2","data":{"version":2,"workerSessionId":"01a08038-30f7-77a0-943b-8a16928ce6f1","planPath":"/tmp/goals-native-pair-KSyOe8/plan.md"},"id":"6b40c31e","parentId":"a6375cb3","timestamp":"2026-09-08T08:52:42.516Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"in","message":{"binding":"native-pair-test","role":"worker","kind":"view","id":"508e947c-aff9-427b-a8ff-6f8158fada1f","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: 508e947c-aff9-427b-a8ff-6f8158fada1f","reason":"settled","backgroundQuiet":true}},"id":"666c7a1b","parentId":"6b40c31e","timestamp":"2026-09-08T08:52:42.525Z"}} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: 508e947c-aff9-427b-a8ff-6f8158fada1f"}],"timestamp":1788857562526}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: 508e947c-aff9-427b-a8ff-6f8158fada1f"}],"timestamp":1788857562526}} -{"type":"message_start","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."},"partialArgs":"{\"instruction\":\"Read the real outputs before declaring completion.\"}","streamIndex":0}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"pending","timestamp":1788857562553}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_start","contentIndex":0}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_delta","contentIndex":0,"delta":"{\"instruction\":\"Read the real outputs before declaring completion.\"}"}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_end","contentIndex":0,"toolCall":{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}}} -{"type":"message_end","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1788857562553,"rawStopReason":"tool_calls"}} -{"type":"tool_execution_start","toolCallId":"test-steer","toolName":"SteerWorker","args":{"instruction":"Read the real outputs before declaring completion."}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"out","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"1508350f-7ffc-4b01-a976-da06813569f8","text":"Read the real outputs before declaring completion."}},"id":"066af95c","parentId":"1bcc9002","timestamp":"2026-09-08T08:52:42.575Z"}} -{"type":"tool_execution_end","toolCallId":"test-steer","toolName":"SteerWorker","result":{"content":[{"type":"text","text":"Worker instruction 1508350f-7ffc-4b01-a976-da06813569f8 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false},"isError":false} -{"type":"message_start","message":{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 1508350f-7ffc-4b01-a976-da06813569f8 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788857562575}} -{"type":"message_end","message":{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 1508350f-7ffc-4b01-a976-da06813569f8 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788857562575}} -{"type":"turn_end","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1788857562553,"rawStopReason":"tool_calls"},"toolResults":[{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 1508350f-7ffc-4b01-a976-da06813569f8 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788857562575}]} -{"type":"turn_start"} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"worker","kind":"received","id":"1508350f-7ffc-4b01-a976-da06813569f8"}},"id":"46f0a926","parentId":"7f0c0a7d","timestamp":"2026-09-08T08:52:42.578Z"}} -{"id":"supervisor-state","type":"response","command":"get_state","success":true,"data":{"model":{"id":"test","name":"Offline test model","reasoning":false,"input":["text"],"contextWindow":16000,"maxTokens":1000,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"api":"openai-completions","provider":"offline","baseUrl":"http://127.0.0.1:38351"},"thinkingLevel":"off","isStreaming":true,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/tmp/goals-native-pair-KSyOe8/agent/sessions/--tmp-goals-native-pair-KSyOe8--/2026-09-08T08-52-42-353Z_01a08038-34f1-74a8-a4a6-0f10c436d961.jsonl","sessionId":"01a08038-34f1-74a8-a4a6-0f10c436d961","autoCompactionEnabled":true,"messageCount":5,"pendingMessageCount":0}} \ No newline at end of file diff --git a/slop/reviews/native-intercom/supervisor-stderr.txt b/slop/reviews/native-intercom/supervisor-stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/slop/reviews/native-intercom/worker-events.jsonl b/slop/reviews/native-intercom/worker-events.jsonl deleted file mode 100644 index 45fb6d7..0000000 --- a/slop/reviews/native-intercom/worker-events.jsonl +++ /dev/null @@ -1,21 +0,0 @@ -{"id":"planning","type":"response","command":"prompt","success":true} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788857561517}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788857561517}} -{"type":"message_start","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"pending","timestamp":1788857561554}} -{"type":"message_update","assistantMessageEvent":{"type":"text_start","contentIndex":0}} -{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Test context retained. Actual outputs still need inspection."}} -{"type":"message_update","assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Test context retained. Actual outputs still need inspection."}} -{"type":"message_end","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788857561554,"rawStopReason":"stop"}} -{"type":"turn_end","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788857561554,"rawStopReason":"stop"},"toolResults":[]} -{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788857561517},{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788857561554,"rawStopReason":"stop"}],"willRetry":false} -{"type":"agent_settled"} -{"id":"worker-state","type":"response","command":"get_state","success":true,"data":{"model":{"id":"test","name":"Offline test model","reasoning":false,"input":["text"],"contextWindow":16000,"maxTokens":1000,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"api":"openai-completions","provider":"offline","baseUrl":"http://127.0.0.1:38351"},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/tmp/goals-native-pair-KSyOe8/agent/sessions/--tmp-goals-native-pair-KSyOe8--/2026-09-08T08-52-41-336Z_01a08038-30f7-77a0-943b-8a16928ce6f1.jsonl","sessionId":"01a08038-30f7-77a0-943b-8a16928ce6f1","autoCompactionEnabled":true,"messageCount":2,"pendingMessageCount":0}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"out","message":{"binding":"native-pair-test","role":"worker","kind":"view","id":"508e947c-aff9-427b-a8ff-6f8158fada1f","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: 508e947c-aff9-427b-a8ff-6f8158fada1f","reason":"settled","backgroundQuiet":true}},"id":"ac6b2134","parentId":"741cdd0c","timestamp":"2026-09-08T08:52:42.523Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"supervisor","kind":"received","id":"508e947c-aff9-427b-a8ff-6f8158fada1f"}},"id":"fd82ceb3","parentId":"ac6b2134","timestamp":"2026-09-08T08:52:42.525Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"in","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"1508350f-7ffc-4b01-a976-da06813569f8","text":"Read the real outputs before declaring completion."}},"id":"495adb1a","parentId":"fd82ceb3","timestamp":"2026-09-08T08:52:42.575Z"}} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788857562576}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788857562576}} \ No newline at end of file diff --git a/slop/reviews/native-intercom/worker-stderr.txt b/slop/reviews/native-intercom/worker-stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/slop/reviews/package-supervision-captures/01-ready-menu.txt b/slop/reviews/package-supervision-captures/01-ready-menu.txt deleted file mode 100644 index 98c9baf..0000000 --- a/slop/reviews/package-supervision-captures/01-ready-menu.txt +++ /dev/null @@ -1,57 +0,0 @@ - old │ │ new │ - 1 │ # Goal plan │ 1 │ # Goal plan - 2 │ │ 2 │ - 3 │ ## Objective │ 3 │ ## Objective - ▌ 4 │ Make hello.txt contain exactly hello from the │ ▌ 4 │ Make `hello.txt` contain exactly `hello` from the - ▌ │ worker followed by one newline, and save a │ ▌ │ worker followed by one newline, and save a - ▌ │ byte-verification log in evidence/hello.log. Use │ ▌ │ byte-verification log in `evidence/hello.log`. Use - ▌ │ fireworks/accounts/fireworks/models/deepseek-v4-fla │ ▌ │ `fireworks/accounts/fireworks/models/deepseek-v4-fl - ▌ │ sh-0731 for the worker. This is a bounded │ ▌ │ ash-0731` for the worker. This is a bounded - ▌ │ functional test: no installs, no commits or network │ ▌ │ functional test: no installs, no commits or network - ▌ │ calls for the deliverable, no unrelated changes. │ ▌ │ calls for the deliverable, no unrelated changes. - ▌ │ Preserve the normal supervisor and worker UI. Draft │ ▌ │ Preserve the normal supervisor and worker UI. Draft - ▌ │ the plan and present it for review; I will select │ ▌ │ the plan and present it for review; the user - ▌ │ Ready. │ ▌ │ selects Ready. - │ │ ▌ 5 │ - │ │ ▌ 6 │ ## Preferences - │ │ ▌ 7 │ - preferred worker model: - │ │ ▌ │ fireworks/accounts/fireworks/models/deepseek-v4-fla - │ │ ▌ │ sh-0731 - │ │ ▌ 8 │ - Implementer is the `goals-worker` subagent - │ │ ▌ │ (visible terminal, normal UI), launched from this - │ │ ▌ │ supervisor session after Ready. - │ │ ▌ 9 │ - No installs, no package changes, no git commits, - - … (51 more diff lines • Ctrl+O to expand) - - - Thinking... - - Plan drafted at /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md. - - No material questions — the objective fully specifies the model, deliverables, byte requirement, and constraints. Project - inspection confirmed: empty repo (no commits), .gitignore already excludes evidence/, AGENTS.md permits exactly this - isolated supervised trial. - - Plan summary: - - - Objective: worker writes hello.txt containing exactly hello\n (6 bytes, hex 68 65 6c 6c 6f 0a) in project root; saves - byte-verification log at evidence/hello.lo ↓ Jump to latest message · End - - ○ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the - worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra - characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that - file -─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - - Review /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md - - → Ready - Discuss - Edit - Cancel - - ↑↓ navigate enter select escape/ctrl+c cancel - -─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - project on  main [?] goals: planning | 0/1 reviewed | LSP | 3.1%/1.0M (auto) | ↑31k ↓5.7k 󰆼 96.8% R207k | $0.012 \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/02-worker-byte-verification.log b/slop/reviews/package-supervision-captures/02-worker-byte-verification.log deleted file mode 100644 index fb9c3ab..0000000 --- a/slop/reviews/package-supervision-captures/02-worker-byte-verification.log +++ /dev/null @@ -1,16 +0,0 @@ -Verification log for hello.txt -Generated: 2026-09-10T04:22:36Z -Checked path: /tmp/goals-edxeth-trial-HSahSm/project/hello.txt ---- wc -c hello.txt --- -22 hello.txt ---- od -An -tx1 hello.txt --- - 68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 - 6f 72 6b 65 72 0a ---- expected hex (reference) --- -68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a ---- byte count comparison --- -observed=22 expected=22 ---- hex comparison --- -observed hex == expected hex ---- result --- -RESULT: PASS diff --git a/slop/reviews/package-supervision-captures/03-completed-plan.md b/slop/reviews/package-supervision-captures/03-completed-plan.md deleted file mode 100644 index 21fe6ab..0000000 --- a/slop/reviews/package-supervision-captures/03-completed-plan.md +++ /dev/null @@ -1,25 +0,0 @@ -# Goal plan - -## Objective -Make `hello.txt` contain exactly the single line `hello from the worker` followed by one newline — i.e. the UTF-8 byte sequence `hello from the worker\n`, 22 bytes — and save a byte-verification log in `evidence/hello.log`. Use `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` for the worker. This is a bounded functional test: no installs, no commits or network calls for the deliverable, no unrelated changes. Preserve the normal supervisor and worker UI. Draft the plan and present it for review; the user selects Ready. - -## Preferences -- preferred worker model: fireworks/accounts/fireworks/models/deepseek-v4-flash-0731 -- Implementer is the `goals-worker` subagent (visible terminal, normal UI), launched from this supervisor session after Ready. -- No installs, no package changes, no git commits, no network calls for the deliverable, no writes outside this project. -- Deliverables live in the project root: `hello.txt` (working file) and `evidence/hello.log` (verification log, already gitignored). -- Worker must create `hello.txt` and `evidence/hello.log` itself; the supervisor verifies after the worker reports, using primary-command byte checks (e.g. `od`/`wc -c`), not just the worker's claims. - -## Goals -- [x] goal: hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file - - evidence (satisfied): `hello.txt` = 22 bytes, `od -An -tx1 -v hello.txt` reads exactly `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`; `evidence/hello.log` (494 bytes, nonempty) records real `wc -c` (22) and `od` output, expected-hex reference, byte-count and hex comparisons, and `RESULT: PASS`; supervisor re-verified both independently; worker resolved model `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` matches plan - - failure modes: file missing; content wrong (truncated to `hello`, wrong casing, missing or altered `from the worker` phrase, trailing whitespace, `hello from the worker\n\n`, CRLF, missing final newline, extra bytes); verification log missing, empty, fabricated without an actual byte check, or inconsistent with the real file; deliverables outside the project root/`evidence/`; any installs, commits, or network calls for the deliverable - - note: uncommitted output is expected, not a failure — the constraint is "no commits", not "everything committed" - - evidence/verification: supervisor reads `evidence/hello.log` (documenting hex dump, byte count, and explicit pass/fail against `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) and independently re-checks `hello.txt` with `od -An -tx1` (expect the 22-byte hex above) and `wc -c` (expect 22), confirming the log matches the real file byte-for-byte - -## Log -- Drafted plan (supervisor): read existing plan, inspected project (empty repo, `evidence/` already gitignored, no commits), no questions needed — model, deliverables, and constraints were specified. Awaiting Ready. -- Revised plan (supervisor, per user clarification before Ready): corrected target content to the full literal line `hello from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`); merged the file and its verification log into one goal/outcome; removed "uncommitted" from failure modes (uncommitted output is expected). No other scope changes. Awaiting Ready before implementation or worker launch. -- Ran goal (supervisor): launched `goals-worker` (trial-hello-writer) on plan after Ready with requested model `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731`; worker confirmed resolved model, created `hello.txt` via `printf` and `evidence/hello.log` from real command output, reported PASS. Supervisor independently re-checked: `wc -c` = 22, `od -An -tx1 -v` = `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`, log matches file byte-for-byte. Constraint check: no installs, no commits (repo has zero commits), no network calls; the only other new path `docs/human_journal.md` is the prototype harness's own supervision journal (written 04:20–04:22Z from the supervisor session, before/at worker launch), not a worker change, left untouched. Goal complete. - -- Parent review: "hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file"; "Inspected the actual artifacts myself, not just the worker report. hello.txt: `wc -c` = 22; `od -An -tx1 -v` reads `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a` exactly — matching the required UTF-8 bytes of `hello from the worker\\n` with a single final newline, no CR, no BOM, no trailing extras (spans two od lines solely due to od's 16-byte wrapping). evidence/hello.log (494 bytes, nonempty, in gitignored evidence/) contains the real captured `wc -c` output (22), the real `od` dump, the expected-hex reference, byte-count and hex comparisons, and an explicit `RESULT: PASS`; log matches the on-disk file byte-for-byte. Worker resolved model was fireworks/accounts/fireworks/models/deepseek-v4-flash-0731 as planned. Constraints held: no installs, no network calls for the deliverable, no commits (repo still has zero commits). The only unexpected new path (docs/human_journal.md) was verified to be the prototype harness's own supervision journal written from the supervisor session (timestamps 04:20–04:22Z, before/at worker launch), not a worker change; left untouched per preserve-evidence. Goal satisfied."; evidence ["/tmp/goals-edxeth-trial-HSahSm/project/hello.txt","/tmp/goals-edxeth-trial-HSahSm/project/evidence/hello.log"] diff --git a/slop/reviews/package-supervision-captures/04-completion-events.json b/slop/reviews/package-supervision-captures/04-completion-events.json deleted file mode 100644 index 4180cd2..0000000 --- a/slop/reviews/package-supervision-captures/04-completion-events.json +++ /dev/null @@ -1,123 +0,0 @@ -[ - { - "file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl", - "event": { - "type": "model_change", - "id": "8c002be1", - "parentId": null, - "timestamp": "2026-09-10T04:18:56.858Z", - "provider": "fireworks", - "modelId": "accounts/fireworks/models/deepseek-v4-flash-0731" - } - }, - { - "file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl", - "tool": "schedule_prompt", - "details": { - "action": "list", - "jobs": [] - }, - "content": [ - { - "type": "text", - "text": "No cron jobs configured." - } - ] - }, - { - "file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl", - "tool": "subagent", - "details": { - "id": "3cbbafbe", - "name": "trial-hello-writer", - "agent": "goals-worker", - "status": "completed", - "mode": "interactive", - "parentClosePolicy": "continue", - "deliveryState": "awaited", - "async": true, - "autoExit": true, - "exitCode": 0, - "elapsed": 50, - "outputTokens": 4191, - "contextTokens": 28435, - "contextWindow": 1000000, - "summary": "All verification passes. Final summary:\n\n---\n\n**Resolved model:** `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` (confirmed from the worker session file — the model is not exposed in `PI_*` env vars).\n\n**Deliverables:**\n1. `/tmp/goals-edxeth-trial-HSahSm/project/hello.txt` — written via `printf 'hello from the worker\\n' > hello.txt` (safe write, no echo/editors/heredocs)\n2. `/tmp/goals-edxeth-trial-HSahSm/project/evidence/hello.log` — captured from real command runs, with a PASS line computed by actual byte comparison\n\n**Observed bytes (independent re-check):**\n```\n$ wc -c hello.txt\n22 hello.txt\n$ od -An -tx1 -v hello.txt (joined single line)\n68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a\n```\nFlank checks: first byte `68`, last byte `0a` (single trailing newline), no CR, no BOM. Hex matches the expected `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a` exactly.\n\n**Result: PASS**\n\n**evidence/hello.log contents** (real captured output):\n- `wc -c hello.txt` → `22 hello.txt`\n- `od -An -tx1 hello.txt` → the dump above (od wraps at 16 bytes/line; the log also shows the expected reference line)\n- byte-count comparison observed=22 expected=22, hex comparison \"observed hex == expected hex\", `RESULT: PASS`\n- Verified the od block in the log matches the live file bytes (diff clean).\n\n**Constraints honored:** no installs, no commits (repo has zero commits — branch 'main' has no commits yet, as before), no network calls, no writes outside the project. Nothing else in the project touched; the only new items are `hello.txt` and `evidence/hello.log` (evidence/ was already gitignored; all existing files were untracked before too).\n\n**Remaining uncertainty:** none material — the supervisor can independently re-run `wc -c` and `od -An -tx1` on hello.txt to confirm the same 22 bytes.", - "sessionFile": "/tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals-edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl" - }, - "content": [ - { - "type": "text", - "text": "Sub-agent \"trial-hello-writer\" completed (50s).\n\nAll verification passes. Final summary:\n\n---\n\n**Resolved model:** `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` (confirmed from the worker session file — the model is not exposed in `PI_*` env vars).\n\n**Deliverables:**\n1. `/tmp/goals-edxeth-trial-HSahSm/project/hello.txt` — written via `printf 'hello from the worker\\n' > hello.txt` (safe write, no echo/editors/heredocs)\n2. `/tmp/goals-edxeth-trial-HSahSm/project/evidence/hello.log` — captured from real command runs, with a PASS line computed by actual byte comparison\n\n**Observed bytes (independent re-check):**\n```\n$ wc -c hello.txt\n22 hello.txt\n$ od -An -tx1 -v hello.txt (joined single line)\n68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a\n```\nFlank checks: first byte `68`, last byte `0a` (single trailing newline), no CR, no BOM. Hex matches the expected `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a` exactly.\n\n**Result: PASS**\n\n**evidence/hello.log contents** (real captured output):\n- `wc -c hello.txt` → `22 hello.txt`\n- `od -An -tx1 hello.txt` → the dump above (od wraps at 16 bytes/line; the log also shows the expected reference line)\n- byte-count comparison observed=22 expected=22, hex comparison \"observed hex == expected hex\", `RESULT: PASS`\n- Verified the od block in the log matches the live file bytes (diff clean).\n\n**Constraints honored:** no installs, no commits (repo has zero commits — branch 'main' has no commits yet, as before), no network calls, no writes outside the project. Nothing else in the project touched; the only new items are `hello.txt` and `evidence/hello.log` (evidence/ was already gitignored; all existing files were untracked before too).\n\n**Remaining uncertainty:** none material — the supervisor can independently re-run `wc -c` and `od -An -tx1` on hello.txt to confirm the same 22 bytes.\n\nSession: /tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals-edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl\nResume: pi --session /tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals-edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl\n\nSub-agent context: 28K/1M tokens (2%) used at finish." - } - ] - }, - { - "file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl", - "tool": "schedule_prompt", - "details": { - "action": "add", - "jobs": [ - { - "id": "pjGJoy_etU", - "name": "goals-01a0898a-3dde-7015-b029-ced69dc7b553", - "schedule": "1h", - "prompt": "Read /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md and keep the plan current (tick finished subtasks, append one ## Log line per run, fill evidence when a discriminator is satisfied); if supervision is paused, exited or all goals are reviewed, remove this job without resuming work; otherwise inspect worker progress and evidence, give a brief assessment, and keep authorized work moving without a duplicate writer.", - "enabled": true, - "type": "interval", - "intervalMs": 3600000, - "createdAt": "2026-09-10T04:23:06.276Z", - "runCount": 0, - "description": "Hourly goals supervision check-in for plan 01a0898a-3dde-7015-b029-ced69dc7b553", - "session": "01a0898a-3dde-7015-b029-ced69dc7b553" - } - ], - "jobId": "pjGJoy_etU", - "jobName": "goals-01a0898a-3dde-7015-b029-ced69dc7b553" - }, - "content": [ - { - "type": "text", - "text": "✓ Created cron job \"goals-01a0898a-3dde-7015-b029-ced69dc7b553\" (pjGJoy_etU)\nType: interval\nSchedule: 1h\nPrompt: Read /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md and keep the plan current (tick finished subtasks, append one ## Log line per run, fill evidence when a discriminator is satisfied); if supervision is paused, exited or all goals are reviewed, remove this job without resuming work; otherwise inspect worker progress and evidence, give a brief assessment, and keep authorized work moving without a duplicate writer." - } - ] - }, - { - "file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl", - "tool": "CompleteGoal", - "details": {}, - "content": [ - { - "type": "text", - "text": "Recorded parent judgment for hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file. This is not independent verification. All goals are reviewed. Remove your hourly job named \"goals-01a0898a-3dde-7015-b029-ced69dc7b553\" with schedule_prompt; leave other jobs untouched." - } - ] - }, - { - "file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl", - "tool": "schedule_prompt", - "details": { - "action": "remove", - "jobs": [], - "jobId": "pjGJoy_etU", - "jobName": "goals-01a0898a-3dde-7015-b029-ced69dc7b553" - }, - "content": [ - { - "type": "text", - "text": "✓ Removed cron job \"goals-01a0898a-3dde-7015-b029-ced69dc7b553\" (pjGJoy_etU)" - } - ] - }, - { - "file": "2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl", - "event": { - "type": "model_change", - "id": "167e897e", - "parentId": "4f4f2164", - "timestamp": "2026-09-10T04:22:08.215Z", - "provider": "fireworks", - "modelId": "accounts/fireworks/models/deepseek-v4-flash-0731" - } - } -] \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/05-parent-byte-check.log b/slop/reviews/package-supervision-captures/05-parent-byte-check.log deleted file mode 100644 index 23bc253..0000000 --- a/slop/reviews/package-supervision-captures/05-parent-byte-check.log +++ /dev/null @@ -1,7 +0,0 @@ -Pi/OpenAI: Node assert.deepStrictEqual(actual, Buffer.from("hello from the worker\n")) passed -bytes=22 -hex=68656c6c6f2066726f6d2074686520776f726b65720a -scheduler={ - "jobs": [], - "version": 1 -} diff --git a/slop/reviews/package-supervision-captures/06-resumed-worker-pane.txt b/slop/reviews/package-supervision-captures/06-resumed-worker-pane.txt deleted file mode 100644 index b4a5e29..0000000 --- a/slop/reviews/package-supervision-captures/06-resumed-worker-pane.txt +++ /dev/null @@ -1,52 +0,0 @@ -{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"goals trial — Fireworks","pane_id":"w8:p69","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1942e7d0586a","terminal_title":"π - project","terminal_title_stripped":"π - project","workspace_id":"w8"} - - State what you remember from your earlier session (the - goal you implemented, the model you were running as, the - checks you ran). - - Confirm the model you are running as now. - ... (8 more lines, ctrl+o to expand) - - - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file - ● Agents · 1 running · 12.7s - └─ ◠ trial-hello-writer [goals-worker] · 9 tool uses ·... - Read-only recovery check (the completed goal s… ·... - Read-only recovery check. Running only the specif... -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 5.2%/1.0M | ↑76k ↓16k 󰆼 94.7% -{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"[goals-worker] Write hello.txt and verify bytes","pane_id":"w8:p6B","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1963e5e54a6c","terminal_title":"π - [goals-worker] Write hello.txt and verify bytes - project","terminal_title_stripped":"π - [goals-worker] Write hello.txt and verify bytes - project","workspace_id":"w8"} - - Thinking... - - All read-only checks pass. Let me confirm the current - model from the session record (read-only; not the plan - file). - - - ⠋ $ grep -oE '"model":"[^"]*"' - /tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals- - edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d - 745a7-040c6138-9c8f7e78-eadd.jsonl | head -1 · 0s - - - ⠦ Working - -▸ Agent trial-hello-writer (goals-worker) -──────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework -──────────────────────────────────────────────────────────── - project in [goals-worker] Write hel… on  main [?] - 3.0%/1.0M | ↑29k ↓5.1k 󰆼 98.3% diff --git a/slop/reviews/package-supervision-captures/07-intercom-supervisor-draft.txt b/slop/reviews/package-supervision-captures/07-intercom-supervisor-draft.txt deleted file mode 100644 index 867419e..0000000 --- a/slop/reviews/package-supervision-captures/07-intercom-supervisor-draft.txt +++ /dev/null @@ -1,51 +0,0 @@ -{"id":"cli:pane:wait-output","result":{"matched_line":" INTERCOM-PROBE-RECEIVED","pane_id":"w8:p69","read":{"format":"text","pane_id":"w8:p69","revision":0,"source":"recent_unwrapped","tab_id":"w8:t1","text":" Use intercom action=send (NOT ask, NOT reply — do not wait for an answer; no acknowledge round-trip expected from you).\n\n 4. Report the delivery result back to the supervisor: whether the send succeeded, the target session id/name you resolved,\n and any error if it failed. One summary, then close this session.\n ... (2 more lines, ctrl+o to expand)\n\n\n╭ From: [goals-worker] Write hello.txt and verify bytes (/tmp/goals-edxeth-trial-HSahSm/project) Ctrl+O expands ──────────╮\n│INTERCOM-PROBE-20260910 │\n│ Ctrl+O to expand │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n\n Thinking...\n\n INTERCOM-PROBE-RECEIVED\n\n The probe message\n\n ⠧ Working\n\n ✓ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the\n worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra\n characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file\n────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n│\n│ UNSENT-DRAFT-KEEP-20260910\n│\n│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks high\n────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n project on  main [?] goals: supervising | 1/1 reviewed | LSP | 5.5%/1.0M (auto) | ↑175k ↓18k 󰆼 10.1% R771k | $0.056","truncated":true,"workspace_id":"w8"},"revision":0,"type":"output_matched"}} -│ not create a scheduler job. I will place an unsent draft in this supervisor editor while it runs; preserve that draft. -│ -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - - Thinking... - - Bounded Intercom test — resuming the same worker session for a read-only intercom probe. - - - ▸ Resume trial-hello-writer (goals-worker) - Bounded intercom delivery test — no implementation, read-only apart from the single intercom send: - - 1. Use the intercom tool with action=list to list active pi sessions. - - 2. Identify THIS supervisor session in the list by its exact session ID: 01a0898a-3dde-7015-b029-ced69dc7b553. Note that - intercom may display only a short prefix in parentheses; if so, match by the unique leading prefix (01a0898a) and confirm - it is the only session with that prefix / that it resolves to the supervisor. Do not pick any other session — no other - recipients. - - 3. Send the supervisor session EXACTLY this message, nothing added or removed: INTERCOM-PROBE-20260910 - Use intercom action=send (NOT ask, NOT reply — do not wait for an answer; no acknowledge round-trip expected from you). - - 4. Report the delivery result back to the supervisor: whether the send succeeded, the target session id/name you resolved, - and any error if it failed. One summary, then close this session. - ... (2 more lines, ctrl+o to expand) - - -╭ From: [goals-worker] Write hello.txt and verify bytes (/tmp/goals-edxeth-trial-HSahSm/project) Ctrl+O expands ──────────╮ -│INTERCOM-PROBE-20260910 │ -│ Ctrl+O to expand │ -╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - - Thinking... - - INTERCOM-PROBE-RECEIVED - - The probe message - - ⠧ Working - - ✓ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the - worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra - characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── -│ -│ UNSENT-DRAFT-KEEP-20260910 -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks high -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - project on  main [?] goals: supervising | 1/1 reviewed | LSP | 5.5%/1.0M (auto) | ↑175k ↓18k 󰆼 10.1% R771k | $0.056 \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/08-intercom-return-worker-draft.txt b/slop/reviews/package-supervision-captures/08-intercom-return-worker-draft.txt deleted file mode 100644 index 4de6511..0000000 --- a/slop/reviews/package-supervision-captures/08-intercom-return-worker-draft.txt +++ /dev/null @@ -1,75 +0,0 @@ -{"id":"cli:pane:wait-output","result":{"matched_line":" ✓ Reply sent to [goals-worker] Write hello.txt and verify","pane_id":"w8:p69","read":{"format":"text","pane_id":"w8:p69","revision":0,"source":"recent_unwrapped","tab_id":"w8:t1","text":"│INTERCOM-RETURN-PROBE │\n│ To reply: intercom({ action: \"reply\", message: \"...\" }) · │\n╰───────────────────────────────────────────────────────────╯\n\n Thinking...\n\n The ask arrived. Replying with the exact acknowledgment.\n\n\n intercom reply\n INTERCOM-RETURN-ACK\n ✓ Reply sent to [goals-worker] Write hello.txt and verify\n bytes (38be39dc)\n\n\n ⠸ Working\n\n ✓ hello.txt exists in the project root, written by the\n worker, containing exactly the UTF-8 byte sequence `hello\n from the worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72\n 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no\n trailing extra characters or blank lines; and\n evidence/hello.log exists in `evidence/` recording the\n byte-level verification of that file\n ● Agents · 1 running · 51.0s\n └─ ◞ trial-hello-writer [goals-worker] · 14 tool uses ...\n Your previous turn identified the supervisor b… ·...\n Sending the intercom ask now (target already conf...\n─────────────────────────────────────────────────────────────\n│\n│\n│\n│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks\n─────────────────────────────────────────────────────────────\n project on  main [?] 6.5%/1.0M | ↑196k ↓26k 󰆼 89.6%","truncated":true,"workspace_id":"w8"},"revision":0,"type":"output_matched"}} - - 2. Send intercom action=ask (NOT send) to that supervisor - session with EXACTLY this message: - INTERCOM-RETURN-PROBE - Then WAIT for the supervisor's reply. Do not finish or - close your session until you have received the reply. - - 3. When the reply arrives, report it VERBATIM (expected: - INTERCOM-RETURN-ACK) together with any round-trip/delivery - metadata from the tool. Only then close. - - ... (1 more lines, ctrl+o to expand) - - -╭ From: [goals-worker] Write hello.txt and verify bytes (/tm╮ -│INTERCOM-RETURN-PROBE │ -│ To reply: intercom({ action: "reply", message: "..." }) · │ -╰───────────────────────────────────────────────────────────╯ - - Thinking... - - The ask arrived. Replying with the exact acknowledgment. - - - intercom reply - INTERCOM-RETURN-ACK - ✓ Reply sent to [goals-worker] Write hello.txt and verify - bytes (38be39dc) - - - ⠸ Working - - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file - ● Agents · 1 running · 51.0s - └─ ◞ trial-hello-writer [goals-worker] · 14 tool uses ... - Your previous turn identified the supervisor b… ·... - Sending the intercom ask now (target already conf... -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 6.5%/1.0M | ↑196k ↓26k 󰆼 89.6% - Thinking... - - Sending the intercom ask now (target already confirmed: - 01a0898a is the unique prefix of the only other session, - the supervisor). - - - intercom ask → 01a0898a - INTERCOM-RETURN-PROBE - ✓ Reply from 01a0898a: - INTERCOM-RETURN-ACK - - - ⠇ Working - -▸ Agent trial-hello-writer (goals-worker) -──────────────────────────────────────────────────────────── -│ -│ UNSENT-WORKER-DRAFT-KEEP-20260910 -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework -──────────────────────────────────────────────────────────── - project in [goals-worker] Write hel… on  main [?] - 3.4%/1.0M | ↑77k ↓7.2k 󰆼 98.7% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/09-open-pane-draft-safe-roundtrip.txt b/slop/reviews/package-supervision-captures/09-open-pane-draft-safe-roundtrip.txt deleted file mode 100644 index 9e44eaa..0000000 --- a/slop/reviews/package-supervision-captures/09-open-pane-draft-safe-roundtrip.txt +++ /dev/null @@ -1,59 +0,0 @@ -{"type":"custom_message","customType":"intercom_message","content":"**From [goals-worker] Read-only draft-safe intercom probe** (/tmp/goals-edxeth-trial-HSahSm/project)\n\n_id bfee08a1-c796-4e98-b97b-f8c1aa21aa6e · seq 2 · sent 2026-09-10T04:37:58.372Z · broker delivered 2026-09-10T04:37:58.373Z · receiver received 2026-09-10T04:37:58.373Z · injected 2026-09-10T04:37:58.373Z_\n\nOPEN-PANE-ACK","display":true,"details":{"from":{"id":"003c8e70-9999-4920-8a5c-064172010188","endpointEpoch":"a9d0684f-b821-4982-980d-9cd0798ddb70","name":"[goals-worker] Read-only draft-safe intercom probe","runtimeFallbackAlias":false,"cwd":"/tmp/goals-edxeth-trial-HSahSm/project","model":"accounts/fireworks/models/deepseek-v4-flash-0731","pid":708378,"startedAt":1789014977644,"lastActivity":1789015078370,"status":"tool:intercom","trustedLocal":true,"contextPct":2,"contextTokens":23575,"contextWindow":1000000},"message":{"id":"bfee08a1-c796-4e98-b97b-f8c1aa21aa6e","timestamp":1789015078372,"senderSequence":2,"content":{"text":"OPEN-PANE-ACK"},"brokerReceivedAt":1789015078373,"brokerDeliveredAt":1789015078373,"receiverReceivedAt":1789015078373,"injectedAt":1789015078373},"bodyText":"OPEN-PANE-ACK"},"id":"0eaf9dc5","parentId":"c645b2a8","timestamp":"2026-09-10T04:37:58.375Z"} -{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"goals trial — Fireworks","pane_id":"w8:p69","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1942e7d0586a","terminal_title":"π - project","terminal_title_stripped":"π - project","workspace_id":"w8"} - processed only the probe exchange). Neither pane closed; - the unsent draft in the worker editor remains untouched - (worker was instructed not to touch or submit it, and it - returned only the ack). - - No file/plan changes, no scheduler job, no new sessions - created, no child-exit handling used (the worker stays open - by design — this is the OPEN-PANE test). Both supervisor - and worker panes remain open. - - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file - ● Agents · 1 running · 119.4s - └─ ◡ draft-safe-trial [goals-worker] · 4 tool uses · 2... - Read-only draft-safe intercom probe · fireworks/a... - Sent. Brief report: -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 7.1%/1.0M | ↑209k ↓30k 󰆼 93.3% -{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"[goals-worker] Read-only draft-safe intercom probe","pane_id":"w8:p6F","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b198218cd1570","terminal_title":"π - [goals-worker] Read-only draft-safe intercom probe - project","terminal_title_stripped":"π - [goals-worker] Read-only draft-safe intercom probe - project","workspace_id":"w8"} - - - intercom send → 01a0898a - OPEN-PANE-ACK - ✓ Message sent to 01a0898a (bfee08a1) - - - Sent. Brief report: - - Intercom send: - - Targeted: 01a0898a → resolves to supervisor - subagent-chat-01a0898a-3dde-7015 (unchanged from before) - - Message: OPEN-PANE-ACK via action=send - - Result: Message sent to 01a0898a — success ✓ - - No file changes, no editor text touched. Staying open in - this pane for further operator/supervisor interaction — no - exit, no shutdown. - -▸ Agent draft-safe-trial (goals-worker) -──────────────────────────────────────────────────────────── -│ -│ UNSENT-OPEN-WORKER-DRAFT-KEEP -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework -──────────────────────────────────────────────────────────── - project in [goals-worker] Read-only… on  main [?] - 2.4%/1.0M | ↑43k ↓1.4k 󰆼 18.4% diff --git a/slop/reviews/package-supervision-captures/10-scheduler-jobs-ui.txt b/slop/reviews/package-supervision-captures/10-scheduler-jobs-ui.txt deleted file mode 100644 index a2a047c..0000000 --- a/slop/reviews/package-supervision-captures/10-scheduler-jobs-ui.txt +++ /dev/null @@ -1,35 +0,0 @@ - - Initial prompt: Say TIMER-PROMPT-OLD, then remove only - the goals-ui-probe job. Do not change files, plans, -───────────────────────────────────────────────────────────── - Jobs — ↑↓ select a add t toggle s scope x remove -───────────────────────────────────────────────────────────── - ▶ ✓ goals-ui-probe every 1h Say TIMER-PROMPT-O -───────────────────────────────────────────────────────────── - Selected: goals-ui-probe (P3Znb2bVxP) - Type: interval Runs: 0 - Prompt: Say TIMER-PROMPT-OLD, then remove only the goals-ui- -───────────────────────────────────────────────────────────── - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file - ● Agents · 1 running · 407.6s - └─ ◡ draft-safe-trial [goals-worker] · 4 tool uses · 2... - Read-only draft-safe intercom probe · fireworks/a... - Sent. Brief report: -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── -───────────────────────────────────────────────────────────── - Scheduled Prompts (1 jobs) - - ✓ goals-ui-probe 1h Say TIMER-PROMPT-OLD, - ... - never 0 -───────────────────────────────────────────────────────────── - project on  main [?] 7.2%/1.0M | ↑210k ↓30k 󰆼 99.6% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/11-before-live-reload-worker-draft.txt b/slop/reviews/package-supervision-captures/11-before-live-reload-worker-draft.txt deleted file mode 100644 index c58146c..0000000 --- a/slop/reviews/package-supervision-captures/11-before-live-reload-worker-draft.txt +++ /dev/null @@ -1,12 +0,0 @@ - this pane for further operator/supervisor interaction — no - exit, no shutdown. - -▸ Agent draft-safe-trial (goals-worker) -──────────────────────────────────────────────────────────── -│ -│ UNSENT-OPEN-WORKER-DRAFT-KEEP -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework -──────────────────────────────────────────────────────────── - project in [goals-worker] Read-only… on  main [?] - 2.4%/1.0M | ↑43k ↓1.4k 󰆼 18.4% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/12-parent-reload.txt b/slop/reviews/package-supervision-captures/12-parent-reload.txt deleted file mode 100644 index 4763c21..0000000 --- a/slop/reviews/package-supervision-captures/12-parent-reload.txt +++ /dev/null @@ -1,24 +0,0 @@ - Listed first — no duplicate existed (No cron jobs - configured), so exactly one job was added. No other jobs - touched, no files/plans/workers changed, and the open - worker (003c8e70, with its unsent draft) was left - completely untouched. Ready for you to inspect through - /schedule-prompt. - - Reloaded keybindings, extensions, skills, prompts, themes, - and context files - - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 7.2%/1.0M | ↑210k ↓30k 󰆼 99.6% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/13-worker-draft-after-parent-reload.txt b/slop/reviews/package-supervision-captures/13-worker-draft-after-parent-reload.txt deleted file mode 100644 index 6350c92..0000000 --- a/slop/reviews/package-supervision-captures/13-worker-draft-after-parent-reload.txt +++ /dev/null @@ -1,14 +0,0 @@ - - No file changes, no editor text touched. Staying open in - this pane for further operator/supervisor interaction — no - exit, no shutdown. - -▸ Agent draft-safe-trial (goals-worker) -──────────────────────────────────────────────────────────── -│ -│ UNSENT-OPEN-WORKER-DRAFT-KEEP -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework -──────────────────────────────────────────────────────────── - project in [goals-worker] Read-only… on  main [?] - 2.4%/1.0M | ↑43k ↓1.4k 󰆼 18.4% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/14-live-reload-reconnection.txt b/slop/reviews/package-supervision-captures/14-live-reload-reconnection.txt deleted file mode 100644 index b7d5cb5..0000000 --- a/slop/reviews/package-supervision-captures/14-live-reload-reconnection.txt +++ /dev/null @@ -1,59 +0,0 @@ -{"type":"custom_message","customType":"intercom_message","content":"**From [goals-worker] Read-only draft-safe intercom probe** (/tmp/goals-edxeth-trial-HSahSm/project)\n\n_id b5f9ebfb-6e4c-4049-8d98-3385851fe601 · seq 3 · sent 2026-09-10T04:44:13.456Z · broker delivered 2026-09-10T04:44:13.456Z · receiver received 2026-09-10T04:44:13.467Z · injected 2026-09-10T04:44:13.467Z_\n\nRELOAD-LIVE-ACK","display":true,"details":{"from":{"id":"003c8e70-9999-4920-8a5c-064172010188","endpointEpoch":"a9d0684f-b821-4982-980d-9cd0798ddb70","name":"[goals-worker] Read-only draft-safe intercom probe","runtimeFallbackAlias":false,"cwd":"/tmp/goals-edxeth-trial-HSahSm/project","model":"accounts/fireworks/models/deepseek-v4-flash-0731","pid":708378,"startedAt":1789014977644,"lastActivity":1789015453456,"status":"tool:intercom","trustedLocal":true,"contextPct":2,"contextTokens":24039,"contextWindow":1000000},"message":{"id":"b5f9ebfb-6e4c-4049-8d98-3385851fe601","timestamp":1789015453456,"senderSequence":3,"content":{"text":"RELOAD-LIVE-ACK"},"brokerReceivedAt":1789015453456,"brokerDeliveredAt":1789015453456,"receiverReceivedAt":1789015453467,"injectedAt":1789015453467},"bodyText":"RELOAD-LIVE-ACK"},"id":"0c7f069e","parentId":"a07ec03f","timestamp":"2026-09-10T04:44:13.470Z"} -{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"goals trial — Fireworks","pane_id":"w8:p69","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1942e7d0586a","terminal_title":"π - project","terminal_title_stripped":"π - project","workspace_id":"w8"} - Reply via Intercom to supervisor - 01a0898a-3dde-7015-b029-ced69dc7b553 with exactly - RELOAD-LIVE-… - ✓ Message sent to 003c8e70 (7784b72a) - - - Send receipt: message dispatched to the live worker - 003c8e70. Per your instruction, I will not treat the send - receipt as the answer — I'm waiting for the worker's actual - RELOAD-LIVE-ACK reply and will acknowledge that real - message when it arrives. No second worker launched, no - resume used, worker was already connected (only - re-targeted). - - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 7.6%/1.0M | ↑282k ↓32k 󰆼 98.5% -{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"[goals-worker] Read-only draft-safe intercom probe","pane_id":"w8:p6F","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b198218cd1570","terminal_title":"π - [goals-worker] Read-only draft-safe intercom probe - project","terminal_title_stripped":"π - [goals-worker] Read-only draft-safe intercom probe - project","workspace_id":"w8"} - -╭ From: subagent-chat-01a0898a-3dde-7015 (/tmp/goals-edxeth╮ -│Reply via Intercom to supervisor 01a0898a-3dde-7015-b029-c│ -│ Ctrl+O to expand │ -╰──────────────────────────────────────────────────────────╯ - - Thinking... - - Sending RELOAD-LIVE-ACK via intercom to the supervisor, no - file changes, staying open. - - - intercom send → 01a0898a - RELOAD-LIVE-ACK - ✓ Message sent to 01a0898a (b5f9ebfb) - - - ⠹ Working - -▸ Agent draft-safe-trial (goals-worker) -──────────────────────────────────────────────────────────── -│ -│ UNSENT-OPEN-WORKER-DRAFT-KEEP -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework -──────────────────────────────────────────────────────────── - project in [goals-worker] Read-only… on  main [?] - 2.4%/1.0M | ↑43k ↓1.5k 󰆼 97.8% diff --git a/slop/reviews/package-supervision-captures/15-edited-timer-fired-once.json b/slop/reviews/package-supervision-captures/15-edited-timer-fired-once.json deleted file mode 100644 index c9bcfc2..0000000 --- a/slop/reviews/package-supervision-captures/15-edited-timer-fired-once.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "fire": [ - { - "type": "custom_message", - "customType": "scheduled_prompt", - "content": [], - "display": true, - "details": { - "jobId": "np-gHnDsp_", - "jobName": "goals-ui-probe", - "prompt": "Say TIMER-PROMPT-NEW, then list jobs and remove only goals-ui-probe owned by this session; no files/plans/workers/other jobs changed." - }, - "id": "81809511", - "parentId": "7112d7c6", - "timestamp": "2026-09-10T04:47:38.495Z" - } - ], - "removed": { - "type": "message", - "id": "19e91aaf", - "parentId": "7ae72770", - "timestamp": "2026-09-10T04:47:44.939Z", - "message": { - "role": "toolResult", - "toolCallId": "call_e5726f051f5643efa7be5639", - "toolName": "schedule_prompt", - "content": [ - { - "type": "text", - "text": "✓ Removed cron job \"goals-ui-probe\" (np-gHnDsp_)" - } - ], - "details": { - "action": "remove", - "jobs": [], - "jobId": "np-gHnDsp_", - "jobName": "goals-ui-probe" - }, - "isError": false, - "timestamp": 1789015664939 - } - }, - "store": { - "jobs": [], - "version": 1 - } -} diff --git a/slop/reviews/package-supervision-captures/16-timer-visible-response.txt b/slop/reviews/package-supervision-captures/16-timer-visible-response.txt deleted file mode 100644 index 7a02b56..0000000 --- a/slop/reviews/package-supervision-captures/16-timer-visible-response.txt +++ /dev/null @@ -1,30 +0,0 @@ - (fired at 04:47:38.495Z) - 3. ✅ Removed only that job — ✓ Removed cron job - "goals-ui-probe" (np-gHnDsp_) - - No files/plans/workers/other jobs changed: - - Scheduler now empty (only the probe job was present, now - removed) - - Open worker (003c8e70) and its editor draft untouched - - Plan and all deliverables untouched, no new workers - launched - - Timer probe result: PASS — the 10s interval fired - naturally, invoked this session's prompt, and the prompt's - self-removal directive executed correctly without - disturbing anything else. - - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 8.2%/1.0M | ↑287k ↓36k 󰆼 99.8% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/17-delayed-reload-worker-exit-crash.txt b/slop/reviews/package-supervision-captures/17-delayed-reload-worker-exit-crash.txt deleted file mode 100644 index c22da44..0000000 --- a/slop/reviews/package-supervision-captures/17-delayed-reload-worker-exit-crash.txt +++ /dev/null @@ -1,29 +0,0 @@ - -code@coi-91cea783-1 /tmp/goals-edxeth-trial-HSahSm/project ma -❯ /tmp/goals-edxeth-trial-HSahSm/start.zsh - - - ✓ hello.txt exists in the project root, written by the - worker, containing exactly the UTF-8 byte sequence `hello - from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 - 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no - trailing extra characters or blank lines; and - evidence/hello.log exists in `evidence/` recording the - byte-level verification of that file -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 8.2%/1.0M | ↑287k ↓36k 󰆼 99.8% -pi exiting due to uncaughtException: -Error: This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload(). - at ExtensionRunner.assertActive (file:///home/code/.local/lib/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/chunk-JVUZSMYM.js:1066:25652) - at get hasUI (file:///home/code/.local/lib/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/chunk-JVUZSMYM.js:1066:27753) - at SubagentWidgetManager.update (/tmp/pi-goals-stock-edxeth-LQFtq2/src/runtime/widget.ts:139:24) - at updateWidget (/tmp/pi-goals-stock-edxeth-LQFtq2/src/runtime/wiring.ts:72:24) - at /tmp/pi-goals-stock-edxeth-LQFtq2/src/runtime/running-registry.ts:255:5 - -code@coi-91cea783-1 /tmp/goals-edxeth-trial-HSahSm/project main* 30m 4s -❯ \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/18-solo-takeover-menu.txt b/slop/reviews/package-supervision-captures/18-solo-takeover-menu.txt deleted file mode 100644 index a5228cf..0000000 --- a/slop/reviews/package-supervision-captures/18-solo-takeover-menu.txt +++ /dev/null @@ -1,18 +0,0 @@ - Timer probe result: PASS — the 10s interval fired naturally, invoked this session's prompt, and the prompt's self-removal - directive executed correctly without disturbing anything else. - - ✓ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the - worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra - characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - - Confirm all other writers for the current and target plans are stopped (inspect /subagents and their panes). A missing - handle is not proof. Take over in this session? - - → Worker confirmed stopped - Cancel - - ↑↓ navigate enter select escape/ctrl+c cancel - -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - project on  main [?] goals: supervising | 1/1 reviewed | LSP | 8.2%/1.0M (auto) | ↑287k ↓36k 󰆼 99.8% R3.5M | $0.111 \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/19-solo-recovery-completed.log b/slop/reviews/package-supervision-captures/19-solo-recovery-completed.log deleted file mode 100644 index 0924b8f..0000000 --- a/slop/reviews/package-supervision-captures/19-solo-recovery-completed.log +++ /dev/null @@ -1,40 +0,0 @@ -Parent Node byte assertions PASS: solo-ok newline; original hello unchanged -# Solo recovery test - -## User-visible result -Create solo.txt containing exactly `solo-ok\n` (8 UTF-8 bytes) and save actual byte-verification output in evidence/solo.log. - -## Preferences -- Explicit solo mode: main session implements and edits; completion is self-verification, not an independent review. -- No other worker, installs, commits, network requests or unrelated file changes. Preserve hello.txt and evidence/hello.log. - -## Goals -- [x] goal: Produce the exact solo.txt bytes and saved verification log - - subtle failure mode: main session reports success without writing the file, or labels self-verification independent review. - - discriminator: actual file is the 8 bytes `solo-ok\n`; saved log records a successful byte comparison; completion log identifies solo self-verification. - - evidence (satisfied): `solo.txt` = 8 bytes, `od -An -tx1 -v` reads exactly `73 6f 6c 6f 2d 6f 6b 0a` (= `solo-ok\n`); `evidence/solo.log` (441 bytes, nonempty) records real `wc -c` (8) and `od` output plus programmatic byte-count and hex comparisons against `736f6c6f2d6f6b0a` and `RESULT: PASS`; verified by the main session itself (solo self-verification, not independent review) - -## Log - -- Solo self-verification: "Produce the exact solo.txt bytes and saved verification log"; "Solo-mode self-verification (main session, not independent review). Inspected actual artifacts on disk: solo.txt exists at 8 bytes; `od -An -tx1 -v solo.txt` outputs exactly `73 6f 6c 6f 2d 6f 6b 0a`, i.e. UTF-8 `solo-ok\\n`. evidence/solo.log exists (441 bytes, nonempty) and records the real `wc -c` result (8), the real `od` dump, the expected-hex reference, programmatic byte-count and hex comparisons (observed=8 expected=8; actual hex 736f6c6f2d6f6b0a == expected hex), and `RESULT: PASS`. Log content matches the on-disk file byte-for-byte. Constraints held: no installs, no commits, no network calls; hello.txt and evidence/hello.log preserved untouched; no unrelated changes. Scheduler: list shows no jobs, so no job named goals-01a0898a-3dde-7015-b029-ced69dc7b553 exists to remove; nothing added/enabled/recreated. Goal satisfied."; evidence ["/tmp/goals-edxeth-trial-HSahSm/project/solo.txt","/tmp/goals-edxeth-trial-HSahSm/project/evidence/solo.log"] - -Prepared by Pi/OpenAI as an isolated functional-test plan. -- Ran goal (main session, solo mode): wrote `solo.txt` via `printf 'solo-ok\n'`, then generated `evidence/solo.log` from real command output with a programmatic PASS/FAIL byte comparison; re-checked `wc -c` = 8 and hex `73 6f 6c 6f 2d 6f 6b 0a` directly. This is solo self-verification by the main session, not independent review. Constraints: no installs/commits/network; `hello.txt` and `evidence/hello.log` preserved. Goal complete. - -Byte-verification log for solo.txt (solo recovery test) -Time: 2026-09-10T04:52:22Z ---- wc -c solo.txt --- -8 solo.txt ---- od -An -tx1 -v solo.txt --- - 73 6f 6c 6f 2d 6f 6b 0a ---- expected hex (8 bytes: solo-ok\n) --- -73 6f 6c 6f 2d 6f 6b 0a ---- byte count comparison --- -observed=8 expected=8 ---- hex comparison (actual vs expected) --- -actual=736f6c6f2d6f6b0a -expected=736f6c6f2d6f6b0a -actual hex == expected hex ---- result --- -RESULT: PASS - diff --git a/slop/reviews/package-supervision-captures/20-final-ready-menu.txt b/slop/reviews/package-supervision-captures/20-final-ready-menu.txt deleted file mode 100644 index a7ade85..0000000 --- a/slop/reviews/package-supervision-captures/20-final-ready-menu.txt +++ /dev/null @@ -1,25 +0,0 @@ - saved to evidence/verified.log, with the supervisor independently repeating the byte checks and reading the log before │ - sign-off. Worker model recorded as fireworks/accounts/fireworks/models/deepseek-v4-flash-0731, no commits, no │ - dependencies, no unrelated changes. █ - █ - Use /goals ready to approve, /goals review to refine, or /goals exit to keep this draft for later. █ - - ○ verified.txt holds exactly the 9 bytes "verified\n", and evidence/verified.log records a real byte check of that file - ◦ write verified.txt with exactly "verified\n" using a byte-exact tool such as printf, never an editor with default - line endings - ◦ run the verify command above so its real output is written to evidence/verified.log and the command exits 0 only on a - pass - ◦ record the outcome and file paths in the plan's evidence section below -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - - Review /tmp/goals-edxeth-trial-X3Y79U/project/.pi/plan/01a089c9-4ce2-75d2-866a-53fdf4cd5007-main.md - - → Ready - Discuss - Edit - Cancel - - ↑↓ navigate enter select escape/ctrl+c cancel - -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - project on  main [?] goals: planning | 0/1 reviewed | LSP | 4.3%/1.0M (auto) | ↑42k ↓13k 󰆼 97.2% R418k | $0.021 \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/21-final-supervisor.txt b/slop/reviews/package-supervision-captures/21-final-supervisor.txt deleted file mode 100644 index 18223ce..0000000 --- a/slop/reviews/package-supervision-captures/21-final-supervisor.txt +++ /dev/null @@ -1,55 +0,0 @@ - preserved │ - - User voice and user-visible result untouched │ - │ - Schedule: job wS79fJFPbB removed; .pi/schedule-prompts.json│ - shows 0 jobs. No other jobs existed or were touched. │ - │ - Scope: zero commits (git log still empty), no packages │ - installed, no unrelated changes. │ - │ - The trial is complete: the approved plan ran to completion │ - through the goals-worker (after the parent's infra repair, │ - resumed via stock subagent_resume), the supervisor │ - independently inspected both artifacts before sign-off, and│ - all closing bookkeeping (job removal, plan finalization) is│ - done. │ - │ -────────────────────────────────────────────────────────────│ -│ │ -│ [pi-goals] │ -│ Plan changed: │ -│ /tmp/goals-edxeth-trial-X3Y79U/project/.pi/plan/01a089c9-4│ -│ e2-75d2-866a-53fdf4cd5007-main.md. Read the current workin│ -│ set and inspect changed requirements, completion claims an│ -│ evidence. Manual checkbox edits are claims, not proof. Do │ -│ not weaken the agreed goal or start a duplicate writer. │ -│ │ -────────────────────────────────────────────────────────────│ - │ - Thinking... │ - │ - Plan-change event. Let me read the current plan and check │ - what changed against what I last verified. │ - │ - │ - read │ - /tmp/goals-edxeth-trial-X3Y79U/project/.pi/plan/01a089c9-4c│ - e2-75d2-866a-53fdf4cd5007-main.md █ - █ - - ⠼ Working - - ✓ verified.txt holds exactly the 9 bytes "verified\n", and - evidence/verified.log records a real byte check of that - file - ● Agents · 1 running · 142.8s - └─ ◟ verified-bytes-worker [goals-worker] · 12 tool us... - Resume the delegated goal. You are the goals-w… ·... - Completion report sent to the supervisor. All tas... -───────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -───────────────────────────────────────────────────────────── - project on  main [?] 8.9%/1.0M | ↑217k ↓35k 󰆼 99.6% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/22-final-open-worker.txt b/slop/reviews/package-supervision-captures/22-final-open-worker.txt deleted file mode 100644 index a852252..0000000 --- a/slop/reviews/package-supervision-captures/22-final-open-worker.txt +++ /dev/null @@ -1,40 +0,0 @@ - - Task 2 — verify command run, real output saved: - - Ran the plan's bash snippet (wc + od + hex comparison + - PASS/FAIL via tee) - - Exit code 0 (pass only) - - evidence/verified.log (112 bytes) contains the raw wc/od - output plus PASS: 9 bytes: verified + LF - - Task 3 — plan evidence filled: - - evidence: line under the goal now records the PASS, byte - count, hex, log path, and constraint compliance - - Constraints maintained: no installs, no git commits - (branch has zero commits), no unrelated changes — only - verified.txt, evidence/verified.log, and the plan evidence - line. - - Completion report sent via Intercom to supervisor - 01a089c9. My pane remains open for the supervisor's - independent inspection before sign-off. - -▸ Agent verified-bytes-worker (goals-worker) - ○ verified.txt holds exactly the 9 bytes "verified\n", and - evidence/verified.log records a real byte check of that - file - ◦ write verified.txt with exactly "verified\n" using a - byte-exact tool such as printf, never an editor with - default line endings - ◦ run the verify command above so its real output is - written to evidence/verified.log and the command exits 0 - only on a pass - ◦ record the outcome and file paths in the plan's - evidence section below -──────────────────────────────────────────────────────────── -│ -│ -│ -│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework -──────────────────────────────────────────────────────────── - project on  main [?] 2.8%/1.0M | ↑28k ↓3.7k 󰆼 97.7% \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/23-final-parent-verification.log b/slop/reviews/package-supervision-captures/23-final-parent-verification.log deleted file mode 100644 index 788b822..0000000 --- a/slop/reviews/package-supervision-captures/23-final-parent-verification.log +++ /dev/null @@ -1,6 +0,0 @@ -Parent independent byte assertion PASS: verified + LF, 9 bytes. Saved verification nonempty with PASS. Scheduler jobs empty. -== byte verification of verified.txt == -9 verified.txt - 76 65 72 69 66 69 65 64 0a -PASS: 9 bytes: verified + LF - diff --git a/slop/reviews/package-supervision-captures/24-installed-goals-status.txt b/slop/reviews/package-supervision-captures/24-installed-goals-status.txt deleted file mode 100644 index 2c1b505..0000000 --- a/slop/reviews/package-supervision-captures/24-installed-goals-status.txt +++ /dev/null @@ -1,30 +0,0 @@ - pi-better-compaction loaded • debug artifacts → - /home/code/.pi/agent/artifacts/pi-better-compaction/sessio - ns/01a089d7-0fdd-77ed-b5cb-d3128229bbbb/lifecycle/2026-09- - 10T05-42-49-930Z-lifecycle.json - -──────────────────────────────────────────────────────────── - Package Updates Available - Package updates are available. Run pi update --extensions - Packages: - - github.com/wassname/pi-better-compaction -──────────────────────────────────────────────────────────── - - Mode: chat - Plan: none - Preferred worker model (plan): not stated; use /goals - model <model> - Recorded worker session: not recorded - Hourly check-in: schedule_prompt job - "goals-01a089d7-0fdd-77ed-b5cb-d3128229bbbb" (list/remove - via schedule_prompt; plan-change reviews are the - plan-watcher event hook) - Liveness is owned by edxeth; inspect /subagents. - -──────────────────────────────────────────────────────────── - - - - accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks -──────────────────────────────────────────────────────────── - project on  main [?] [░░░░░░░░░░] 0.0%/1.0M | ↑0 ↓0 \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/25-installed-subagents.txt b/slop/reviews/package-supervision-captures/25-installed-subagents.txt deleted file mode 100644 index 3f7cfb1..0000000 --- a/slop/reviews/package-supervision-captures/25-installed-subagents.txt +++ /dev/null @@ -1,26 +0,0 @@ - - github.com/wassname/pi-better-compaction -──────────────────────────────────────────────────────────── - - Mode: chat - Plan: none - Preferred worker model (plan): not stated; use /goals - model <model> - Recorded worker session: not recorded - Hourly check-in: schedule_prompt job - "goals-01a089d7-0fdd-77ed-b5cb-d3128229bbbb" (list/remove - via schedule_prompt; plan-change reviews are the - plan-watcher event hook) - Liveness is owned by edxeth; inspect /subagents. - -──────────────────────────────────────────────────────────── - Subagents - -← Running Completed Agents Orchestrator: Off → - -▸ ◆ goals-worker - Implement the approved goal, save actual - verification evide… - - ↑↓ navigate · ←→ tabs · Enter details · Esc close -──────────────────────────────────────────────────────────── - project on  main [?] [░░░░░░░░░░] 0.0%/1.0M | ↑0 ↓0 \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/26-goals-action-menu.txt b/slop/reviews/package-supervision-captures/26-goals-action-menu.txt deleted file mode 100644 index 35a9cf4..0000000 --- a/slop/reviews/package-supervision-captures/26-goals-action-menu.txt +++ /dev/null @@ -1,25 +0,0 @@ - Package updates are available. Run pi update --extensions - Packages: - - github.com/wassname/pi-better-compaction -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - - Goal plan actions - - → status — Show current plan - new — New plan - attach — Open an existing plan - review — Review current plan - ready — Approve draft - stop — Pause work - resume — Continue paused work - solo — Work in this session - model — Set worker model - exit — Leave goal mode - help — Show commands - - ↑↓ navigate enter select escape/ctrl+c cancel - -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - goals-menu-final-check on  main accounts/fireworks/models/deepseek-v4-flash-0731 · [░░░░░░░░░░] 0.0%/1.0M (auto) · $0.000 \ No newline at end of file diff --git a/slop/reviews/package-supervision-captures/27-automatic-plan-proposal.txt b/slop/reviews/package-supervision-captures/27-automatic-plan-proposal.txt deleted file mode 100644 index 202578c..0000000 --- a/slop/reviews/package-supervision-captures/27-automatic-plan-proposal.txt +++ /dev/null @@ -1,25 +0,0 @@ - - Appendix (context, not approved) - - - Hex mapping for the discriminator: "hello\n" = 68656c6c6f0a. wc -c gives 6 for this exact file; any extra byte (trailing - space, second newline, CRLF) changes the count or the hex and fails the verify command. - - - ○ create hello.txt with exactly the 6 bytes "hello\n" and save byte-check.log - ◦ write hello.txt with `printf 'hello\n' > hello.txt` (printf, not echo: echo adds an extra newline) - ◦ run byte and content checks, save their output to byte-check.log - ◦ run the verify command, save its output as evidence -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - - Review /tmp/goals-menu-final-check/.pi/plan/01a08acb-b748-77f7-8df7-f992ad01cac0-main.md - - → Ready - Discuss - Edit - Cancel - - ↑↓ navigate enter select escape/ctrl+c cancel - -──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - goals-menu-final-check on  main [?] - goals: planning | 0/1 reviewed · accounts/fireworks/models/deepseek-v4-flash-0731 · [░░░░░░░░░░] 4.0%/1.0M (auto) · $0.012 \ No newline at end of file diff --git a/slop/reviews/review-fixes-native/supervisor-events.jsonl b/slop/reviews/review-fixes-native/supervisor-events.jsonl deleted file mode 100644 index cc52f97..0000000 --- a/slop/reviews/review-fixes-native/supervisor-events.jsonl +++ /dev/null @@ -1,21 +0,0 @@ -{"type":"extension_ui_request","id":"ec31db54-0eee-4cc4-bbdc-550e3297292e","method":"notify","message":"supervisor model: offline/test","notifyType":"info"} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-visible-supervisor-v2","data":{"version":2,"workerSessionId":"01a080aa-3018-770c-8926-822a3e1a2aa9","planPath":"/tmp/goals-native-pair-Mk0U2D/plan.md"},"id":"280d42da","parentId":"6c8e3540","timestamp":"2026-09-08T10:57:13.430Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"in","message":{"binding":"native-pair-test","role":"worker","kind":"view","id":"b483d8c1-8c16-4f2b-8d2f-2de98f190337","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: b483d8c1-8c16-4f2b-8d2f-2de98f190337","reason":"settled","backgroundQuiet":true}},"id":"97ea169c","parentId":"280d42da","timestamp":"2026-09-08T10:57:13.439Z"}} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: b483d8c1-8c16-4f2b-8d2f-2de98f190337"}],"timestamp":1788865033440}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: b483d8c1-8c16-4f2b-8d2f-2de98f190337"}],"timestamp":1788865033440}} -{"type":"message_start","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."},"partialArgs":"{\"instruction\":\"Read the real outputs before declaring completion.\"}","streamIndex":0}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"pending","timestamp":1788865033468}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_start","contentIndex":0}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_delta","contentIndex":0,"delta":"{\"instruction\":\"Read the real outputs before declaring completion.\"}"}} -{"type":"message_update","assistantMessageEvent":{"type":"toolcall_end","contentIndex":0,"toolCall":{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}}} -{"type":"message_end","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1788865033468,"rawStopReason":"tool_calls"}} -{"type":"tool_execution_start","toolCallId":"test-steer","toolName":"SteerWorker","args":{"instruction":"Read the real outputs before declaring completion."}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"out","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"780f7dac-e4e5-4351-b890-0684a42de681","text":"Read the real outputs before declaring completion."}},"id":"d35e2a1d","parentId":"d5c62400","timestamp":"2026-09-08T10:57:13.489Z"}} -{"type":"tool_execution_end","toolCallId":"test-steer","toolName":"SteerWorker","result":{"content":[{"type":"text","text":"Worker instruction 780f7dac-e4e5-4351-b890-0684a42de681 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false},"isError":false} -{"type":"message_start","message":{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 780f7dac-e4e5-4351-b890-0684a42de681 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788865033490}} -{"type":"message_end","message":{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 780f7dac-e4e5-4351-b890-0684a42de681 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788865033490}} -{"type":"turn_end","message":{"role":"assistant","content":[{"type":"toolCall","id":"test-steer","name":"SteerWorker","arguments":{"instruction":"Read the real outputs before declaring completion."}}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1788865033468,"rawStopReason":"tool_calls"},"toolResults":[{"role":"toolResult","toolCallId":"test-steer","toolName":"SteerWorker","content":[{"type":"text","text":"Worker instruction 780f7dac-e4e5-4351-b890-0684a42de681 sent through pi-intercom. Receipt and execution are not confirmed by this result."}],"details":{},"isError":false,"timestamp":1788865033490}]} -{"type":"turn_start"} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"worker","kind":"received","id":"780f7dac-e4e5-4351-b890-0684a42de681"}},"id":"13443ef1","parentId":"573b245e","timestamp":"2026-09-08T10:57:13.492Z"}} -{"id":"supervisor-state","type":"response","command":"get_state","success":true,"data":{"model":{"id":"test","name":"Offline test model","reasoning":false,"input":["text"],"contextWindow":16000,"maxTokens":1000,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"api":"openai-completions","provider":"offline","baseUrl":"http://127.0.0.1:34037"},"thinkingLevel":"off","isStreaming":true,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/tmp/goals-native-pair-Mk0U2D/agent/sessions/--tmp-goals-native-pair-Mk0U2D--/2026-09-08T10-57-13-272Z_01a080aa-3438-70b0-8498-53cf3c7d68b3.jsonl","sessionId":"01a080aa-3438-70b0-8498-53cf3c7d68b3","autoCompactionEnabled":true,"messageCount":5,"pendingMessageCount":0}} \ No newline at end of file diff --git a/slop/reviews/review-fixes-native/supervisor-stderr.txt b/slop/reviews/review-fixes-native/supervisor-stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/slop/reviews/review-fixes-native/worker-events.jsonl b/slop/reviews/review-fixes-native/worker-events.jsonl deleted file mode 100644 index a2a3e07..0000000 --- a/slop/reviews/review-fixes-native/worker-events.jsonl +++ /dev/null @@ -1,21 +0,0 @@ -{"id":"planning","type":"response","command":"prompt","success":true} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788865032458}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788865032458}} -{"type":"message_start","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"pending","timestamp":1788865032494}} -{"type":"message_update","assistantMessageEvent":{"type":"text_start","contentIndex":0}} -{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Test context retained. Actual outputs still need inspection."}} -{"type":"message_update","assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Test context retained. Actual outputs still need inspection."}} -{"type":"message_end","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788865032494,"rawStopReason":"stop"}} -{"type":"turn_end","message":{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788865032494,"rawStopReason":"stop"},"toolResults":[]} -{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788865032458},{"role":"assistant","content":[{"type":"text","text":"Test context retained. Actual outputs still need inspection."}],"api":"openai-completions","provider":"offline","model":"test","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"stop","timestamp":1788865032494,"rawStopReason":"stop"}],"willRetry":false} -{"type":"agent_settled"} -{"id":"worker-state","type":"response","command":"get_state","success":true,"data":{"model":{"id":"test","name":"Offline test model","reasoning":false,"input":["text"],"contextWindow":16000,"maxTokens":1000,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"api":"openai-completions","provider":"offline","baseUrl":"http://127.0.0.1:34037"},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/tmp/goals-native-pair-Mk0U2D/agent/sessions/--tmp-goals-native-pair-Mk0U2D--/2026-09-08T10-57-12-216Z_01a080aa-3018-770c-8926-822a3e1a2aa9.jsonl","sessionId":"01a080aa-3018-770c-8926-822a3e1a2aa9","autoCompactionEnabled":true,"messageCount":2,"pendingMessageCount":0}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"out","message":{"binding":"native-pair-test","role":"worker","kind":"view","id":"b483d8c1-8c16-4f2b-8d2f-2de98f190337","text":"The worker stopped.\n\nThe saved plan needs a check of the actual outputs.\n\nworker view id: b483d8c1-8c16-4f2b-8d2f-2de98f190337","reason":"settled","backgroundQuiet":true}},"id":"0615fd50","parentId":"0390c249","timestamp":"2026-09-08T10:57:13.437Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"supervisor","kind":"received","id":"b483d8c1-8c16-4f2b-8d2f-2de98f190337"}},"id":"4df9ba42","parentId":"0615fd50","timestamp":"2026-09-08T10:57:13.439Z"}} -{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"in","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"780f7dac-e4e5-4351-b890-0684a42de681","text":"Read the real outputs before declaring completion."}},"id":"814b241e","parentId":"4df9ba42","timestamp":"2026-09-08T10:57:13.490Z"}} -{"type":"agent_start"} -{"type":"turn_start"} -{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788865033490}} -{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788865033490}} \ No newline at end of file diff --git a/slop/reviews/review-fixes-native/worker-stderr.txt b/slop/reviews/review-fixes-native/worker-stderr.txt deleted file mode 100644 index e69de29..0000000 diff --git a/slop/reviews/supervisor-prompt-flow/initial-validation.txt b/slop/reviews/supervisor-prompt-flow/initial-validation.txt deleted file mode 100644 index 81607c6..0000000 --- a/slop/reviews/supervisor-prompt-flow/initial-validation.txt +++ /dev/null @@ -1,66 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 19 passed (19) - Tests 108 passed (108) - Start at 10:47:37 - Duration 3.83s (transform 2.36s, setup 0ms, import 6.55s, tests 9.35s, environment 2ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -src/worker-view.ts:2:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Sort the imported names. - - 1 │ import { compile } from "@sting8k/pi-vcc/src/core/summarize"; - > 2 │ import { supervisorCheckIn, type SupervisorReviewReason } from "./prompts.js"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ - 4 │ export interface SessionBlock { - - i Safe fix: Organize imports and exports (Biome) - - 1 1 │ import { compile } from "@sting8k/pi-vcc/src/core/summarize"; - 2 │ - import·{·supervisorCheckIn,·type·SupervisorReviewReason·}·from·"./prompts.js"; - 2 │ + import·{·type·SupervisorReviewReason,·supervisorCheckIn·}·from·"./prompts.js"; - 3 3 │ - 4 4 │ export interface SessionBlock { - - -test/worker-view.test.ts:2:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Sort the imported names. - - 1 │ import { expect, it } from "vitest"; - > 2 │ import { supervisorPeriodicReview, supervisorPlanChangeReview, supervisorReadyReview, type SupervisorReviewReason, supervisorStartedReview, supervisorStoppedReview } from "../src/prompts.js"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ import { workerView } from "../src/worker-view.js"; - 4 │ - - i Safe fix: Organize imports and exports (Biome) - - 1 1 │ import { expect, it } from "vitest"; - 2 │ - import·{·supervisorPeriodicReview,·supervisorPlanChangeReview,·supervisorReadyReview,·type·SupervisorReviewReason,·supervisorStartedReview,·supervisorStoppedReview·}·from·"../src/prompts.js"; - 2 │ + import·{·type·SupervisorReviewReason,·supervisorPeriodicReview,·supervisorPlanChangeReview,·supervisorReadyReview,·supervisorStartedReview,·supervisorStoppedReview·}·from·"../src/prompts.js"; - 3 3 │ import { workerView } from "../src/worker-view.js"; - 4 4 │ - - -Checked 36 files in 63ms. No fixes applied. -Found 2 errors. -check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Some errors were emitted while running checks. - - diff --git a/slop/reviews/supervisor-prompt-flow/review.md b/slop/reviews/supervisor-prompt-flow/review.md deleted file mode 100644 index ab9ec82..0000000 --- a/slop/reviews/supervisor-prompt-flow/review.md +++ /dev/null @@ -1,49 +0,0 @@ -# Supervisor prompt flow review - -Pi/OpenAI implementation, based on `a7385d4`. Scope: centralize supervisor instructions in `src/prompts.ts` and make check-in tasks and tool descriptions ask for judgment followed by useful action. No transport, lifecycle, approval-gate, plan-selection, or planning-policy changes. - -## Narrative order and wiring - -1. Existing planning and worker resync prompts, unchanged. -2. `supervisorOpening`, `supervisorPrompt`, `supervisorReviewContext`, `supervisorOrientation`, `supervisorCompaction`: role and plan context. The user-authored agency opening and constitution/pi-supervisor provenance are retained. Long role asks for applicable AGENTS.md/skills, remains generic, removes one duplicate autonomy paragraph, and makes SteerWorker—not a recap—the continuation action. The short review and startup/compaction cadence are unchanged. -3. `supervisorCheckIn`: ready, started, active periodic, stopped/settled, plan-edit tasks. `src/worker-view.ts` invokes it outside truncatable activity content. Status prefixes remain exactly `The worker is ready to begin.`, `The worker is still working.`, and `The worker stopped.`. Observed idleness still governs the prefix; an idle interval gets stopped guidance, while a nominal settled event that is not idle gets active-work guidance. -4. `supervisorPlanReview`: existing diff/claim data plus plan-change guidance, wired from `src/index.ts`. The guidance now precedes truncatable diff detail so long diffs do not evict it. -5. SteerWorker description, parameter description and delivery result. -6. ApproveGoal description, parameter descriptions and approval-success instruction. Acceptance is conditional on the supervisor judging the result achieved; mechanics are a separate paragraph. Gate errors stay at their checks, unchanged. The successful result tells the supervisor to use SteerWorker for CompleteGoal and continue remaining goals. -7. Existing worker CompleteGoal description corrected to address its caller: the worker runs verification and seeks supervisor review first; the tool consumes recorded approval. It no longer tells the worker to "direct the worker" or implies the read-only supervisor can create evidence. Approval gates are unchanged. - -Runtime data labels and view serialization remain near their producers, rather than turning this into a string registry. The dynamic mechanical errors remain in supervisor-session.ts as allowed by the task. - -## Exact event tasks - -Ready: -> Check the agreed outcome and decide the next useful action. Use SteerWorker to send the worker a concrete starting instruction; do not repeat one already being acted on. - -Started: -> The worker has begun a turn. Check whether its direction fits the agreed goal; let productive work continue and use SteerWorker only if a correction is needed. - -Active periodic: -> Is the worker on track toward the user's intended outcome? Check for drift, mistaken assumptions, or wasted effort. Use SteerWorker to send a correction where useful; otherwise let productive work continue without interruption. - -Stopped/settled: -> Inspect the results and judge whether the agreed goal is actually achieved. If unfinished, investigate why the worker stopped and use SteerWorker to send the next useful instruction and resume work. If a verified dependency prevents progress, establish what will resume it and how that will be observed. Do not treat stopping as completion. Consider ApproveGoal only after the results satisfy the goal. - -Plan edit/manual tick: -> Assess plan changes against the user's intent and preferences. Manual checkbox edits are claims, not proof of completion. Inspect the actual result before accepting a claim; use SteerWorker to send corrections when the plan or work has drifted. Preserve authorized changes. - -ApproveGoal decision paragraph: -> Use only after judging that the actual result satisfies the user's intended outcome and the goal's discriminator. This tool records your acceptance; its mechanical checks cannot establish success. If the goal is unmet or evidence is insufficient, do not approve: use SteerWorker to request the next useful work or check. - -## Validation and limits - -- Read AGENTS.md, annoy-less skill and installed Pi extension docs: before_agent_start persistent custom messages/chained system prompt, and sendUserMessage behavior (an idle worker starts a turn; an active worker receives queued steering). -- `validation.txt`: 108 tests pass in 19 files, including real installed Pi RPC and native fork/Intercom checks; typecheck, lint, build and diff check pass. -- `initial-validation.txt`: same tests/typecheck passed, lint found only two import-order issues. Fixed those and reran the full command successfully. -- Added 9 worker-view event/status combinations and 3 prompt-semantic tests. Updated flow tests assert manual ticks and external plan edits carry judgment/continuation instructions. Supervisor hook/tool tests verify centralized text is wired, including startup/compaction and approval success. -- Native pair fixture now produces its view through the actual workerView. The real Pi provider request is asserted to contain the stopped task and actual registered SteerWorker/ApproveGoal descriptions, and its emitted instruction reaches the worker exactly. The local model is deterministic: this establishes wiring, not judgment quality. -- Approval logic/transport/plan extraction are unchanged. `git diff --quiet HEAD -- src/approval.ts src/intercom.ts src/plan-view.ts src/plan.ts` passed before commit. Unicode envelope budget regression still passes with the added event tasks. -- No user or test Herdr panes operated. No push. Unrelated dirty native logs were not changed; docs/human_journal.md was never read or written. Tests unset inherited PI_GOALS_EVIDENCE_DIR, PI_SUBAGENT_CHILD and PI_GOALS_ROLE. - -## Parent acceptance still required - -In a new isolated Herdr task, require an exact result (for example a specific byte sequence). Let the worker stop with a real artifact that fails that goal. Read both panes and the artifact: the supervisor must identify the mismatch, send a corrective SteerWorker instruction rather than approve, observe the resumed worker, and only approve after the corrected result satisfies the discriminator. Also confirm productive active work is left alone and an authorized plan edit is not mechanically rejected. Do not count deterministic test output or delivery receipts as autonomous outcome success. Independent reviewer gate remains parent-owned. diff --git a/slop/reviews/supervisor-prompt-flow/validation.txt b/slop/reviews/supervisor-prompt-flow/validation.txt deleted file mode 100644 index aebc72b..0000000 --- a/slop/reviews/supervisor-prompt-flow/validation.txt +++ /dev/null @@ -1,26 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 19 passed (19) - Tests 108 passed (108) - Start at 10:48:47 - Duration 3.88s (transform 4.98s, setup 0ms, import 9.43s, tests 8.78s, environment 2ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 36 files in 37ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - diff --git a/slop/reviews/vcc-view/compare.mjs b/slop/reviews/vcc-view/compare.mjs deleted file mode 100644 index e77a15c..0000000 --- a/slop/reviews/vcc-view/compare.mjs +++ /dev/null @@ -1,56 +0,0 @@ -// Read-only replay of recorded research branches. Run from the pi-goals root after npm run build. -import assert from "node:assert/strict"; -import { execFileSync } from "node:child_process"; -import { createHash } from "node:crypto"; -import { readFileSync, writeFileSync } from "node:fs"; -import { createRequire } from "node:module"; -import { resolve } from "node:path"; -import ts from "typescript"; - -const output = "slop/reviews/vcc-view"; -const root = "/home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/"; -const workerPath = `${root}2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl`; -const supervisorPath = `${root}2026-09-08T22-43-35-654Z_01a08330-e866-7004-9b7f-5efdceb2488e.jsonl`; -const load = path => readFileSync(path, "utf8").trim().split("\n").map(line => JSON.parse(line)); -const entries = load(workerPath); -const byId = new Map(entries.map(entry => [entry.id, entry])); -const records = load(supervisorPath); -const oldSource = execFileSync("git", ["show", "8953dce:src/worker-view.ts"], { encoding: "utf8" }); -const oldCode = ts.transpileModule(oldSource, { compilerOptions: { module: ts.ModuleKind.ES2022 } }).outputText; -const { workerView: oldView } = await import(`data:text/javascript;base64,${Buffer.from(oldCode).toString("base64")}`); -const piRequire = createRequire(import.meta.resolve("@earendil-works/pi-coding-agent")); -const { createJiti } = piRequire("jiti"); -const jiti = createJiti(import.meta.url, { moduleCache: false, fsCache: false }); -const { workerView: newView } = await jiti.import(resolve("src/worker-view.ts")); -const { workerView: builtView } = await jiti.import(resolve("dist/worker-view.js")); -const results = []; -for (const [name, through] of [["pilot-preparation", "3cb9b26f"], ["flow-implementation", "69943231"], ["settled-checkpoint", "e266d41e"]]) { - const record = records.find(entry => entry.customType === "pi-goals-intercom" && entry.data.message.kind === "view" && entry.data.message.through === through); - assert(record, `recorded view ${through}`); - const message = record.data.message; - const branch = []; - for (let entry = byId.get(through); entry; entry = byId.get(entry.parentId)) branch.unshift(entry); - assert(branch.length, "nonempty live branch"); - const ack = branch.filter(entry => entry.customType === "pi-goals-intercom" && entry.data.direction === "ack" && entry.data.message.through).at(-1); - const context = { - sourceSession: workerPath, - model: message.text.match(/^worker model: (.*)$/m)[1], - latestDirection: message.text.match(/latest human direction:\n([\s\S]*?)\ntool calls with no result:/)[1], - background: message.text.match(/^tracked background work: (.*)$/m)[1], - since: ack?.data.message.through, - }; - const args = [branch, message.reason, message.text.startsWith("The worker stopped."), context]; - const before = oldView(...args); - const after = newView(...args); - assert.equal(builtView(...args), after, "built/source real compiler parity"); - const envelope = { binding: message.binding, role: "worker", kind: "view", id: message.id, text: after, reason: message.reason, through, backgroundQuiet: message.backgroundQuiet }; - assert(Buffer.byteLength(JSON.stringify(envelope)) < 16_000, "serialized transport bound"); - // Normalize only the saved files' trailing blank lines; byte metrics use the exact rendered strings. - writeFileSync(`${output}/${name}-old.md`, before.trimEnd() + "\n"); - writeFileSync(`${output}/${name}-vcc.md`, after.trimEnd() + "\n"); - results.push({ name, timestamp: record.timestamp, through, since: context.since, branchEntries: branch.length, branchSha256: createHash("sha256").update(JSON.stringify(branch)).digest("hex"), oldBytes: Buffer.byteLength(before), vccBytes: Buffer.byteLength(after), oldSerializedTextBytes: Buffer.byteLength(JSON.stringify(before)), vccSerializedTextBytes: Buffer.byteLength(JSON.stringify(after)), envelopeBytes: Buffer.byteLength(JSON.stringify(envelope)) }); -} -const manifest = { baseline: "8953dce", workerPath, supervisorPath, compiler: "@sting8k/pi-vcc@0.5.0", results }; -writeFileSync(`${output}/comparison.json`, JSON.stringify(manifest, null, 2) + "\n"); -console.log(JSON.stringify(manifest, null, 2)); -console.log("PASS: three identical historical branch/ack windows, serialized bounds, source and built compiler execution agree."); diff --git a/slop/reviews/vcc-view/comparison.json b/slop/reviews/vcc-view/comparison.json deleted file mode 100644 index 856332d..0000000 --- a/slop/reviews/vcc-view/comparison.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "baseline": "8953dce", - "workerPath": "/home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl", - "supervisorPath": "/home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T22-43-35-654Z_01a08330-e866-7004-9b7f-5efdceb2488e.jsonl", - "compiler": "@sting8k/pi-vcc@0.5.0", - "results": [ - { - "name": "pilot-preparation", - "timestamp": "2026-09-08T22:59:31.347Z", - "through": "3cb9b26f", - "branchEntries": 597, - "branchSha256": "f0da668db6266780aa3ba803e5260d4009732c74ab3276f4db55910b8d74af5a", - "oldBytes": 4961, - "vccBytes": 5356, - "oldSerializedTextBytes": 5324, - "vccSerializedTextBytes": 5462, - "envelopeBytes": 5655 - }, - { - "name": "flow-implementation", - "timestamp": "2026-09-08T23:17:08.542Z", - "through": "69943231", - "since": "54648ab4", - "branchEntries": 715, - "branchSha256": "2293e2e7158c051c3571cd50befb14900cea39314640421a5735633775af427b", - "oldBytes": 4897, - "vccBytes": 2396, - "oldSerializedTextBytes": 5109, - "vccSerializedTextBytes": 2452, - "envelopeBytes": 2646 - }, - { - "name": "settled-checkpoint", - "timestamp": "2026-09-09T00:11:07.924Z", - "through": "e266d41e", - "since": "18c6af89", - "branchEntries": 981, - "branchSha256": "3460caf8d1a093309ad2639decbc181151f27871c42ffa62ca98803aae726ce7", - "oldBytes": 1676, - "vccBytes": 1840, - "oldSerializedTextBytes": 1721, - "vccSerializedTextBytes": 1882, - "envelopeBytes": 2078 - } - ] -} diff --git a/slop/reviews/vcc-view/flow-implementation-old.md b/slop/reviews/vcc-view/flow-implementation-old.md deleted file mode 100644 index e5eadb8..0000000 --- a/slop/reviews/vcc-view/flow-implementation-old.md +++ /dev/null @@ -1,67 +0,0 @@ -The worker is still working. - -review trigger: turns -source session: /home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl -worker model: openai-codex/gpt-6-astra -latest human direction: -but also try the flow healing one as a next goal on the list -tool calls with no result: none -tracked background work: processes: 1 (e55-fine-job802-follower); subagents: 0; unregistered detached work is not tracked - -new worker transcript since the last acknowledged view: -[truncated; inspect source session] -: answer_reached={} reason={} E={:.3f}", setting.name, task.name, r["scorable"], r["reason"], r["E"]) - if setting.method == "base": - if r["scorable"]: - base_scorable.add(task.name) - if task.name in {"s_add", "l_moral_lie"}: - ref_ids = ids[:, :n_prompt + cfg.kl_tokens] - base_logp = model(ref_ids, use_cache=False).logits[:, n_prompt - 1:-1].float().log_softmax(-1) - references.append((ref_ids, n_prompt - 1, base_logp)) - report = evaluate_with_vector(model, tok, vignettes=vignettes, max_think_tokens=cfg.think_tokens, - batch_size=2, log_demo=False, verbose=0) - kl = shared_prefix_kl(model, references) - if setting.method == "logit_diff": - dump(outdir / f"{setting.name}_adapter.json", stats) - dump(outdir / f"{setting.name}_behavior.json", report["per_row"]) - if setting.method == "base": - base_report = report - delta = dclr_per_foundation(base_report, report) - care, auth = delta["Care"]["mean"], delta["Authority"]["mean"] - other = [delta[f]["mean"] for f in FOUNDATION_ORDER if f not in {"Care", "Authority"}] - off = sum(abs(x) for x in other) / len(other) - scorable = [r for r in generated if r["scorable"]] - subset = [r for r in generated if r["task"] in base_scorable] - direction = random_v if setting.random_direction else v0 - along = setting.c * float(direction @ v0) - point = dict(name=setting.name, method=setting.method, internal_c=setting.c, - logit_alpha=setting.alpha if setting.method == "logit_diff" else 1.0, - frac_scorable=len(scorable) / len(tasks), n_base_scorable=len(subset), - frac_on_base_scorable=sum(r["scorable"] for r in subset) / len(subset) if subset else float("nan"), - mean_nonforced_E=sum(r["E"] for r in generated) / len(tasks), - care=care, auth=auth, dlog_on_target=-auth, dlog_off_target=off, - net_behavior_nats=care - auth, score=float("nan"), - pmass_coherence=report["mean_pmass_allowed"], base_pmass=base_report["mean_pmass_allowed"], - pmass_floor=0.99 * base_report["mean_pmass_allowed"], - passes_pmass=report["mean_pmass_allowed"] >= 0.99 * base_report["mean_pmass_allowed"], - steering_strength_v0=along, total_strength=setting.c, - off_axis_strength=setting.c * float((direction - (direction @ v0) * v0).norm()), - shared_prefix_kl_nats=kl, behavior_pairs=delta["Authority"]["n"], - behavior_pairs_total=delta["Authority"]["n_total"], elapsed_s=time.monotonic() - stage_start) - points.append(point) - pl.DataFrame([{k: v for k, v in r.items() if k not in {"completion", "prefix", "generated_ids"}} for r in records]).write_csv(outdir / "tasks.csv") - pl.DataFrame([{"name": r["name"], "prompt": r["task"], "task": r["task"], - "scenario": choice_metadata[r["task"]]["scenario"] if r["task"] in choice_metadata else None, - "rating_1_to_5": None, "coherent": None, "passes_demo_gate": None, - "premise_preserved": None, "post_answer_repetition": None, "evidence_quote": None, - "care_choice_verified": None, "failure_reason": "pending_manual"} for r in records]).write_csv(outdir / "demo_audit.tsv", separator="\t") - table = write_report(outdir, points, records, cfg) - logger.info("{}: {:.1f}s, ΔCare={:+.3f}, ΔAuth={:+.3f}, KL={:.4f}", setting.name, point["elapsed_s"], care, auth, kl) - assert {(r["name"], r["task"]) for r in records} == {(s.name, t.name) for s in settings for t in tasks} - assert len(records) == len(settings) * len(tasks), "duplicate or missing generation" - logger.info("SHOULD every treatment/prompt cell retained: {} records PASS", len(records)) - logger.info("RESULT_DEMO: NO_RESULT (pending manual audit)\n{}\nreport={}\nelapsed_s={:.1f}", table, outdir / "report.md", time.monotonic() - start) - - -if __name__ == "__main__": - main(tyro.cli(Cfg)) diff --git a/slop/reviews/vcc-view/flow-implementation-vcc.md b/slop/reviews/vcc-view/flow-implementation-vcc.md deleted file mode 100644 index 2e514cd..0000000 --- a/slop/reviews/vcc-view/flow-implementation-vcc.md +++ /dev/null @@ -1,39 +0,0 @@ -The worker is still working. - -review trigger: turns -source session: /home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl -worker model: openai-codex/gpt-6-astra -latest human direction: -but also try the flow healing one as a next goal on the list -tool calls with no result: none -tracked background work: processes: 1 (e55-fine-job802-follower); subagents: 0; unregistered detached work is not tracked - -new worker overview since the last acknowledged view (VCC algorithmic compression; local # refs index new messages; tool-result bodies omitted; inspect source for evidence): -[Files And Changes] -- Modified: experiments/e56_flow_repair/flow.py, experiments/e56_flow_repair/test_flow.py, - slop/reviews/e56_flow_matching_source.md, slop/reviews/e56_flow_discussion_brief.md, - experiments/e56_flow_repair/intervention.py, src/manifold_steer/autoencoder.py -- Read: slop/reviews/2026-09-09_glm-5.3-flash_e56_flow_scientist.md, - /workspace/2026/lite/steering-lite/src/steering_lite/variants/mean_diff.py, - slop/audits/steering_tradeoff/flow_synthetic.log, slop/reviews/2026-09-09_deepseek-v4-pro-0813_e56_flow_scientist.md, - /home/code/.pi/agent/skills/arxiv/SKILL.md, /workspace/2026/lite/steering-lite/src/steering_lite/config.py, - experiments/e55_logit_diff_amplification/logit_diff.py, /workspace/2026/lite/steering-lite/src/steering_lite/vector.py - , slop/reviews/2026-09-09_deepseek-v4-pro-0813_e56_flow_discussion.md, - slop/reviews/2026-09-09_glm-5.3-flash_e56_flow_discussion.md - -[assistant] -* (15 earlier tool-call entries omitted) -* read "experiments/e55_logit_diff_amplification/logit_diff.py" (#30) -* read "/workspace/2026/lite/steering-lite/src/steering_lite/vector.py" (#30) -* write "experiments/e56_flow_repair/intervention.py" (#34) -* edit "experiments/e56_flow_repair/test_flow.py" (#36) -* read "slop/reviews/2026-09-09_deepseek-v4-pro-0813_e56_flow_discussion.md" (#38) -* read "slop/reviews/2026-09-09_glm-5.3-flash_e56_flow_discussion.md" (#40) -* edit "src/manifold_steer/autoencoder.py" (#42, #44, #46) x3 -(thinking) Thinking: **Preparing evaluation configuration** - -**Refactoring evaluation context** (#48) -(thinking) Thinking: **Preparing token geometry logging** - -**Implementing token geometry logging** (#48) -* bash "sed -n '255,370p' experiments/e55_logit_diff_amplification/run.py" (#48) diff --git a/slop/reviews/vcc-view/herdr-acceptance.md b/slop/reviews/vcc-view/herdr-acceptance.md deleted file mode 100644 index b4906b1..0000000 --- a/slop/reviews/vcc-view/herdr-acceptance.md +++ /dev/null @@ -1,20 +0,0 @@ -# VCC functional acceptance - -Pi/OpenAI observed the isolated interactive worker in Herdr pane `w8:p54`, task repo `/tmp/pi-goals-vcc-functional-task`, using source commit `039f4a4`. Parent submitted the trivial exact-byte goal and selected Ready. The pair then completed without parent task execution or steering. - -Observed rendered output: - -> [supervisor] Supervisor approval is recorded. Call CompleteGoal for Deliver the committed hello file with byte proof and completion approval now. Do not change any files or run further work. - -> CompleteGoal -> Sign-off accepted. Goal ticked [x] in .pi/plan/01a083f1-296e-7594-8623-3e009c90a85d-v1.md. - -Parent independently read artifact bytes with `od -An -tx1 hello.txt`: - -``` -48 65 6c 6c 6f 20 66 72 6f 6d 20 56 43 43 2e 0a -``` - -These encode `Hello from VCC.` plus one newline. Task commit: `a701105 Add verified VCC hello file`. - -This establishes successful real planning/Ready/supervised completion with the VCC dependency loaded. It does not establish improved scientific judgment or cost savings. Replay comparisons are in `review.md`. The subsequent goal-repeat/reminder change was not loaded in this pair; its cadence, changed-checkbox and compaction behavior were checked separately in automated tests. Rejecting a valid artifact that fails a scientific goal remains a behavioral acceptance gap. diff --git a/slop/reviews/vcc-view/pilot-preparation-old.md b/slop/reviews/vcc-view/pilot-preparation-old.md deleted file mode 100644 index c19b42a..0000000 --- a/slop/reviews/vcc-view/pilot-preparation-old.md +++ /dev/null @@ -1,144 +0,0 @@ -The worker is still working. - -review trigger: turns -source session: /home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl -worker model: openai-codex/gpt-6-astra -latest human direction: -but also try the flow healing one as a next goal on the list -tool calls with no result: none -tracked background work: processes: 0; subagents: 0; unregistered detached work is not tracked - -compaction summary (worker account, not independent evidence): -[OpenAI native compaction checkpoint] - -new worker transcript (initial or reset view): -[truncated; inspect source session] -"enqueued_at": "2026-09-08T13:38:57.301057325+08:00" - } - }, - "priority": 0, - "label": "why: validate common L22-24 C2 on dog legs; resolve: correct clean controls and consistent transfer on plain questions", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 769, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T13:38:57.518018760+08:00" - } - }, - "priority": 0, - "label": "why: validate common L22-24 C2 on dog name; resolve: correct clean controls and consistent transfer on plain questions", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 770, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T13:38:57.740040876+08:00" - } - }, - "priority": 0, - "label": "why: validate common L22-24 C2 on dog property; resolve: correct clean controls and consistent transfer on plain questions", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 771, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T13:38:57.987386109+08:00" - } - }, - "priority": 0, - "label": "why: validate common L22-24 C2 on ant legs; resolve: correct clean controls and consistent transfer on plain questions", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 772, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T13:38:58.231236887+08:00" - } - }, - "priority": 0, - "label": "why: validate common L22-24 C2 on ant name; resolve: correct clean controls and consistent transfer on plain questions", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 773, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T13:38:58.435527993+08:00" - } - }, - "priority": 0, - "label": "why: validate common L22-24 C2 on ant property; resolve: correct clean controls and consistent transfer on plain questions", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 775, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T13:41:03.538541658+08:00" - } - }, - "priority": 0, - "label": "why: selected dog band may just cause generic changes; resolve: eight random spans matched per-token edit magnitude", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 776, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T13:41:04.114346905+08:00" - } - }, - "priority": 0, - "label": "why: selected ant band may just cause generic changes; resolve: eight random spans matched per-token edit magnitude", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 778, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T15:16:24.724372591+08:00" - } - }, - "priority": 0, - "label": "why: dog clean controls may fail from assistant-prefilled questions; resolve: user-role and generation-boundary repair must give correct clean answers before assessing fixed L22-24 C2 transfer", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 779, - "status": { - "Queued": { - "enqueued_at": "2026-09-08T15:16:24.923827832+08:00" - } - }, - "priority": 0, - "label": "why: ant clean controls may fail from assistant-prefilled questions; resolve: user-role and generation-boundary repair must give correct clean answers before assessing fixed L22-24 C2 transfer", - "path": "/workspace/2026/suppressed-activations" - }, - { - "id": 801, - "status": { - "Running": { - "enqueued_at": "2026-09-09T06:50:35.856041965+08:00", - "start": "2026-09-09T06:50:48.080797309+08:00" - } - }, - "priority": 0, - "label": "why: larger paired MLP reader/writer needs a fitting check; resolve: batch-eight memory and complete local readouts before held-out training; args --steps 4 --eval-every 2", - "path": "/workspace/2026/LUCID3_wikit" - } - ] -} - - -tool: edit - -Successfully replaced 3 block(s) in experiments/e55_logit_diff_amplification/run_fine_s43/run_card.md. - -tool: bash - -(no output) diff --git a/slop/reviews/vcc-view/pilot-preparation-vcc.md b/slop/reviews/vcc-view/pilot-preparation-vcc.md deleted file mode 100644 index 23f9dbb..0000000 --- a/slop/reviews/vcc-view/pilot-preparation-vcc.md +++ /dev/null @@ -1,82 +0,0 @@ -The worker is still working. - -review trigger: turns -source session: /home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl -worker model: openai-codex/gpt-6-astra -latest human direction: -but also try the flow healing one as a next goal on the list -tool calls with no result: none -tracked background work: processes: 0; subagents: 0; unregistered detached work is not tracked - -compaction summary (worker account, not independent evidence): -[OpenAI native compaction checkpoint] - -new worker overview (initial or reset view) (VCC algorithmic compression; local # refs index new messages; tool-result bodies omitted; inspect source for evidence): -[Session Goal] -- was your job killed? -- [Scope change] -- maybe read it your self ml-debug - -[Files And Changes] -- Modified: slop/audits/e55/job798_parent_read.md, /workspace/2026/mfv/manifold-steer/.pi/plan/01a0809b-a528-7724-a514-5 - 9f3c61116a6-v1.md, experiments/e55_logit_diff_amplification/LAB.md, experiments/ACTIVE.md, - experiments/e55_logit_diff_amplification/choice_tasks.py, experiments/e55_logit_diff_amplification/run.py, - experiments/e55_logit_diff_amplification/choice_summary.py, - experiments/e55_logit_diff_amplification/test_choice_summary.py, justfile, - experiments/e55_logit_diff_amplification/run_fine_s43/run_card.md (+2 more) -- Read: experiments/e55_logit_diff_amplification/LAB.md, /home/code/.pi/agent/skills/ml-debug/SKILL.md, - experiments/e55_logit_diff_amplification/run_s43/run.log, experiments/e55_logit_diff_amplification/run_s43/run_card.md - , /home/code/.pi/agent/skills/plan-format/SKILL.md, experiments/e55_logit_diff_amplification/run.py, - /home/code/.pi/agent/skills/pueue/SKILL.md, /home/code/.pi/agent/skills/machine-map/SKILL.md, - /home/code/.pi/agent/skills/ponytailplus/SKILL.md, /workspace/2026/lite/steering-lite/src/steering_lite/eval/tinymfv.p - y (+11 more) - -[Commits] -- dff95a9: Record E55 independent demo audit and parent caveats - -[User Preferences] -- Style: Make it easy for a busy and forgetfull user to review. Use ASD-STE100 Simplified Technical English. Use active - voice, one idea per sentence, common words, - -[truncated; inspect source session] -te the stale E55 LAB/ACTIVE claims ('Not run'/'queued798') from existing evidence. -Then implement and run the smallest finer-dose comparison resolving matched achieved behavior versus coherence, with a -reviewed run card and saved just smoke output before queueing on the existing default group. Specify behavior -measurement and uncertainty before collecting new results: forced 32-token CLR and answer-span E alone cannot establish -the tradeoff; retain full 512-token generations, premise/repetition audits and missing completions rather than selecting -survivors. Compare only overlapping measured effects and keep confirmation prompts separate. Diagnose unexpected results -with ml-debug. Goal2 is authorized next: inspect existing AE repair and prepare independent-reviewed corruption-to-clean -flow pseudocode during safe CPU work if goal1 is queued; no sign-off dependency forbids that preparation. Deliver the -single linked report with both plots, full demos, independent reviews and saved verification/job receipts. Continue -autonomously within scope; no approval claim until real evidence is ready. - -[assistant] -* (40 earlier tool-call entries omitted) -* edit "experiments/e55_logit_diff_amplification/run.py" (#161, #163) x2 -* process (#165) -* bash "cp /home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/subagent-artifacts/outputs/7a33c86b-5383- - 4752-8ecb-d5912c2583bd/slop/reviews/tradeoff_measurement.md slop/reviews/tradeoff_prequeue.md; cp - /home/code/.pi/agent/sessio..." (#167) -The review found an important measurement issue: moralmaps deliberately prevents `</think>` and EOS during its probe. -Its `emitted_close=0` therefore was not evidence of natural nontermination. - -The follow-up keeps that probe diagnostic only. It adds freely generated care-versus-authority choices, finer doses, -paired option orders, and explicit uncertainty for missing answers. I’m fixing the review’s parser and analysis issues -before queueing. - -Flow preparation also confirms a distinct test: the previous online AE used one learned map; flow repair would integrate -a time-conditioned recovery field. (#169) -* edit ".pi/plan/01a0809b-a528-7724-a514-59f3c61116a6-v1.md" (#169) -* bash "grep -E 'Ran |OK|PASS|elapsed_s=' slop/audits/steering_tradeoff/reviewed_smoke.log; cat - slop/audits/steering_tradeoff/saved_vector_check.log; git diff --check; pueue add --help | head -65; pueue status - --json | jq '[.tasks[] | select(.gr..." (#171) -* bash "pueue status --json | jq '{groups:.groups,tasks:[.tasks[] | select(.group=="default" and (.status.Done == null)) - | {id,status,priority,label,path}]}'" (#173) -(thinking) Thinking: **Refining run-card wording** (#175) -* edit "experiments/e55_logit_diff_amplification/run_fine_s43/run_card.md" (#175) -(thinking) Thinking: **Checking saved-vector comment** - -**Extracting old-run report blocks** (#177) -* bash "UV_NO_SYNC=1 uv run python -c 'from pathlib import Path; - p=Path("experiments/e55_logit_diff_amplification/LAB.md"); report=Path("experiments/e55_logit_diff_amplification/run_s - 43/report.md").read_text(); blocks=report.split("\n## ")[1:]; ..." (#177) diff --git a/slop/reviews/vcc-view/plan.md b/slop/reviews/vcc-view/plan.md deleted file mode 100644 index 9510744..0000000 --- a/slop/reviews/vcc-view/plan.md +++ /dev/null @@ -1,7 +0,0 @@ -# VCC worker-view change / UAT plan - -Goal: improve supervisor judgment per context token by replacing raw transcript-tail extraction with the existing deterministic VCC compiler. Keep acknowledged-entry slicing, plan changes, tracked background work, and missing tool results. Preserve two recent thinking tails next to their actions. No transport or supervision lifecycle redesign. - -Acceptance: pin and inspect the compiler dependency; test new-turn slicing, compaction/rewind resets, thinking/action ordering, tool arguments, extracted files/context, output omission notices and serialized byte bounds. Run full tests, typecheck, lint and build. Render old and new views from identical recorded maniworker windows, saving reproducible comparison and honest information-loss notes. The parent must perform real isolated Herdr acceptance after this handoff; no research pane interaction here. - -Baseline: HEAD 8953dce, src/worker-view.ts 69 lines. Pre-existing dirty native worker/supervisor event logs and untracked docs/human_journal.md are outside scope and remain untouched/unstaged. No dependency lifecycle scripts will run. diff --git a/slop/reviews/vcc-view/review.md b/slop/reviews/vcc-view/review.md deleted file mode 100644 index 16b8c01..0000000 --- a/slop/reviews/vcc-view/review.md +++ /dev/null @@ -1,62 +0,0 @@ -# VCC worker overview: implementation and replay review - -## Decision and scope - -Use the deterministic compiler from `@sting8k/pi-vcc@0.5.0` inside the existing worker view. No model call, copied compiler, transport/lifecycle change, new monitoring framework, or change to approval rules. Keep acknowledged-entry boundaries, compaction reset, missing-result matching, plan diff/status claims and existing managed process/subagent tracking. Add context percentage from `ctx.getContextUsage().percent`; unknown remains omitted. - -`src/worker-view.ts`: **69 -> 100 lines (+31)**. Compiler declaration: **10 lines**. Caller: **+1 line**. Not fewer lines than the previous raw-tail implementation, but much smaller than transplanting the 302-line pi-supervise view plus its lifecycle. VCC itself remains an external dependency, not free code complexity. - -## Dependency provenance and security - -- Inspected `../pi-supervise/src/view.ts`, package manifest/lock, installed compiler and normalization/brief/extractor path. Custom last-two-thinking support is in pi-supervise's adapter, not a patched installed VCC package. -- Downloaded exact registry tarball using `npm pack @sting8k/pi-vcc@0.5.0 --ignore-scripts --pack-destination /tmp/pi-goals-vcc-package --json`. -- `diff -qr /tmp/pi-goals-vcc-package/package ../pi-supervise/node_modules/@sting8k/pi-vcc` produced no differences. This includes all installed package files, not just version strings. -- Pinned exact `0.5.0` in dependencies and lockfile. Registry: `https://registry.npmjs.org/@sting8k/pi-vcc/-/pi-vcc-0.5.0.tgz`; SHA512 integrity: `KJbOVUFbyghn6h+RD9bDXFNWkKNqpxaCpPQWceOuxMPe9ySpbEfaYnqO9CZUiCP3AFmQ5Ghnsg2B8pdKgY+0Hg==`. -- Tarball SHA1: `090e5c7cacec00b1083bf423bc08aa2d3eb9cb3a`; size 16,206,703 bytes compressed, 16,712,402 unpacked. It ships more than just the compiler. Added one package; no new transitive packages beyond already installed peers. -- Read cybersec-situational-awareness skill before fetching/installing. Used `npm install --save-exact @sting8k/pi-vcc@0.5.0 --ignore-scripts --no-audit --no-fund`. No lifecycle scripts run. Mise is installed but has no configured/installed Node version; used current project Node v22.23.2/npm rather than install another toolchain. This was not a sandboxed install. -- Runtime imports only compiler source, not the VCC extension entrypoint. The compiler pipeline is algorithmic: no network, shell, or model call. -- Local upstream clone is newer (09c4a74, 0.6.0 work); deliberately did not switch versions. This reproduces the installed reference dependency. - -### Source-only package type boundary - -Direct tsc traversal exposed three upstream 0.5.0 errors: `brief.ts:61,77` passes Intl SegmentData with optional `isWordLike` to a required-boolean shape, and `normalize.ts:21` compares Pi Message role with `bashExecution`, outside that union. Supervisor approved a narrow declaration for the exact compile input/output, with tsconfig path mapping just as this repo handles pi-intercom. No runtime fallback or node_modules patch. Source and built JS imports execute the real compiler in replay; tests likewise execute the real package. This declaration must be reviewed if the pinned compiler version changes. - -## Replay method and result - -`node slop/reviews/vcc-view/compare.mjs` loads the actual maniworker branch terminating at each recorded manisuper view's `through` ID, follows parent links (not dead branches), and recovers its last acknowledged view boundary. Both renderers receive identical entries, context, trigger and idle state. Old renderer comes from HEAD 8953dce; new source and built renderer outputs must agree. Paths, timestamps, IDs and branch SHA256 hashes are in `comparison.json`. No sessions were messaged, reloaded or edited. The source sessions remain local prerequisites to reproduce the replay; saved rendered views are reviewable without them. - -| Recorded window | Old UTF-8 bytes | VCC UTF-8 bytes | Observation | -|---|---:|---:|---| -| Pilot preparation, 22:59:31 | 4,961 | 5,356 | Slightly larger, much better task signal | -| Flow implementation, 23:17:08 | 4,897 | 2,396 | About half the bytes; restores action context | -| Settled checkpoint, 00:11:07 | 1,676 | 1,840 | Slightly larger; direct test output lost | - -These are bytes, **not measured token counts or realized supervisor cost savings**. Each complete serialized transport envelope is below 16KB (largest 5,655 bytes). The raw transcript and VCC brief keep the same total 5.5KB serialized content allowance; VCC splits extracted headers (1.5KB) from newest brief (4KB) when headers exist, so one cannot evict the other. - -### What improves - -- Pilot old view is dominated by unrelated queue-job JSON. VCC retains the decision that moralmaps deliberately suppresses think closure/EOS, so `emitted_close=0` is not evidence of natural nontermination; the narrower choice pilot/uncertainty approach; edited files/commit; recent thinking about run-card wording and saved-vector checks; actual shell commands rather than `tool: bash`. -- Flow old view is almost entirely a raw Python source tail with no useful explanation of the current action. VCC gives changed file paths, reviewed source paths, edit/read/bash arguments and the two recent thinking tails about evaluation configuration and token geometry. Existing E55 follower tracking remains unchanged. -- Checkpoint retains the claim `Test passed. Committed as c41e0f2`, extracted commit title, changed files, verification-log path, and thinking about byte identity/rubric migration. The old view had no tool arguments or thinking. - -### Honest limits / evidence lost - -- The checkpoint's actual `Ran 1 test ... OK` tool result is absent in VCC. Supervisor must read the retained log path before treating the worker's pass claim as evidence. The view explicitly says tool-result bodies are omitted and provides the source session path. -- VCC file lists/commits are extracted activity, not independent proof that a write or commit succeeded. Some file classifications are heuristic (`write` is reported as Modified). Full source/artifact inspection still matters. -- VCC's initial Session Goal extraction includes weak historical phrases such as `was your job killed?`, not the actual agreed research discriminator. It is an overview, not a replacement for the plan or latest human direction. Existing plan review remains separate. -- Generic `process` calls appear by name only in VCC's installed compiler; their detailed command/state is not reconstructed here. Existing live background summary still names tracked processes/subagents. Unregistered detached work remains untracked, as before. -- Older brief/tool entries can still be cut. Long paths/commands can wrap or truncate. Local `#` references index fresh messages, not session entry IDs; the label now explains this. The compiler's unavailable `vcc_recall` instruction is removed. -- Two recent thinking tails are limited to 400 characters before the compiler's own shortening; hidden/redacted thinking cannot be recovered. Large views may still cut earlier retained thinking. -- First/reset views can repeat older instructions. No new deduplication or lifecycle machinery was introduced in this scoped change. - -## Validation - -Final output: `validation.txt`. `npm test`: **94/94**, 18 files including RPC. Typecheck, lint, build and diff check pass. Six focused worker-view tests added to the previous three: thinking/action order and immutability; extracted paths/blockers/arguments with omitted output notices and metadata; partial pending calls across acknowledgements; rewind/compaction reset and unknown context; oversized headers/brief preserving newest activity; omitted-result versus empty-update distinction and commit extraction. Existing flow fixture supplies the new standard context-usage API. - -During implementation, full tests caught a partial `edit` call with no arguments: the adapter now supplies an empty argument object for that incomplete call while preserving missing-result status. Two added assertions initially assumed VCC classified `write` as Created and could extract a commit from a result without its call; corrected tests to the inspected compiler semantics, not patched dependency behavior. Upstream type errors are isolated as described above. All final checks pass. - -## Remaining acceptance - -The replay supports a **better overview**, especially when raw output crowds out decisions, but not a claim that this produces better outcomes or lower total token cost. Parent-owned isolated real Herdr acceptance and independent reviewer gate remain required. Suggested UAT: trivial artifact and saved verification log; inspect the supervisor's new overview and actual artifact read; exercise a manual plan tick/edit and full ApproveGoal -> CompleteGoal sequence. Do not use the user's research panes. - -No Herdr panes, GPU jobs, supervisor sessions, user research files, old native evidence logs or human journal were modified by this task. No push performed. diff --git a/slop/reviews/vcc-view/settled-checkpoint-old.md b/slop/reviews/vcc-view/settled-checkpoint-old.md deleted file mode 100644 index 131b9f6..0000000 --- a/slop/reviews/vcc-view/settled-checkpoint-old.md +++ /dev/null @@ -1,44 +0,0 @@ -The worker stopped. - -review trigger: settled -source session: /home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl -worker model: openai-codex/gpt-6-astra -latest human direction: -but also try the flow healing one as a next goal on the list -tool calls with no result: none -tracked background work: processes: 2 (e55-fine-job802-follower, e56-recovery-job805-follower); subagents: 0; unregistered detached work is not tracked - -new worker transcript since the last acknowledged view: -tool: edit - -Successfully replaced 1 block(s) in experiments/e55_logit_diff_amplification/audit_choices.py. - -tool: edit - -Successfully replaced 1 block(s) in experiments/e55_logit_diff_amplification/test_audit_choices.py. - -tool: process - -Started process audit-rubric-template-test (proc_43e4) with pid 3380015. - -Moved the unchanged rubric to [audit_instructions.md](experiments/e55_logit_diff_amplification/audit_instructions.md). The file-roundtrip test is running; queued code is untouched. - -tool: read - -test_prepare_and_summary_file_paths (experiments.e55_logit_diff_amplification.test_audit_choices.AuditTests.test_prepare_and_summary_file_paths) ... ok - ----------------------------------------------------------------------- -Ran 1 test in 0.018s - -OK - - -tool: bash - -[main c41e0f2] Move generated audit rubric out of Python source - 4 files changed, 19 insertions(+), 12 deletions(-) - create mode 100644 experiments/e55_logit_diff_amplification/audit_instructions.md - create mode 100644 slop/audits/steering_tradeoff/audit_rubric_template_test.log - - -Test passed. Committed as `c41e0f2`; rubric wording is unchanged. diff --git a/slop/reviews/vcc-view/settled-checkpoint-vcc.md b/slop/reviews/vcc-view/settled-checkpoint-vcc.md deleted file mode 100644 index bbfeca0..0000000 --- a/slop/reviews/vcc-view/settled-checkpoint-vcc.md +++ /dev/null @@ -1,33 +0,0 @@ -The worker stopped. - -review trigger: settled -source session: /home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl -worker model: openai-codex/gpt-6-astra -latest human direction: -but also try the flow healing one as a next goal on the list -tool calls with no result: none -tracked background work: processes: 2 (e55-fine-job802-follower, e56-recovery-job805-follower); subagents: 0; unregistered detached work is not tracked - -new worker overview since the last acknowledged view (VCC algorithmic compression; local # refs index new messages; tool-result bodies omitted; inspect source for evidence): -[Files And Changes] -- Modified: experiments/e55_logit_diff_amplification/audit_choices.py, - experiments/e55_logit_diff_amplification/test_audit_choices.py -- Read: slop/audits/steering_tradeoff/audit_rubric_template_test.log - -[Commits] -- c41e0f2: Move generated audit rubric out of Python source - -[assistant] -* edit "experiments/e55_logit_diff_amplification/audit_choices.py" (#0) -* edit "experiments/e55_logit_diff_amplification/test_audit_choices.py" (#2) -* process (#4) -(thinking) Thinking: **Checking baseline byte identity** - -**Testing the rubric migration** (#6) -Moved the unchanged rubric to [audit_instructions.md](experiments/e55_logit_diff_amplification/audit_instructions.md). -The file-roundtrip test is running; queued code is untouched. (#6) -* read "slop/audits/steering_tradeoff/audit_rubric_template_test.log" (#7) -* bash "git add experiments/e55_logit_diff_amplification/audit_choices.py - experiments/e55_logit_diff_amplification/audit_instructions.md - experiments/e55_logit_diff_amplification/test_audit_choices.py && git add -f slop/audits/steering_tradeoff/a..." (#9) -Test passed. Committed as `c41e0f2`; rubric wording is unchanged. (#11) diff --git a/slop/reviews/vcc-view/validation.txt b/slop/reviews/vcc-view/validation.txt deleted file mode 100644 index aee5006..0000000 --- a/slop/reviews/vcc-view/validation.txt +++ /dev/null @@ -1,73 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 18 passed (18) - Tests 94 passed (94) - Start at 10:10:30 - Duration 3.11s (transform 3.98s, setup 0ms, import 8.30s, tests 7.14s, environment 2ms) - - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 34 files in 45ms. No fixes applied. - -> @wassname2/pi-goals@0.2.2 build -> tsc - -{ - "baseline": "8953dce", - "workerPath": "/home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T10-41-19-145Z_01a0809b-a528-7724-a514-59f3c61116a6.jsonl", - "supervisorPath": "/home/code/.pi/agent/sessions/--workspace-2026-mfv-manifold-steer--/2026-09-08T22-43-35-654Z_01a08330-e866-7004-9b7f-5efdceb2488e.jsonl", - "compiler": "@sting8k/pi-vcc@0.5.0", - "results": [ - { - "name": "pilot-preparation", - "timestamp": "2026-09-08T22:59:31.347Z", - "through": "3cb9b26f", - "branchEntries": 597, - "branchSha256": "f0da668db6266780aa3ba803e5260d4009732c74ab3276f4db55910b8d74af5a", - "oldBytes": 4961, - "vccBytes": 5356, - "oldSerializedTextBytes": 5324, - "vccSerializedTextBytes": 5462, - "envelopeBytes": 5655 - }, - { - "name": "flow-implementation", - "timestamp": "2026-09-08T23:17:08.542Z", - "through": "69943231", - "since": "54648ab4", - "branchEntries": 715, - "branchSha256": "2293e2e7158c051c3571cd50befb14900cea39314640421a5735633775af427b", - "oldBytes": 4897, - "vccBytes": 2396, - "oldSerializedTextBytes": 5109, - "vccSerializedTextBytes": 2452, - "envelopeBytes": 2646 - }, - { - "name": "settled-checkpoint", - "timestamp": "2026-09-09T00:11:07.924Z", - "through": "e266d41e", - "since": "18c6af89", - "branchEntries": 981, - "branchSha256": "3460caf8d1a093309ad2639decbc181151f27871c42ffa62ca98803aae726ce7", - "oldBytes": 1676, - "vccBytes": 1840, - "oldSerializedTextBytes": 1721, - "vccSerializedTextBytes": 1882, - "envelopeBytes": 2078 - } - ] -} -PASS: three identical historical branch/ack windows, serialized bounds, source and built compiler execution agree. diff --git a/slop/test-logs/feedback-fixes-full.log b/slop/test-logs/feedback-fixes-full.log deleted file mode 100644 index 22f7dfd..0000000 --- a/slop/test-logs/feedback-fixes-full.log +++ /dev/null @@ -1,13 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 23 passed (23) - Tests 174 passed (174) - Start at 18:09:15 - Duration 5.31s (transform 7.14s, setup 0ms, import 12.81s, tests 16.70s, environment 3ms) - diff --git a/slop/test-logs/feedback-fixes-lint.log b/slop/test-logs/feedback-fixes-lint.log deleted file mode 100644 index 750fafa..0000000 --- a/slop/test-logs/feedback-fixes-lint.log +++ /dev/null @@ -1,5 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 43 files in 49ms. No fixes applied. diff --git a/slop/test-logs/feedback-fixes-typecheck.log b/slop/test-logs/feedback-fixes-typecheck.log deleted file mode 100644 index 83f64fc..0000000 --- a/slop/test-logs/feedback-fixes-typecheck.log +++ /dev/null @@ -1,4 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - diff --git a/slop/test-logs/final-review-fixes-full.log b/slop/test-logs/final-review-fixes-full.log deleted file mode 100644 index 275c8ab..0000000 --- a/slop/test-logs/final-review-fixes-full.log +++ /dev/null @@ -1,13 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 test -> vitest run - - - RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals - - - Test Files 23 passed (23) - Tests 162 passed (162) - Start at 13:25:21 - Duration 5.32s (transform 5.34s, setup 0ms, import 10.79s, tests 16.11s, environment 4ms) - diff --git a/slop/test-logs/final-review-fixes-lint.log b/slop/test-logs/final-review-fixes-lint.log deleted file mode 100644 index 60c8285..0000000 --- a/slop/test-logs/final-review-fixes-lint.log +++ /dev/null @@ -1,5 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 lint -> biome check src/ test/ - -Checked 43 files in 41ms. No fixes applied. diff --git a/slop/test-logs/final-review-fixes-typecheck.log b/slop/test-logs/final-review-fixes-typecheck.log deleted file mode 100644 index 83f64fc..0000000 --- a/slop/test-logs/final-review-fixes-typecheck.log +++ /dev/null @@ -1,4 +0,0 @@ - -> @wassname2/pi-goals@0.2.2 typecheck -> tsc --noEmit - diff --git a/src/approval.ts b/src/approval.ts deleted file mode 100644 index 418956e..0000000 --- a/src/approval.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { createHash } from "node:crypto"; -import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, readSync, renameSync, rmSync, type Stats, statSync, writeFileSync } from "node:fs"; -import { dirname, join, relative, resolve } from "node:path"; - -import { foldPlan, GOAL_LINE } from "./plan.js"; - -export interface WorktreeSnapshot { - status: string; - indexHash: string; - files: Array<{ path: string; kind: "file" | "symlink" | "missing"; mode?: number; contentHash?: string }>; -} - -export interface ApprovalRecord { - version: 3; - verdict: "accept"; - approvalId: string; - goal: string; - planPath: string; - goalBlockHash: string; - repoRoot: string; - head: string; - tree: string; - cleanWorktree: boolean; - force?: { reason: string; worktree: WorktreeSnapshot }; - inspected: { plan: true; repository: true; evidence: true; verifyOutput: true }; - verifyOutputPath: string; - supervisor: { sessionId: string; runId: string | null }; - timestamp: string; -} - -function command(repoRoot: string, args: string[]): string { - return execFileSync("git", args, { cwd: repoRoot, encoding: "utf8" }).trim(); -} - -// Capture bytes, not status flags: an edited file can change again while Git still reports M or ??. -function worktreeSnapshot(repoRoot: string, status: string, pathspec: string[]): WorktreeSnapshot { - const index = execFileSync("git", ["ls-files", "--stage", "-z", "--", ...pathspec], { cwd: repoRoot }); - const paths = status.split("\0").filter(Boolean).map(entry => entry.slice(3)).sort(); - const files = paths.map((path): WorktreeSnapshot["files"][number] => { - const fullPath = join(repoRoot, path); - let stat: Stats; - try { stat = lstatSync(fullPath); } - catch (error) { - if (["ENOENT", "ENOTDIR"].includes((error as NodeJS.ErrnoException).code ?? "")) return { path, kind: "missing" }; - throw error; - } - const mode = stat.mode & 0o777; - const hash = createHash("sha256"); - if (stat.isSymbolicLink()) return { path, kind: "symlink", mode, contentHash: hash.update(readlinkSync(fullPath, { encoding: "buffer" })).digest("hex") }; - if (!stat.isFile()) throw new Error(`Cannot fingerprint dirty path ${path}: only regular files and symlinks are supported.`); - const fd = openSync(fullPath, "r"); - try { - const buffer = Buffer.alloc(256 * 1024); - for (;;) { - const bytes = readSync(fd, buffer, 0, buffer.length, null); - if (!bytes) break; - hash.update(buffer.subarray(0, bytes)); - } - } finally { closeSync(fd); } - return { path, kind: "file", mode, contentHash: hash.digest("hex") }; - }); - return { status, indexHash: createHash("sha256").update(index).digest("hex"), files }; -} - -export function repositoryState(cwd: string, captureWorktree = false): { repoRoot: string; head: string; tree: string; cleanWorktree: boolean; worktree?: WorktreeSnapshot } { - const repoRoot = command(cwd, ["rev-parse", "--show-toplevel"]); - const head = command(repoRoot, ["rev-parse", "HEAD"]); - const tree = command(repoRoot, ["rev-parse", "HEAD^{tree}"]); - const prefix = relative(repoRoot, resolve(cwd)).replaceAll("\\", "/"); - const owned = prefix ? `${prefix}/.pi` : ".pi"; - const pathspec = [".", - `:(exclude,glob)${owned}/plan/*.md`, - `:(exclude,glob)${owned}/pi-goals/approvals/*`, - `:(exclude,glob)${owned}/pi-goals/models/*`, - ]; - // NUL delimiters and no rename folding preserve whitespace/newlines and both sides of renames. - const raw = execFileSync("git", ["status", "--porcelain=v1", "-z", "--no-renames", ...(captureWorktree ? ["--ignore-submodules=none"] : []), "--untracked-files=all", "--", ...pathspec], { cwd: repoRoot }); - const status = raw.toString("utf8"); - if (captureWorktree && !raw.equals(Buffer.from(status))) throw new Error("Cannot fingerprint non-UTF-8 Git paths."); - return { repoRoot, head, tree, cleanWorktree: status === "", ...(captureWorktree ? { worktree: worktreeSnapshot(repoRoot, status, pathspec) } : {}) }; -} - -export function goalBlock(plan: string, goal: string): string | null { - const lines = foldPlan(plan).split("\n"); - const wanted = goal.trim().toLowerCase(); - const hits = lines.flatMap((line, index) => { - const match = GOAL_LINE.exec(line); - return match && (match[1] === " " || match[1] === "/") && match[2].trim().toLowerCase() === wanted ? [index] : []; - }); - if (hits.length !== 1) return null; - const start = hits[0]; - let end = lines.length; - for (let index = start + 1; index < lines.length; index++) { - if (GOAL_LINE.test(lines[index]) || /^#{1,2}\s/.test(lines[index])) { - end = index; - break; - } - } - return lines.slice(start, end).join("\n").trimEnd(); -} - -export function hashGoalBlock(block: string): string { - return createHash("sha256").update(block).digest("hex"); -} - -export function verifyOutputPath(repoRoot: string, path: string): string | null { - const resolved = resolve(repoRoot, path); - const relativePath = relative(repoRoot, resolved).replaceAll("\\", "/"); - if (!relativePath || relativePath.startsWith("../") || relativePath === "..") return null; - try { - const output = statSync(resolved); - if (!output.isFile() || output.size === 0) return null; - command(repoRoot, ["ls-files", "--error-unmatch", "--", relativePath]); - return relativePath; - } catch { - return null; - } -} - -export function approvalPath(cwd: string, sessionId: string, goal: string): string { - const goalId = createHash("sha256").update(goal.trim().toLowerCase()).digest("hex").slice(0, 16); - return join(cwd, ".pi", "pi-goals", "approvals", `${sessionId}-${goalId}.json`); -} - -export function writeApproval(path: string, record: ApprovalRecord): void { - mkdirSync(dirname(path), { recursive: true }); - const temporary = `${path}.${process.pid}.tmp`; - try { - writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`); - renameSync(temporary, path); - } finally { - if (existsSync(temporary)) rmSync(temporary, { force: true }); - } -} - -export function readApproval(path: string): ApprovalRecord | null { - if (!existsSync(path)) return null; - try { - return JSON.parse(readFileSync(path, "utf8")) as ApprovalRecord; - } catch { - return null; - } -} - -export function approvalMatches(record: ApprovalRecord | null, input: { - approvalId: string; - goal: string; - planPath: string; - goalBlockHash: string; - repoRoot: string; - head: string; - tree: string; - cleanWorktree: boolean; - worktree?: WorktreeSnapshot; -}): boolean { - return record?.version === 3 - && record.verdict === "accept" - && record.approvalId === input.approvalId - && record.goal === input.goal - && resolve(record.planPath) === resolve(input.planPath) - && record.goalBlockHash === input.goalBlockHash - && resolve(record.repoRoot) === resolve(input.repoRoot) - && record.head === input.head - && record.tree === input.tree - && (record.force - ? Boolean(record.force.reason?.trim()) && Boolean(record.force.worktree) && Boolean(input.worktree) - && record.cleanWorktree === input.cleanWorktree - && JSON.stringify(record.force.worktree) === JSON.stringify(input.worktree) - : record.cleanWorktree === true && input.cleanWorktree) - && record.inspected.plan === true - && record.inspected.repository === true - && record.inspected.evidence === true - && record.inspected.verifyOutput === true - && Boolean(record.verifyOutputPath); -} diff --git a/src/background.ts b/src/background.ts deleted file mode 100644 index 56baf27..0000000 --- a/src/background.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { randomUUID } from "node:crypto"; -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; - -// Pi/OpenAI: Adapted from this repo's feature/simple-visible-supervision at cecb1e9. -export async function backgroundState(pi: ExtensionAPI): Promise<{ quiet: boolean; description: string }> { - const tools = pi.getAllTools(); - const hasProcesses = tools.some(tool => tool.name === "process"); - const hasSubagents = tools.some(tool => tool.name === "subagent"); - let processes: unknown; - pi.events.emit("processes:request:list", { reply: (value: unknown) => { processes = value; } }); - const rows = Array.isArray(processes) ? processes : !hasProcesses && processes === undefined ? [] : null; - const known = rows?.every(p => p && ["running", "terminating", "terminate_timeout", "exited", "killed"].includes(p.status)); - const activeProcesses = known ? rows!.filter(p => !["exited", "killed"].includes(p.status)) : null; - let subagents: number | null = hasSubagents ? null : 0; - if (hasSubagents) { - const requestId = randomUUID(); - subagents = await new Promise<number | null>(resolve => { - let unsubscribe: (() => void) | undefined; - const finish = (value: number | null) => { clearTimeout(timer); unsubscribe?.(); resolve(value); }; - const timer = setTimeout(() => finish(null), 2000); - unsubscribe = pi.events.on(`subagents:rpc:v1:reply:${requestId}`, (reply: any) => { - if (reply?.requestId !== requestId) return; - const count = reply?.success && reply?.data?.fleet?.version === 1 ? reply.data.fleet.totalActive : undefined; - finish(Number.isSafeInteger(count) && count >= 0 ? count : null); - }); - pi.events.emit("subagents:rpc:v1:request", { version: 1, requestId, method: "status", params: {}, source: { extension: "pi-goals" } }); - }); - } - return { - quiet: activeProcesses?.length === 0 && subagents === 0, - description: `processes: ${activeProcesses?.length ?? "unknown"}${activeProcesses?.length ? ` (${activeProcesses.map(p => p.name || p.id).join(", ")})` : ""}; subagents: ${subagents ?? "unknown"}; unregistered detached work is not tracked`, - }; -} diff --git a/src/herdr.ts b/src/herdr.ts deleted file mode 100644 index edce840..0000000 --- a/src/herdr.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; - -const execFileAsync = promisify(execFile); - -interface LaunchSupervisorInput { - cwd: string; - sourceSessionFile: string; - workerSessionId: string; - planPath: string; - approvalId: string; - extensionPath: string; - model: string | null; -} - -function shellQuote(value: string): string { - return `'${value.replaceAll("'", "'\\''")}'`; -} - -function findPaneId(value: unknown): string | null { - if (!value || typeof value !== "object") return null; - const record = value as Record<string, unknown>; - for (const key of ["pane_id", "paneId"]) { - if (typeof record[key] === "string") return record[key]; - } - for (const child of Object.values(record)) { - const found = findPaneId(child); - if (found) return found; - } - return null; -} - -async function herdr(args: string[], json = true): Promise<unknown> { - const bin = process.env.HERDR_BIN_PATH ?? "herdr"; - const { stdout } = await execFileAsync(bin, args, { encoding: "utf8", timeout: 15_000 }); - if (!json) return stdout.trim(); - return stdout.trim() ? JSON.parse(stdout) : {}; -} - -function stalePaneError(error: unknown): boolean { - const record = error as { stdout?: unknown; stderr?: unknown; message?: unknown }; - const text = [record.stdout, record.stderr, record.message].filter((value): value is string => typeof value === "string").join("\n"); - return /\b(?:NOT_FOUND|PANE_GONE|PANE_NOT_FOUND)\b/i.test(text); -} - -export function supervisorCommand(input: LaunchSupervisorInput): string { - const env = [ - "PI_GOALS_ROLE=supervisor", - `PI_GOALS_MODEL_EXPLICIT=${input.model ? "1" : "0"}`, - `PI_GOALS_WORKER_ID=${input.workerSessionId}`, - `PI_GOALS_PLAN_PATH=${input.planPath}`, - `PI_GOALS_APPROVAL_ID=${input.approvalId}`, - `PI_GOALS_OWNER_SESSION_ID=${input.workerSessionId}`, - ]; - const args = [ - "pi", - "-e", input.extensionPath, - "--fork", input.sourceSessionFile, - "--name", `goals-supervisor-${input.workerSessionId.slice(0, 8)}`, - ]; - if (input.model) args.push("--model", input.model); - return `env ${[...env, ...args].map(shellQuote).join(" ")}`; -} - -export async function openSupervisorPane(input: LaunchSupervisorInput, onOpened: (paneId: string) => void): Promise<string> { - if (process.env.HERDR_ENV !== "1") throw new Error("Ready needs a Herdr session so pi-goals can open the supervisor session."); - await herdr(["--version"], false); - const split = await herdr(["pane", "split", "--current", "--direction", "right", "--cwd", input.cwd, "--no-focus"]); - const paneId = findPaneId(split); - if (!paneId) throw new Error("Herdr did not return the new supervisor pane ID."); - onOpened(paneId); - await herdr(["pane", "run", paneId, supervisorCommand(input)]); - return paneId; -} - -export async function closeSupervisorPane(paneId: string): Promise<void> { - try { - await herdr(["pane", "close", paneId]); - } catch (error) { - if (stalePaneError(error)) return; - throw error; - } -} diff --git a/src/index.ts b/src/index.ts index 2cd1cc9..5bfb7d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,842 +1,437 @@ -/** - * PI: pi-goals owns one versioned plan per session. After Ready, the main session implements the - * plan while a compacted, visible fork supervises it through pi-intercom. - * - * Each /goals call makes `.pi/plan/<session_id>-vN.md`. The selected version survives resume and - * compaction. Old plans stay on disk but inactive. A session with no selected plan has no widget, - * supervision, or CompleteGoal sign-off. - * - * TypeScript reads only goal checkbox lines for the widget. Models read the plan as prose. The - * worker edits the project and records evidence. The supervisor inspects it and writes a private - * approval checkpoint. - * - * -- Pi/Codex - */ - -import { execFileSync } from "node:child_process"; -import { randomUUID } from "node:crypto"; -import { existsSync, type FSWatcher, mkdirSync, readdirSync, readFileSync, rmSync, watch, writeFileSync } from "node:fs"; -import { join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; +// Pi/OpenAI: Plan and supervise in the main chat; delegate implementation to a visible worker. +import { createHash } from "node:crypto"; +import { type FSWatcher, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs"; +import { dirname, isAbsolute, join, resolve } from "node:path"; import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { Type } from "typebox"; -import { approvalMatches, approvalPath, goalBlock, hashGoalBlock, readApproval, repositoryState } from "./approval.js"; -import { backgroundState } from "./background.js"; -import { closeSupervisorPane, openSupervisorPane } from "./herdr.js"; -import { GoalIntercom } from "./intercom.js"; -import { FOLD_LINE, foldPlan, GOAL_LINE } from "./plan.js"; -import { completeGoalDescription, completeGoalParamDescription, planDrafting, planningState, resync, supervisorPlanReview } from "./prompts.js"; -import { RoleModels } from "./role-models.js"; -import { isVisibleSupervisor, registerVisibleSupervisor, restoredSupervisor } from "./supervisor-session.js"; -import { workerView } from "./worker-view.js"; +import { foldPlan, GOAL_LINE } from "./plan.js"; +import { planViews } from "./plan-view.js"; +import { + attachGoalPlanDescription, + attachNotice, + childPlanAttached, + childPlanRole, + completeGoalDescription, + completionLog, + completionResult, + discuss, + emptyEvidence, + evidenceUnavailable, + goalToolBlocked, + manualReview, + messages, + pausedRole, + pauseExitNotice, + planChangedReview, + planContext, + planDocument, + planning, + planningSeed, + planUnavailable, + readyApproved, + removeGoalSchedule, + resumeNotice, + scheduleCheckIn, + soloNotice, + soloRole, + supervisor, + upkeep, +} from "./prompts.js"; -export { foldPlan } from "./plan.js"; - -const STATE = "pi-goals-state"; -const STATUS_KEY = "pi-goals"; -const WIDGET_KEY = "pi-goals-widget"; -const PLANNING_CONTEXT = "pi-goals-planning-context"; -const PLAN_DIR = ".pi/plan"; -// For static text (the /goals description) where there is no ctx to resolve the session id. -const PLAN_SHAPE = `${PLAN_DIR}/<session_id>-vN.md`; -// Plan mode blocks edit/write except for its plan file. bash remains available for read-only inspection. -- Pi/Codex -const PLAN_MODE_BLOCKED_TOOLS = ["edit", "write"]; - -// An indented checkbox line that isn't a goal: a subtask. Only the widget reads these, so the human -// sees the next action and not just the goal -- this file IS the task list. -const SUBTASK_LINE = /^\s+(?:\d+\.|[-*])\s*\[([ xX/-])\]\s*(.*)$/; +const STATE = "pi-goals-main-supervisor-v1"; +const WORKER = "goals-worker"; +type Mode = "chat" | "planning" | "supervising" | "paused" | "solo"; type GoalStatus = "open" | "active" | "done" | "cancelled"; -const CHAR_TO_STATUS: Record<string, GoalStatus> = { " ": "open", "/": "active", x: "done", "-": "cancelled" }; -const STATUS_TO_CHAR: Record<GoalStatus, string> = { open: " ", active: "/", done: "x", cancelled: "-" }; -const goalKey = (subject: string) => subject.trim().toLowerCase(); - -function scanGoals(plan: string): Array<{ status: GoalStatus; subject: string; line: number }> { - const goals: Array<{ status: GoalStatus; subject: string; line: number }> = []; - foldPlan(plan).split("\n").forEach((line, i) => { - const m = GOAL_LINE.exec(line); - if (m) goals.push({ status: CHAR_TO_STATUS[m[1].toLowerCase()] ?? "open", subject: m[2].trim(), line: i }); - }); - return goals; +interface State { + mode: Mode; + plan?: string; + worker?: { id?: string; sessionFile: string }; + workerStopped?: boolean; + signoffs: Record<string, { evidence: string[]; observation: string }>; + child?: boolean; } - -/** Open subtasks under the goal on line `goalLine`, up to the next goal line. */ -export function openSubtasks(plan: string, goalLine: number): string[] { - const lines = foldPlan(plan).split("\n"); - const out: string[] = []; - for (let i = goalLine + 1; i < lines.length; i++) { - if (GOAL_LINE.test(lines[i])) break; - const m = SUBTASK_LINE.exec(lines[i]); - if (m && (m[1] === " " || m[1] === "/")) out.push(m[2].trim()); - } - return out; -} - -export function nextPlanVersion(planNames: string[], sessionId: string): number { - const prefix = `${sessionId}-v`; - const versions = planNames.flatMap((name) => { - if (!name.startsWith(prefix) || !name.endsWith(".md")) return []; - const version = Number(name.slice(prefix.length, -".md".length)); - return Number.isInteger(version) && version > 0 ? [version] : []; - }); - return Math.max(0, ...versions) + 1; -} - -type Phase = "planning" | "working" | null; - -export function isMainSession(isSubagentChild = process.env.PI_SUBAGENT_CHILD === "1"): boolean { - return !isSubagentChild && !isVisibleSupervisor(); -} - -interface PlanState { - phase: Phase; - supervisorModel: string | null; - supervisorPaneId: string | null; - approvalId: string | null; - planVersion: number | null; - latestDirection: string; - signedOffGoals: string[]; - previousPlan: string | null; -} - -export default function piGoalsExtension(pi: ExtensionAPI): void { - let initialized = false; - pi.on("session_start", async (_event, ctx) => { - if (initialized) return; - initialized = true; - const saved = restoredSupervisor(ctx.sessionManager.getEntries()); - if (saved || isVisibleSupervisor()) registerVisibleSupervisor(pi, saved); - else if (isMainSession()) registerWorker(pi); - // Pi's dispatcher iterates its live handler list, including those just registered. +const initial = (): State => ({ mode: "chat", signoffs: {} }); +const digest = (text: string) => createHash("sha256").update(text).digest("hex"); +const key = (text: string) => text.trim().toLowerCase(); +function goals(text: string) { + return foldPlan(text).split("\n").flatMap((line, index) => { + const match = GOAL_LINE.exec(line); + if (!match) return []; + const box = match[1].toLowerCase(); + return [{ subject: match[2].trim(), status: (box === "x" ? "done" : box === "/" ? "active" : box === "-" ? "cancelled" : "open") as GoalStatus, index }]; }); } +const result = (text: string) => ({ content: [{ type: "text" as const, text }], details: {} }); -export function registerWorker(pi: ExtensionAPI): void { - const intercom = new GoalIntercom(pi); - const models = new RoleModels(pi); - intercom.onSteer = (instruction) => { - if (state.phase !== "working" || modelError) throw new Error("Worker is paused or its plan is not active; instruction not delivered. Use /goals reconnect after selecting an available model."); - pi.sendUserMessage(`[supervisor] ${instruction}`, { deliverAs: "steer" }); - }; - let state: PlanState = { - phase: null, - supervisorModel: null, - supervisorPaneId: null, - approvalId: null, - planVersion: null, - latestDirection: "", - signedOffGoals: [], - previousPlan: null, - }; - let modelError: string | null = null; - let readyAttempt: object | undefined; - let wasConnected = false; - intercom.onConnectionChange = (ctx) => { - const connected = intercom.connected; - const rejoined = connected && !wasConnected; - wasConnected = connected; - updateWidget(ctx); - // Ready publishes its own first view. Subsequent rejoins need a new ID even if the old view was accepted. - if (rejoined && !readyAttempt && state.phase === "working" && !modelError) { - void publishWorkerView(ctx, "settled").catch(error => { if (!intercom.ended) ctx.ui.notify(`Recovery view failed: ${String(error)}`, "error"); }); - } - }; - let planningContextPending = false; - let resyncReason: string | null = "New session."; - - const planRel = (ctx: ExtensionContext) => (state.planVersion === null ? PLAN_SHAPE : `${PLAN_DIR}/${ctx.sessionManager.getSessionId()}-v${state.planVersion}.md`); - const planPath = (ctx: ExtensionContext) => { - if (state.planVersion === null) throw new Error("No active plan version."); - return join(ctx.cwd, planRel(ctx)); - }; - const readPlan = (ctx: ExtensionContext): string => (state.planVersion !== null && existsSync(planPath(ctx)) ? readFileSync(planPath(ctx), "utf-8") : ""); - const writePlan = (ctx: ExtensionContext, content: string): void => { - mkdirSync(join(ctx.cwd, PLAN_DIR), { recursive: true }); - writeFileSync(planPath(ctx), content); - }; - const nextVersion = (ctx: ExtensionContext): number => - nextPlanVersion(existsSync(join(ctx.cwd, PLAN_DIR)) ? readdirSync(join(ctx.cwd, PLAN_DIR)) : [], ctx.sessionManager.getSessionId()); - - function persist(): void { - pi.appendEntry<PlanState>(STATE, state); - } - - // Only CompleteGoal adds sign-off; direct edits remain claims for supervisor judgment. - function refreshSignoffs(ctx: ExtensionContext): void { - if (state.phase !== "working") return; - const goals = scanGoals(readPlan(ctx)); - const signedOffGoals = state.signedOffGoals.filter(subject => { - const matches = goals.filter(goal => goalKey(goal.subject) === subject); - return matches.length === 1 && matches[0].status === "done"; - }); - if (signedOffGoals.length !== state.signedOffGoals.length) { - state = { ...state, signedOffGoals }; - persist(); - } - } - - function planReview(plan: string): string { - const goals = scanGoals(plan); - const previous = scanGoals(state.previousPlan ?? ""); - const changes = goals.flatMap(goal => { - const old = previous.find(prior => goalKey(prior.subject) === goalKey(goal.subject)); - return old?.status === goal.status ? [] : [`${goal.subject}: ${old ? `[${STATUS_TO_CHAR[old.status]}]` : "not previously observed"} -> [${STATUS_TO_CHAR[goal.status]}]${goal.status === "done" ? state.signedOffGoals.includes(goalKey(goal.subject)) ? "; CompleteGoal sign-off recorded" : "; manual completion claim, no CompleteGoal sign-off recorded" : ""}`]; - }); - const claims = goals.filter(goal => goal.status === "done" && !state.signedOffGoals.includes(goalKey(goal.subject))); - return supervisorPlanReview(claims.map(goal => goal.subject), changes, planDiff(state.previousPlan ?? "", plan)); - } - - function pauseReason(): string | null { - if (!state.phase) return null; - if (modelError) return `${modelError} Select /model, then run /goals reconnect.`; - if (state.phase === "working" && !intercom.connected) return intercom.peerPresent - ? "Supervisor is present but not ready. Inspect its pane for startup/compaction or model errors; recover with /model then /goals reconnect in the supervisor pane if needed." - : "Supervisor disconnected. Run /goals reconnect, or /goals restart to replace its tracked pane without discarding the plan."; - return null; - } - - async function restoreModel(role: "planning" | "worker", ctx: ExtensionContext): Promise<void> { - modelError = `${role} model restoration is pending.`; - intercom.markNotReady(); - try { - await models.enter(role, ctx); - modelError = null; - } catch (error) { - modelError = error instanceof Error ? error.message : String(error); - throw error; - } - } - - function beginReview(ctx: ExtensionContext): void { - for (const goal of scanGoals(readPlan(ctx))) { - rmSync(approvalPath(ctx.cwd, ctx.sessionManager.getSessionId(), goal.subject), { force: true }); - } - const approvalId = randomUUID(); - state = { ...state, approvalId }; - intercom.configure(approvalId, "worker", ctx, false); - persist(); - } - - function repositoryRoot(cwd: string): string { - return execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" }).trim(); - } - - async function startSupervisor(ctx: ExtensionContext, isCurrent = () => !intercom.ended): Promise<void> { - if (intercom.ended) throw new Error("Session ended before supervisor startup."); - repositoryRoot(ctx.cwd); - const sourceSessionFile = ctx.sessionManager.getSessionFile(); - if (!sourceSessionFile) throw new Error("The current session is not persisted, so it cannot be forked."); - if (state.supervisorPaneId && state.approvalId) { - intercom.configure(state.approvalId, "worker", ctx, false); - await intercom.waitReady(undefined, { peerOnly: true }); - return; - } - beginReview(ctx); - const binding = state.approvalId; - const current = () => isCurrent() && !intercom.ended && state.approvalId === binding; - let paneId: string | null = null; - try { - paneId = await openSupervisorPane({ - cwd: ctx.cwd, - sourceSessionFile, - workerSessionId: ctx.sessionManager.getSessionId(), - planPath: planPath(ctx), - approvalId: state.approvalId!, - extensionPath: fileURLToPath(import.meta.url), - model: state.supervisorModel, - }, (opened) => { - if (!current()) throw new Error("Supervisor startup was cancelled."); - paneId = opened; - state = { ...state, supervisorPaneId: opened }; - persist(); - }); - } catch (error) { - if (paneId) throw new Error(`Supervisor startup failed in Herdr pane ${paneId}; it remains open for inspection. ${error instanceof Error ? error.message : String(error)}`); - throw error; - } - if (!current()) throw new Error("Supervisor startup was cancelled."); - state = { ...state, supervisorPaneId: paneId }; - persist(); - await intercom.waitReady(undefined, { peerOnly: true }); - } - - let workerTurns = 0; - let viewGeneration = 0; - let viewTimer: ReturnType<typeof setInterval> | undefined; +export default function mainSupervisor(pi: ExtensionAPI) { + let state = initial(); + let generation = 0; + let workerRevision = 0; + let launchPending = false; + let notice = true; let planWatcher: FSWatcher | undefined; let planEditTimer: ReturnType<typeof setTimeout> | undefined; - - async function publishWorkerView(ctx: ExtensionContext, reason: "ready" | "settled" | "turns" | "interval" | "started" | "plan"): Promise<void> { - if (state.phase !== "working" || modelError || !intercom.bound) return; - const generation = ++viewGeneration; - const binding = state.approvalId; - const background = reason === "started" ? { quiet: false, description: "agent starting; background state not queried" } : await backgroundState(pi); - if (!intercom.bound || modelError || generation !== viewGeneration || binding !== state.approvalId || state.phase !== "working") return; - refreshSignoffs(ctx); - const plan = readPlan(ctx); - const entries = ctx.sessionManager.getBranch(); - const view = workerView(entries, reason, reason !== "started" && ctx.isIdle(), { - sourceSession: ctx.sessionManager.getSessionFile()!, latestDirection: state.latestDirection, - model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "not selected", - contextPercent: ctx.getContextUsage()?.percent, - since: intercom.acknowledgedEntry, background: background.description, - planReview: `Plan: ${planRel(ctx)}\n${planReview(plan)}`, - }); - intercom.view(view, reason, entries.at(-1)?.id, background.quiet); - if (reason !== "started" && intercom.connected && state.previousPlan !== plan) { - state = { ...state, previousPlan: plan }; - persist(); + let planHash = ""; + const childEnvironment = process.env.PI_SUBAGENT_AGENT === WORKER; + const save = () => pi.appendEntry(STATE, state); + // Missing, empty and failed reads are unavailable snapshots, never an empty authoritative plan. + const readPlan = () => { + try { + if (!state.plan) throw new Error(messages.noPlan); + const text = readFileSync(state.plan, "utf8"); + if (!text.trim()) throw new Error(messages.emptyPlan); + return { text }; + } catch (error) { return { error: planUnavailable(state.plan, error) }; } + }; + const planText = () => { + const snapshot = readPlan(); + if (snapshot.text === undefined) throw new Error(snapshot.error); + return snapshot.text; + }; + let turnsStale = 0; + let lastWorkingSet = ""; + const checkIn = (ctx: ExtensionContext) => scheduleCheckIn(ctx.sessionManager.getSessionId(), state.plan ?? ""); + const hasScheduleTool = () => pi.getAllTools().some((tool) => tool.name === "schedule_prompt"); + const notedPlanValue = (prefix: string) => { + const snapshot = readPlan(); + if (snapshot.text === undefined) return null; + const m = new RegExp(`^\\-\\s*${prefix}:\\s*(.+)$`, "im").exec(foldPlan(snapshot.text)); + return m?.[1]?.trim() ?? null; + }; + function refresh(ctx: ExtensionContext) { + if (state.mode === "chat") { ctx.ui.setStatus("goals", undefined); ctx.ui.setWidget("goals", undefined); return; } + const snapshot = readPlan(); + if (snapshot.text === undefined) { + ctx.ui.setStatus("goals", snapshot.error); + ctx.ui.setWidget("goals", [snapshot.error]); + return; } - const goals = scanGoals(plan); - if (goals.length > 0 && goals.every((goal) => (goal.status === "done" && state.signedOffGoals.includes(goalKey(goal.subject))) || goal.status === "cancelled")) { - stopWorkerTimers(); - state = { ...state, phase: null }; - models.leave(); - persist(); - intercom.detach(); + const items = goals(snapshot.text); + // Reopened/deleted/ambiguous goal identities lose their sign-off. Manual ticks remain claims. + for (const subject of Object.keys(state.signoffs)) { + const matches = items.filter((g) => key(g.subject) === subject); + if (matches.length !== 1 || matches[0].status !== "done") { delete state.signoffs[subject]; save(); } } + const accepted = items.filter((g) => g.status === "done" && state.signoffs[key(g.subject)]).length; + ctx.ui.setStatus("goals", `goals: ${state.child ? "worker" : state.mode} | ${accepted}/${items.length} reviewed`); + const mark = (status: GoalStatus, signed: boolean) => status === "done" ? (signed ? "✓" : "?") : status === "active" ? "▸" : status === "cancelled" ? "✗" : "○"; + const lines: string[] = items.map((g) => `${mark(g.status, Boolean(state.signoffs[key(g.subject)]))} ${g.subject}`); + if (items.some((g) => g.status === "done" && !state.signoffs[key(g.subject)])) lines.push("? = completion claim; parent review still required"); + ctx.ui.setWidget("goals", lines); } - - function startWorkerTimers(ctx: ExtensionContext): void { - if (!planWatcher) { - const activePath = planPath(ctx); - try { - // Watch the containing directory so atomic replacement does not lose the file watch. - planWatcher = watch(join(ctx.cwd, PLAN_DIR), (_event, filename) => { - if (intercom.ended || state.phase !== "working") return; - if (filename && join(ctx.cwd, PLAN_DIR, filename.toString()) !== activePath) return; - if (planEditTimer) clearTimeout(planEditTimer); - planEditTimer = setTimeout(() => { - planEditTimer = undefined; - if (intercom.ended || state.phase !== "working" || planPath(ctx) !== activePath) return; - updateWidget(ctx); - // Working edits coalesce into the existing settled view; idle edits wake review now. - if (ctx.isIdle() && readPlan(ctx) !== state.previousPlan) { - void publishWorkerView(ctx, "plan").catch(error => { if (!intercom.ended) ctx.ui.notify(`Plan review failed: ${String(error)}`, "error"); }); - } - }, 150); - }); - planWatcher.on("error", error => { if (!intercom.ended) ctx.ui.notify(`Plan watch failed: ${error.message}`, "error"); }); - } catch (error) { ctx.ui.notify(`Could not watch active plan: ${String(error)}`, "warning"); } - } - if (!viewTimer) viewTimer = setInterval(() => { - void publishWorkerView(ctx, "interval").catch(error => { if (!intercom.ended) ctx.ui.notify(`Worker view failed: ${String(error)}`, "error"); }); - }, 60 * 60_000); - } - - function stopWorkerTimers(): void { + function watchPlan(ctx: ExtensionContext) { planWatcher?.close(); planWatcher = undefined; - if (planEditTimer) clearTimeout(planEditTimer); + clearTimeout(planEditTimer); planEditTimer = undefined; - if (viewTimer) clearInterval(viewTimer); - viewTimer = undefined; - } - - async function stopSupervisor(): Promise<boolean> { - readyAttempt = undefined; - if (!state.supervisorPaneId) { stopWorkerTimers(); intercom.detach(); return true; } + const snapshot = readPlan(); + if (snapshot.text !== undefined) planHash = digest(planViews(snapshot.text).short); + if (state.child || state.mode !== "supervising" || !state.plan) return; + const stamp = generation; + // Watch the directory so atomic plan replacement remains observable. This is an event hook: + // plan-change reviews, not another scheduled loop (the hourly job is schedule_prompt's). A + // short debounce coalesces bursts. Existing high-level plan views exclude maintenance + // (tasks/evidence/Log) while preserving requirement wording and goal checkbox claims. try { - await closeSupervisorPane(state.supervisorPaneId); - stopWorkerTimers(); - intercom.detach(); - state = { ...state, supervisorPaneId: null }; - persist(); - return true; - } catch { - return false; - } + planWatcher = watch(dirname(state.plan), { persistent: false }, () => { + if (stamp !== generation) return; + if (planEditTimer) clearTimeout(planEditTimer); + planEditTimer = setTimeout(() => { + planEditTimer = undefined; + if (stamp !== generation || state.mode !== "supervising") return; + const snapshot = readPlan(); + if (snapshot.text === undefined) { ctx.ui.notify(snapshot.error!, "warning"); return; } + refresh(ctx); + const hash = digest(planViews(snapshot.text).short); + if (hash === planHash) return; + planHash = hash; + notice = true; + send(planChangedReview(state.plan!)); + }, 150); + }); + planWatcher.on("error", (error) => { planWatcher?.close(); planWatcher = undefined; ctx.ui.notify(`Plan monitoring failed: ${error.message}`, "error"); }); + } catch (error) { ctx.ui.notify(`Plan monitoring unavailable: ${String(error)}`, "error"); } } - - function updateWidget(ctx: ExtensionContext): void { - refreshSignoffs(ctx); - const paused = pauseReason(); - if (paused) { - ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("warning", "goals paused")); - ctx.ui.setWidget(WIDGET_KEY, [`pi-goals paused: ${paused}`]); - return; + function restore(ctx: ExtensionContext) { + generation++; + state = initial(); + for (const entry of ctx.sessionManager.getBranch()) { + if (entry.type === "custom" && entry.customType === STATE) state = structuredClone(entry.data as State); } - if (state.phase === "planning") { - ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("warning", "planning")); - ctx.ui.setWidget(WIDGET_KEY, ["pi-goals: drafting goals"]); - return; + if (childEnvironment) { + state.child = true; + state.mode = "solo"; + // Lineage-only workers attach the explicit task path using AttachGoalPlan. + save(); } - const goals = scanGoals(readPlan(ctx)); - if (goals.length === 0) { - ctx.ui.setStatus(STATUS_KEY, undefined); - ctx.ui.setWidget(WIDGET_KEY, undefined); - return; - } - const isSignedOff = (subject: string) => state.signedOffGoals.includes(goalKey(subject)); - const done = goals.filter(g => g.status === "done" && isSignedOff(g.subject)).length; - const claimed = goals.filter(g => g.status === "done" && !isSignedOff(g.subject)); - const liveGoals = goals.filter(g => g.status === "active" || g.status === "open"); - const stateLabel = claimed.length ? ` · ${claimed.length} claimed, awaiting review` : liveGoals.length > 0 ? " · supervised" : " · complete"; - ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("accent", `◷ ${done}/${goals.length} goals${stateLabel}`)); - const mark: Record<GoalStatus, string> = { done: "✔", active: "▸", open: "◻", cancelled: "✗" }; - // Only live goals get lines so finished work never pushes current work off screen. The active - // goal also shows its open subtasks: this file is the task list, so the widget is the task list. - // No path line: the session id makes it too long to be useful in the widget. - const plan = readPlan(ctx); - const lines: string[] = claimed.map(g => `? claimed complete; awaiting supervisor review: ${g.subject}`); - if (liveGoals.length === 0 && claimed.length === 0) lines.push("✔ complete"); - for (const g of liveGoals) { - lines.push(`${mark[g.status]} ${g.status === "active" ? "supervising… " : ""}${g.subject}`); - if (g.status === "active") lines.push(...openSubtasks(plan, g.line).slice(0, 3).map((s) => ctx.ui.theme.fg("muted", ` ◦ ${s}`))); - } - ctx.ui.setWidget(WIDGET_KEY, lines); + notice = true; + turnsStale = 0; + lastWorkingSet = ""; + refresh(ctx); + watchPlan(ctx); } - - // --- /goals: enter plan mode or configure supervision -- Pi/Codex ----------------------------- - - pi.registerCommand("goals", { - description: `Plan goals, then open a visible supervisor session. /goals <objective> | work | supervise | noplan | reconnect | restart | clear | model <supervisor>`, - handler: async (args, ctx) => { - let arg = args.trim(); - if (arg === "supervise") { ctx.ui.notify("This is the worker session. Run /goals supervise in the saved supervisor session; no new pairing was created.", "warning"); return; } - if (arg === "work") { - if (state.phase !== "working" || !state.approvalId || !state.supervisorPaneId) { ctx.ui.notify("No approved worker pairing to reconnect. A retained draft still needs Ready.", "warning"); return; } - arg = "reconnect"; - } - if (arg === "noplan") { - if (state.phase !== "planning") { ctx.ui.notify("Not in planning mode; the current plan is unchanged.", "info"); return; } - readyAttempt = undefined; - planningContextPending = false; - resyncReason = null; - stopWorkerTimers(); - intercom.detach(); - models.leave(); - state = { ...state, phase: null }; - persist(); - updateWidget(ctx); - ctx.ui.notify(`Planning exited. Draft preserved at ${planRel(ctx)}; no implementation was approved or started.`, "info"); - return; - } - if (arg === "reconnect" || arg === "restart") { - if (!state.phase) { ctx.ui.notify("No active plan to recover.", "info"); return; } - if (!ctx.isIdle()) { ctx.ui.notify("Stop the current turn before recovering goal supervision.", "warning"); return; } - readyAttempt = undefined; - try { - await restoreModel(state.phase === "planning" ? "planning" : "worker", ctx); - if (arg === "restart") { - if (!(await stopSupervisor())) throw new Error("Could not close the tracked supervisor pane; no replacement was opened."); - state = { ...state, supervisorPaneId: null, approvalId: null }; - persist(); - } - if (state.phase === "working" || state.supervisorPaneId) { - if (arg === "reconnect") { - if (!state.approvalId) throw new Error("No saved supervision binding. Use /goals restart."); - intercom.configure(state.approvalId, "worker", ctx, false); - await intercom.waitReady(undefined, { peerOnly: true }); - } else await startSupervisor(ctx); - } - if (intercom.ended) return; - if (state.phase === "working") { - intercom.markReady(); - startWorkerTimers(ctx); - } - ctx.ui.notify(state.phase === "planning" ? "Planning model restored. Choose Ready when the plan is agreed." : "Goal supervision reconnected; the current plan is unchanged.", "info"); - } catch (error) { - if (intercom.ended) return; - ctx.ui.notify(`Goal recovery failed: ${String(error)} Use /goals reconnect to retry, or /goals restart to explicitly replace the tracked pane.`, "warning"); - } - updateWidget(ctx); - return; - } - if (arg === "clear") { - if (state.planVersion === null) { - ctx.ui.notify("No active plan to disconnect.", "info"); - return; - } - const currentPlan = planRel(ctx); - if (!(await stopSupervisor())) { - ctx.ui.notify("Could not close the visible supervisor; the plan remains connected.", "warning"); - return; - } - state = { ...state, phase: null, supervisorPaneId: null, approvalId: null, planVersion: null }; - models.leave(); - modelError = null; - persist(); - updateWidget(ctx); - ctx.ui.notify(`Disconnected from ${currentPlan}; the file remains on disk.`, "info"); - return; - } - if (arg === "model" || arg.startsWith("model ")) { - if (state.phase === "working") { - ctx.ui.notify("Run /goals clear before changing the active supervisor model.", "warning"); - return; - } - if (!(await stopSupervisor())) { - ctx.ui.notify("Could not close the visible supervisor; its model was not changed.", "warning"); - return; - } - const ref = arg.slice("model".length).trim(); - state = { ...state, supervisorModel: ref || null, supervisorPaneId: null, approvalId: null }; - persist(); - ctx.ui.notify(`Goal-supervisor model ${ref ? `set to ${ref}` : "reset to the remembered supervisor model"}.`, "info"); - return; - } - if (!(await stopSupervisor())) { - ctx.ui.notify("Could not close the visible supervisor; no new plan was started.", "warning"); - return; - } - await restoreModel("planning", ctx); - state = { ...state, phase: "planning", supervisorPaneId: null, approvalId: null, planVersion: nextVersion(ctx), latestDirection: arg, signedOffGoals: [], previousPlan: null }; - planningContextPending = true; - resyncReason = null; - writePlan(ctx, ""); - persist(); - updateWidget(ctx); - // The drafting rules are sent ONCE, with the seed. v2 re-injected them every turn, which is - // why plan mode read as never-ending: every reply re-armed it. They come back only on a - // resync (session start / compaction), when the model has genuinely lost them. - const seed = arg - ? `We're in plan mode. Objective: ${arg}\n\n${planDrafting}\n\nWrite the plan to ${planPath(ctx)}.` - : `We're in plan mode. Tell me what you want to plan.\n\n${planDrafting}\n\nWrite the plan to ${planPath(ctx)}.`; - pi.sendUserMessage(seed, { deliverAs: "followUp" }); - }, - }); - - // --- hooks -------------------------------------------------------------------------------------- - - /** Restore the complete plan once after session start or compaction. */ - function dueInjection(ctx: ExtensionContext, plan: string): string | null { - if (state.phase === "planning" || !plan.trim() || !resyncReason) return null; - const why = resyncReason; - resyncReason = null; - return resync(plan, planRel(ctx), why); + function compatible() { + const tools = pi.getAllTools(); + const properties = (name: string) => (tools.find((t) => t.name === name)?.parameters as { properties?: Record<string, unknown> } | undefined)?.properties; + return properties("subagent")?.title && properties("subagent")?.agent && properties("subagent_resume")?.sessionFile && properties("subagent_kill")?.id; } - - // The phase snapshot enters context only when planning starts or context was lost. - pi.on("before_agent_start", async (_event, ctx) => { - const paused = pauseReason(); - if (paused) return { systemPrompt: `${ctx.getSystemPrompt()}\n\nGoal work is paused: ${paused} Do not implement or sign off goals. Human input and read-only diagnosis remain available; wait for recovery before resuming autonomous work.` }; - if (state.phase === "working") { - return { - systemPrompt: `${ctx.getSystemPrompt()}\n\nYou are the implementation worker for ${planRel(ctx)}. Keep the full conversation and do the work directly. A stronger read-only supervisor watches this session through pi-intercom and can steer you. Commit your evidence before asking for sign-off; never commit or discard unrelated changes to satisfy the clean-worktree gate. The supervisor can explicitly accept an inspected unchanged dirty state with ApproveGoal force and a reason. Stop when a goal appears complete so the supervisor can inspect a settled worker view. Call CompleteGoal only after the supervisor says it recorded approval. -- PI[Kimi K3]`, - }; + function send(content: string, triggerTurn = true) { + // sendMessage(triggerTurn:true) bypasses before_agent_start in Pi 0.85.1. + // A normal saved prompt prepares the current role before starting the turn. + if (triggerTurn) pi.sendUserMessage(`[pi-goals]\n${content}`, { deliverAs: "followUp" }); + else pi.sendMessage({ customType: "pi-goals-supervision", content, display: true }, { deliverAs: "followUp", triggerTurn: false }); + } + async function confirmOwnership(ctx: ExtensionContext, target: string, text: string, solo = true): Promise<boolean> { + if (launchPending) { ctx.ui.notify("Worker launch/resume is still pending; inspect its result before takeover.", "warning"); return false; } + const stamp = generation; + const revision = workerRevision; + const confirmation = solo ? "Worker confirmed stopped" : "Previous supervisor confirmed stopped"; + const choice = await ctx.ui.select(solo ? "Confirm all other writers for the current and target plans are stopped (inspect /subagents and their panes). A missing handle is not proof. Take over in this session?" : "Confirm no other supervisor owns this plan. Preserve any existing worker session and reconnect rather than starting another writer.", [confirmation, "Cancel"]); + if (stamp !== generation || revision !== workerRevision) return false; + if (choice !== confirmation) return false; + if (readFileSync(target, "utf8") !== text) { ctx.ui.notify("Plan changed during takeover; confirm again.", "warning"); return false; } + return true; + } + function enterSolo(ctx: ExtensionContext) { + state.mode = "solo"; state.workerStopped = true; + generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); + send(`${removeGoalSchedule(ctx.sessionManager.getSessionId())}\n\n${soloNotice(state.plan!)}`); + } + const help = "/goals new [initial idea] | review | ready | status | stop | resume | solo | exit | attach <plan.md> [solo] | model <model>\n/subagents opens the worker controls. Stop/exit pause this plan locally; worker termination must be confirmed through subagent_kill or its pane. No forced compaction or model switch; the worker pane's own model is chosen with /model in that pane. Hourly check-ins are one session-bound schedule_prompt job; plan-change reviews are the plan-watcher event hook."; + async function ready(ctx: ExtensionContext, menu: boolean) { + if (state.mode !== "planning") { ctx.ui.notify("Ready applies to a draft; use status or resume.", "warning"); return; } + const text = planText(); + const items = goals(text); + if (!items.length || new Set(items.map((g) => key(g.subject))).size !== items.length) { + ctx.ui.notify("Write a plan with distinct '- [ ] goal: ...' subjects before Ready.", "warning"); return; } - if (!planningContextPending) return; - planningContextPending = false; - return { message: { customType: PLANNING_CONTEXT, content: planningState(planPath(ctx)), display: false } }; - }); - - // PI: Working turns never see an obsolete planning snapshot. Auto-compaction retries skip - // before_agent_start, so context restores the planning snapshot exactly once in that path. - pi.on("context", async (event, ctx) => { - const messages = state.phase === "planning" ? event.messages : event.messages.filter((message) => (message as { customType?: string }).customType !== PLANNING_CONTEXT); - const removedPlanningContext = messages.length !== event.messages.length; - if (state.phase === "planning" && planningContextPending) { - planningContextPending = false; - return { messages: [...messages, { role: "user" as const, content: [{ type: "text" as const, text: planningState(planPath(ctx)) }], timestamp: Date.now() }] }; - } - const text = dueInjection(ctx, readPlan(ctx)); - if (!text) return removedPlanningContext ? { messages } : undefined; - return { messages: [...messages, { role: "user" as const, content: [{ type: "text" as const, text }], timestamp: Date.now() }] }; - }); - - // PI: Human plan-mode replies are durable evidence of the interview, not model summaries. - pi.on("input", async (event, ctx) => { - if (event.source === "extension") return; - state = { ...state, latestDirection: event.text }; - persist(); - if (state.phase === "planning") writePlan(ctx, appendInterview(readPlan(ctx), event.text)); - }); - - pi.on("agent_start", async (_event, ctx) => { - await publishWorkerView(ctx, "started"); - }); - - pi.on("turn_end", async (_event, ctx) => { - updateWidget(ctx); - if (state.phase !== "working") return; - workerTurns++; - if (workerTurns < 50) return; - workerTurns = 0; - await publishWorkerView(ctx, "turns"); - }); - - pi.on("tool_call", async (event, ctx) => { - const paused = pauseReason(); - if (paused && !(["read", "grep", "find", "ls"].includes(event.toolName) || (event.toolName === "bash" && isPlanningReadOnlyCommand(String((event.input as { command?: string }).command))))) { - return { block: true, terminate: true, reason: `Goal work is paused: ${paused} Only read-only diagnosis is available.` }; - } - if (state.phase === "planning") { - if (PLAN_MODE_BLOCKED_TOOLS.includes(event.toolName)) { - const target = (event.input as { path?: string }).path; - if (target && resolve(ctx.cwd, target) === resolve(planPath(ctx))) return; - return { block: true, reason: `Planning is read-only: only ${planRel(ctx)} may be written. Agree the plan, then choose Ready.` }; - } - if (event.toolName === "bash" && !isPlanningReadOnlyCommand(String((event.input as { command?: string }).command))) { - return { block: true, reason: "Planning is read-only: inspect facts without writes or pipes, then put the change in the plan." }; - } - return; - } - }); - - // A compaction loses context, so restore either the planning snapshot or the working plan once. - pi.on("session_compact", async () => { - if (state.phase === "planning") planningContextPending = true; - else resyncReason = "The session was just compacted."; - }); - - // PI: Print after Pi settles. agent_end is still streaming, so its message queues behind the menu. - pi.on("agent_settled", async (_event, ctx) => { - if (state.phase === "working") { - await publishWorkerView(ctx, "settled"); - return; - } - if (state.phase !== "planning" || modelError || !ctx.hasUI) return; - const version = state.planVersion; - const planning = () => !intercom.ended && state.phase === "planning" && state.planVersion === version; - let printed = ""; - while (true) { - if (!planning()) return; - const plan = readPlan(ctx); - if (scanGoals(plan).length === 0) { - if (plan.trim()) ctx.ui.notify(`The plan has no goal line. Revise ${planRel(ctx)} to add one.`, "warning"); - return; - } - if (plan !== printed) { - printed = plan; - pi.sendMessage({ customType: "plan", content: plan, display: true }); - } - const choice = await ctx.ui.select(`Plan drafted in ${planRel(ctx)}.`, ["Ready", "Refine", "Edit", "Cancel"]); - if (!planning()) return; - if (choice === "Refine") { - const notes = await ctx.ui.editor("What should change about the plan?", ""); - if (!planning()) return; - if (!notes?.trim()) continue; - state = { ...state, latestDirection: notes }; - persist(); - writePlan(ctx, appendInterview(plan, notes)); - planningContextPending = true; - pi.sendUserMessage(`Revise the plan at ${planPath(ctx)} using these human notes:\n\n${notes}\n\nKeep the same goal structure.`, { deliverAs: "followUp" }); - return; - } + const stamp = generation; + if (menu) { + const choice = await ctx.ui.select(`Review ${state.plan}`, ["Ready", "Discuss", "Edit", "Cancel"]); + if (stamp !== generation || digest(planText()) !== digest(text)) { ctx.ui.notify("Plan changed during review. Review it again.", "warning"); return; } + if (choice === "Discuss") { send(discuss); return; } if (choice === "Edit") { - const edited = await ctx.ui.editor("Edit the plan", plan); - if (!planning()) return; - if (edited !== undefined && edited !== plan) writePlan(ctx, edited); - continue; - } - if (choice === "Cancel") { - if (!(await stopSupervisor())) { ctx.ui.notify("Could not close the tracked supervisor; plan was not discarded.", "warning"); return; } - rmSync(planPath(ctx), { force: true }); - models.leave(); - state = { ...state, phase: null, supervisorPaneId: null, approvalId: null, planVersion: null }; - persist(); - updateWidget(ctx); - ctx.ui.notify("Plan discarded.", "info"); + const edited = await ctx.ui.editor("Edit goal plan", text); + if (edited !== undefined && stamp === generation && planText() === text && state.plan) { writeFileSync(state.plan, edited); refresh(ctx); } return; } if (choice !== "Ready") return; - const attempt = {}; - readyAttempt = attempt; - const current = () => !intercom.ended && readyAttempt === attempt && state.planVersion === version; - const checkApprovedPlan = () => { - if (readPlan(ctx) !== plan) throw new Error("The plan changed after Ready was selected. Review the changed plan and select Ready again; the existing supervisor pane is retained."); - }; - try { - checkApprovedPlan(); - await startSupervisor(ctx, current); - if (!current()) return; - checkApprovedPlan(); - await restoreModel("worker", ctx); - if (!current()) return; - checkApprovedPlan(); - state = { ...state, phase: "working" }; - resyncReason = "The plan was approved."; - persist(); - intercom.markReady(); - startWorkerTimers(ctx); - await publishWorkerView(ctx, "ready"); - if (!current()) return; - checkApprovedPlan(); - updateWidget(ctx); - ctx.ui.notify(`Visible supervisor opened in Herdr pane ${state.supervisorPaneId}.`, "info"); - pi.sendUserMessage("The plan is approved. Begin implementation as the worker."); - readyAttempt = undefined; - } catch (error) { - if (!current()) return; - intercom.markNotReady(); - stopWorkerTimers(); - ctx.ui.notify(`Goal supervisor could not start: ${error instanceof Error ? error.message : String(error)} Use /goals reconnect to retry, or /goals restart to replace the tracked pane.`, "warning"); - state = { ...state, phase: "planning" }; - persist(); - updateWidget(ctx); - } - return; + } + if (!compatible()) { ctx.ui.notify("Requires edxeth/pi-subagents 2.9.x, not nicobailon/pi-subagents. Draft preserved; /goals solo is available.", "error"); return; } + state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); + send(`${checkIn(ctx)}\n\n${readyApproved(WORKER, state.plan!, state.worker?.sessionFile, text, ctx.sessionManager.getSessionId())}`); + } + + pi.on("session_start", (_e, ctx) => restore(ctx)); + pi.on("session_tree", (_e, ctx) => restore(ctx)); + pi.on("session_shutdown", () => { generation++; planWatcher?.close(); planWatcher = undefined; clearTimeout(planEditTimer); planEditTimer = undefined; }); + pi.on("session_compact", () => { notice = true; }); + pi.on("turn_end", (_event, ctx) => { + if (!["supervising", "solo"].includes(state.mode)) return; + const snapshot = readPlan(); + if (snapshot.text === undefined) { notice = true; return; } + const workingSet = foldPlan(snapshot.text); + turnsStale = workingSet === lastWorkingSet ? turnsStale + 1 : 0; + lastWorkingSet = workingSet; + refresh(ctx); + if (turnsStale === 8 && goals(snapshot.text).some(g => g.status === "open" || g.status === "active")) { + // Pi queues context-only messages until tool results are appended at turn_end. + // This reaches the next model call in a long run without triggering another run. + pi.sendMessage({ customType: "pi-goals-upkeep", content: upkeep(state.plan!), display: false }, { triggerTurn: false }); } }); - - pi.on("session_start", async (_event, ctx) => { - const last = ctx.sessionManager - .getEntries() - .filter((e: { type?: string; customType?: string }) => e.type === "custom" && e.customType === STATE) - .pop() as { data?: PlanState } | undefined; - state = { - phase: last?.data?.phase ?? null, - supervisorModel: last?.data?.supervisorModel ?? null, - supervisorPaneId: last?.data?.supervisorPaneId ?? null, - approvalId: last?.data?.approvalId ?? null, - planVersion: last?.data?.planVersion ?? null, - latestDirection: last?.data?.latestDirection ?? "", - signedOffGoals: last?.data?.signedOffGoals ?? [], - previousPlan: last?.data?.previousPlan ?? null, - }; - modelError = state.phase ? "Role model restoration is pending." : null; - planningContextPending = state.phase === "planning"; - resyncReason = state.phase === "working" ? "New session." : null; - if (state.phase === "working" && state.approvalId) { - intercom.configure(state.approvalId, "worker", ctx, false); - startWorkerTimers(ctx); - } + pi.on("agent_end", (_e, ctx) => { refresh(ctx); if (!planWatcher && state.mode === "supervising") watchPlan(ctx); }); + let proposedDraft = ""; + let proposing = false; + pi.on("agent_settled", async (_e, ctx) => { + if (state.child || state.mode !== "planning" || !ctx.hasUI || proposing) return; + const text = planText(); + const version = `${state.plan}:${digest(text)}`; + if (!goals(text).length || version === proposedDraft) return; + proposedDraft = version; + proposing = true; try { - if (state.phase) await restoreModel(state.phase === "planning" ? "planning" : "worker", ctx); - } catch (error) { - if (!intercom.ended) ctx.ui.notify(`Goal work paused: ${String(error)} Use /model, then /goals reconnect.`, "warning"); + pi.sendMessage({ customType: "goal-plan-proposal", content: text, display: true }, { triggerTurn: false }); + await ready(ctx, true); + } finally { proposing = false; } + }); + // No context hook. Historical message arrays, native checkpoints and model selection are untouched. + pi.on("before_agent_start", (event, ctx) => { + if (state.mode === "chat") return; + const snapshot = readPlan(); + if (snapshot.text === undefined) { + notice = true; // Retry resync on the next turn; do not consume a failed snapshot. + return { systemPrompt: `${event.systemPrompt}\n\n${state.child ? childPlanRole : ""}\n${snapshot.error}` }; } - if (intercom.ended) return; - if (state.phase === "working" && state.approvalId && !modelError) { - intercom.markReady(); - void intercom.waitReady().catch(error => { - if (!intercom.ended && state.phase === "working") ctx.ui.notify(`Goal work paused: ${String(error)} Use /goals reconnect or /goals restart.`, "warning"); - }); - } - updateWidget(ctx); + const role = state.child ? childPlanRole : state.mode === "supervising" + ? supervisor(WORKER, state.plan!, ctx.sessionManager.getSessionId()) + : state.mode === "planning" ? planning(state.plan!) : state.mode === "paused" ? pausedRole : soloRole; + const content = notice ? planContext(state.child ? "worker" : state.mode, state.plan, snapshot.text) + : undefined; + if (content) turnsStale = 0; + notice = false; + return { systemPrompt: `${event.systemPrompt}\n\n${role}`, ...(content ? { message: { customType: "pi-goals-plan", content, display: false } } : {}) }; + }); + pi.on("tool_call", (event) => { + if (state.child || !["subagent", "subagent_resume"].includes(event.toolName)) return; + // Solo means this chat took over implementation: no concurrent writer may be delegated. + if (state.mode === "planning" || state.mode === "paused" || state.mode === "solo") return { block: true, reason: goalToolBlocked(state.mode) }; + if (state.plan) { launchPending = true; state.workerStopped = false; workerRevision++; save(); } + }); + pi.on("tool_result", (event) => { + if (state.child || !state.plan || !["subagent", "subagent_resume"].includes(event.toolName)) return; + launchPending = false; + if (event.isError) return; + const details = event.details as { id?: string; sessionFile?: string } | undefined; + if (details?.id && details.sessionFile) { state.worker = { id: details.id, sessionFile: details.sessionFile }; state.workerStopped = false; workerRevision++; save(); } }); - pi.on("session_shutdown", async () => { - stopWorkerTimers(); - }); - - pi.registerTool({ - name: "CompleteGoal", - label: "Goal signoff", - description: completeGoalDescription, - parameters: Type.Object({ - goal: Type.String({ description: completeGoalParamDescription }), - }), - async execute(_id, params, signal, _onUpdate, ctx) { - if (signal?.aborted) return result("Goal sign-off cancelled; no completion recorded.", true); - const binding = state.approvalId; - const version = state.planVersion; - if (state.phase !== "working") return result("Planning is not approved. Choose Ready before signing off a goal.", true); - if (pauseReason()) return result(`Goal sign-off blocked: ${pauseReason()}`, true); - if (!state.approvalId) return result("Goal sign-off blocked: no current supervisor review.", true); - const background = await backgroundState(pi); - if (signal?.aborted || intercom.ended || state.approvalId !== binding || state.planVersion !== version || state.phase !== "working") return result("Goal sign-off cancelled or superseded; no completion recorded.", true); - if (intercom.ended || !background.quiet || pauseReason()) return result(`Goal sign-off blocked: ${pauseReason() ?? background.description}`, true); - const plan = readPlan(ctx); - if (!plan.trim()) return result(`No plan file at ${planRel(ctx)}. Run /goals to draft one.`, true); - const block = goalBlock(plan, params.goal); - if (!block) return result(`No unique open goal line matched "${params.goal}" in ${planRel(ctx)}.`, true); - const approval = readApproval(approvalPath(ctx.cwd, ctx.sessionManager.getSessionId(), params.goal)); - let repository: ReturnType<typeof repositoryState>; + pi.registerCommand("goals", { + description: "Goal plan actions: new, review, ready, status, stop, resume, solo, attach, model, exit", + getArgumentCompletions: (prefix) => ["new", "review", "ready", "status", "stop", "resume", "solo", "attach", "model", "exit", "help"].filter((verb) => verb.startsWith(prefix)).map((verb) => ({ value: verb, label: verb })), + handler: async (args, ctx) => { try { - repository = repositoryState(ctx.cwd, Boolean(approval?.force)); - } catch (error) { - return result(`Goal sign-off could not inspect the repository: ${error instanceof Error ? error.message : String(error)}`, true); - } - if (!repository.cleanWorktree && !approval?.force) return result("Goal sign-off blocked: worktree is dirty. Request supervisor inspection, not an unrelated cleanup commit.", true); - if (!approvalMatches(approval, { - approvalId: state.approvalId, - goal: params.goal, - planPath: planPath(ctx), - goalBlockHash: hashGoalBlock(block), - repoRoot: repository.repoRoot, - head: repository.head, - tree: repository.tree, - cleanWorktree: repository.cleanWorktree, - worktree: repository.worktree, - })) return result("Goal sign-off blocked: no matching supervisor approval checkpoint. Request a fresh supervisor review.", true); - const ticked = tickGoal(plan, params.goal); - if (!ticked) return result(`No unique exact goal line matched "${params.goal}" in ${planRel(ctx)}.`, true); - if (signal?.aborted) return result("Goal sign-off cancelled; no completion recorded.", true); - writePlan(ctx, appendLog(ticked, `${stamp()} mechanically signed off "${params.goal}" after matching supervisor approval`)); - state = { ...state, signedOffGoals: [...state.signedOffGoals.filter(goal => goal !== goalKey(params.goal)), goalKey(params.goal)] }; - persist(); - updateWidget(ctx); - return result(`Sign-off accepted. Goal ticked [x] in ${planRel(ctx)}.`); + if (state.child) { ctx.ui.notify("This is the delegated worker. Goal approval belongs to its parent.", "info"); return; } + let command = args.trim(); + if (!command) { + const actions = ["status — Show current plan", "new — New plan", "attach — Open an existing plan", "review — Review current plan", "ready — Approve draft", "stop — Pause work", "resume — Continue paused work", "solo — Work in this session", "model — Set worker model", "exit — Leave goal mode", "help — Show commands"]; + const before = generation; + const choice = await ctx.ui.select("Goal plan actions", actions); + if (!choice || before !== generation) return; + command = choice.split(" — ")[0]; + if (["attach", "model"].includes(command)) { + const value = await ctx.ui.editor(command === "attach" ? "Plan path (optional: solo)" : "Worker model (provider/model)", ""); + if (!value?.trim() || before !== generation) return; + command += ` ${value.trim()}`; + } + } + if (command === "help") { ctx.ui.notify(help, "info"); return; } + if (command === "status") { + refresh(ctx); + ctx.ui.notify([ + `Mode: ${state.mode}`, + `Plan: ${state.plan ?? "none"}`, + `Preferred worker model (plan): ${notedPlanValue("preferred worker model") ?? "not stated; use /goals model <model>"}`, + `Recorded worker session: ${state.worker?.sessionFile ?? "not recorded"}`, + notedPlanValue("worker session") ? `Worker session noted in plan: ${notedPlanValue("worker session")}` : "", + `Hourly check-in: schedule_prompt job ${JSON.stringify(`goals-${ctx.sessionManager.getSessionId()}`)} (list/remove via schedule_prompt; plan-change reviews are the plan-watcher event hook)`, + "Liveness is owned by edxeth; inspect /subagents.", + ].filter(Boolean).join("\n"), "info"); + return; + } + if (command === "review" && state.mode === "supervising") { notice = true; send(manualReview(state.plan ?? "")); return; } + if (command === "review" || command === "ready") { await ready(ctx, command === "review"); return; } + if (command === "model" || command.startsWith("model ")) { + if (!state.plan || !goals(planText()).length) { ctx.ui.notify("Register a goal plan first.", "warning"); return; } + const ref = command.slice("model".length).trim(); + if (!ref) { ctx.ui.notify("Use /goals model <provider/model>; no preference changed.", "info"); return; } + const lines = planText().split("\n"); + const pref = `- preferred worker model: ${ref || "(none specified)"}`; + const found = lines.findIndex((line) => /^-\s*preferred worker model:/i.test(line)); + if (found >= 0) lines[found] = pref; + else { const title = lines.findIndex((line) => /^#\s/.test(line)); lines.splice(title >= 0 ? title + 1 : 0, 0, pref); } + writeFileSync(state.plan, lines.join("\n")); + planHash = digest(planViews(planText()).short); + refresh(ctx); + ctx.ui.notify(ref ? `Preferred worker model set to ${ref} in plan preferences. The supervisor selects it at launch and verifies the resolved model; the worker pane's own model is chosen with /model in that pane.` : "Preferred worker model cleared.", "info"); + return; + } + if (command === "attach" || command.startsWith("attach ")) { + const rest = command.slice("attach".length).trim(); + const [raw, kind, extra] = rest.split(/\s+/); + const solo = kind === "solo"; + if (extra || (kind && !solo)) { ctx.ui.notify("Use /goals attach <path-to-plan.md> [solo].", "warning"); return; } + if (!raw) { ctx.ui.notify("Use /goals attach <path-to-plan.md> [solo].", "info"); return; } + const target = isAbsolute(raw) ? raw : resolve(ctx.cwd, raw); + let text: string; + try { text = readFileSync(target, "utf8"); } catch { ctx.ui.notify(`Cannot read plan at ${target}.`, "error"); return; } + if (!goals(text).length) { ctx.ui.notify(`${target} has no '- [ ] goal:' lines; attach a judgeable plan.`, "warning"); return; } + if (!solo && ((state.worker && !state.workerStopped) || state.mode === "supervising")) { ctx.ui.notify("Exit and resolve the existing worker before replacing the plan. The current plan is preserved.", "warning"); return; } + const noted = /^-\s*worker session:\s*(\S+)/im.exec(foldPlan(text))?.[1]; + if (!(await confirmOwnership(ctx, target, text, solo))) return; + const retained = target === state.plan ? state.signoffs : {}; + const worker = noted ? { sessionFile: resolve(ctx.cwd, noted) } : state.workerStopped ? state.worker : undefined; + state = { mode: solo ? "solo" : "planning", plan: target, signoffs: retained, worker, workerStopped: solo || (!noted && state.workerStopped) }; + generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); + if (solo) enterSolo(ctx); + else send(attachNotice(target, false, noted)); + return; + } + if (command === "stop" || command === "exit") { + if (state.mode === "planning") { + if (command === "stop") { ctx.ui.notify("A draft cannot pause; use /goals exit to leave planning with the draft preserved.", "warning"); return; } + // Planning exit must not get the model trapped re-planning or lose the draft. + state.mode = "chat"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); + ctx.ui.notify(`Planning exited; draft preserved at ${state.plan}. No implementation was approved or started. Reconnect with /goals attach ${state.plan}.`, "info"); + return; + } + state.mode = command === "stop" ? "paused" : "chat"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); + send(`${removeGoalSchedule(ctx.sessionManager.getSessionId())}\n\n${pauseExitNotice(state.worker, command === "exit")}`, Boolean(state.worker) || hasScheduleTool()); + return; + } + if (command === "resume") { + if (state.mode !== "paused" || !state.plan) { ctx.ui.notify("Only a paused approved plan can resume. A draft needs Ready.", "warning"); return; } + if (!compatible()) { ctx.ui.notify("edxeth tools unavailable; plan remains paused.", "error"); return; } + state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); + send(`${checkIn(ctx)}\n\n${resumeNotice(WORKER, state.plan, state.worker)}`); + return; + } + if (command === "solo") { + if (!state.plan || !goals(planText()).length) { ctx.ui.notify("Register a goal plan first.", "warning"); return; } + if (!(await confirmOwnership(ctx, state.plan, planText()))) return; + enterSolo(ctx); + return; + } + if (command !== "new" && !command.startsWith("new ")) { ctx.ui.notify(`Unknown or incomplete command. ${help}`, "warning"); return; } + const objective = command.slice(4).trim(); + if ((state.worker && !state.workerStopped) || state.mode === "supervising") { ctx.ui.notify("Exit and resolve the existing worker before replacing the plan. The current plan is preserved.", "warning"); return; } + const path = join(ctx.cwd, ".pi", "plan", `${ctx.sessionManager.getSessionId()}-main.md`); + mkdirSync(dirname(path), { recursive: true }); + // Never overwrite an earlier plan at this session path; the model can revise it after inspection. + try { writeFileSync(path, planDocument(objective), { flag: "wx" }); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; } + state = { mode: "planning", plan: path, signoffs: {}, worker: state.worker, workerStopped: state.workerStopped }; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); + send(planningSeed(objective, path)); + } catch (error) { ctx.ui.notify(String(error), "error"); } + }, + }); + pi.registerTool({ + name: "AttachGoalPlan", label: "Attach delegated plan", description: attachGoalPlanDescription, + parameters: Type.Object({ path: Type.String() }), + async execute(_id, params, _signal, _update, ctx) { + if (!state.child) return result(messages.childAttachOnly); + try { + if (!isAbsolute(params.path) || !goals(readFileSync(params.path, "utf8")).length) return result(messages.invalidAttachment); + } catch { return result(messages.invalidAttachment); } + state.plan = params.path; generation++; notice = true; save(); refresh(ctx); + return result(childPlanAttached(params.path)); + }, + }); + pi.registerTool({ + name: "CompleteGoal", label: "Review goal evidence", + description: completeGoalDescription, + parameters: Type.Object({ goal: Type.String(), evidence: Type.Array(Type.String(), { minItems: 1 }), observation: Type.String({ minLength: 1 }) }), + async execute(_id, params, signal, _update, ctx) { + if (state.child || !["supervising", "solo"].includes(state.mode)) return result(messages.completionUnavailable); + if (signal?.aborted) return result(messages.cancelled); + const snapshot = readPlan(); + if (snapshot.text === undefined) return result(snapshot.error!); + const text = snapshot.text; + const matches = goals(text).filter((g) => g.status !== "cancelled" && key(g.subject) === key(params.goal)); + if (matches.length !== 1 || !state.plan) return result(messages.uniqueGoal); + const evidence = params.evidence.map((file) => isAbsolute(file) ? file : resolve(ctx.cwd, file)); + try { for (const file of evidence) if (!readFileSync(file).length) throw new Error(emptyEvidence(file)); } + catch (error) { return result(evidenceUnavailable(error)); } + const lines = text.split("\n"); + lines[matches[0].index] = lines[matches[0].index].replace(/\[[ xX/-]\]/, "[x]"); + let log = lines.findIndex(line => /^##\s+Log\s*$/i.test(line)); + if (log === -1) { lines.push("", "## Log"); log = lines.length - 1; } + lines.splice(log + 1, 0, "", completionLog(params.goal, params.observation, evidence, state.mode === "solo")); + writeFileSync(state.plan, `${lines.join("\n").trimEnd()}\n`); + state.signoffs[key(matches[0].subject)] = { evidence, observation: params.observation }; + planHash = digest(planViews(planText()).short); + save(); refresh(ctx); + const remaining = goals(planText()).some((goal) => goal.status !== "cancelled" && (goal.status !== "done" || !state.signoffs[key(goal.subject)])); + return result(completionResult(matches[0].subject, ctx.sessionManager.getSessionId(), remaining, state.mode === "solo")); }, }); } - -// --- helpers (module scope) -------------------------------------------------------------------- - -// A compact changed span, not a second plan parser. Worker views bound its serialized size. -function planDiff(before: string, after: string): string { - if (before === after) return "none"; - const old = before.split("\n"); - const next = after.split("\n"); - let start = 0; - while (start < old.length && start < next.length && old[start] === next[start]) start++; - let oldEnd = old.length; - let nextEnd = next.length; - while (oldEnd > start && nextEnd > start && old[oldEnd - 1] === next[nextEnd - 1]) { oldEnd--; nextEnd--; } - return [`@@ from line ${start + 1} @@`, ...old.slice(start, oldEnd).map(line => `- ${line}`), ...next.slice(start, nextEnd).map(line => `+ ${line}`)].join("\n"); -} - -function result(text: string, isError = false) { - return { content: [{ type: "text" as const, text }], details: {}, isError }; -} - -function mutatingReadCommand(part: string): boolean { - return /(?:^|\s)--output(?:=|\s|$)|^find\b.*\s-(?:delete|exec|execdir|ok|okdir|fprint|fprintf|fls)(?:\s|$)/.test(part) - || (/^git\s+branch\b/.test(part) && !/^git\s+branch(?:\s+(?:--show-current|--list|-a|--all|-r|--remotes|-v|-vv))*$/.test(part)); -} - -function isPlanningReadOnlyCommand(command: string): boolean { - if (/[|><`$\n\r]/.test(command)) return false; - return command.split(/&&|;/).every((raw) => { - const part = raw.trim(); - return !mutatingReadCommand(part) && /^(?:cd\b|pwd|ls\b|git\s+(?:status|log|diff|show|branch)\b|rg\b|grep\b|find\b|head\b|tail\b|wc\b|stat\b|test\b)\b/.test(part); - }); -} - - -/** Local time, not UTC: agents freehand-stamp their manual ## Log lines from the local clock they - * see, so a UTC tool stamp made the trail read as two different afternoons (dogfood finding). */ -function stamp(): string { - const d = new Date(); - const p = (n: number) => String(n).padStart(2, "0"); - return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; -} - -/** Tick the goal line whose subject exactly matches `goal` (trimmed, case-insensitive) to [x]. - * Null when there is no unique exact match. Reuses GOAL_LINE and is deliberately not fuzzy. */ -export function tickGoal(plan: string, goal: string): string | null { - const lines = plan.split("\n"); - const want = goal.trim().toLowerCase(); - const hits = scanGoals(plan).filter(g => g.subject.toLowerCase() === want).map(g => g.line); - if (hits.length !== 1) return null; - lines[hits[0]] = lines[hits[0]].replace(/\[[ xX/-]\]/, "[x]"); - return lines.join("\n"); -} - -/** Append one line under ## Log (creating the section at EOF if absent). */ -export function appendLog(text: string, entry: string): string { - const lines = text.split("\n"); - const line = `- ${entry}`; - const header = lines.findIndex((l) => FOLD_LINE.test(l)); - if (header === -1) return `${text.replace(/\n+$/, "")}\n\n## Log\n${line}\n`; - let insertAt = header + 1; - for (let i = header + 1; i < lines.length; i++) { - if (/^#{1,6}\s/.test(lines[i])) break; - if (/^\s*-\s+/.test(lines[i])) insertAt = i + 1; - } - lines.splice(insertAt, 0, line); - return lines.join("\n"); -} - -/** PI: Preserve human plan-mode answers verbatim below the fold. */ -export function appendInterview(text: string, answer: string): string { - const lines = text.split("\n"); - const header = lines.findIndex((l) => /^##\s+Interview\s*$/i.test(l)); - const entry = [`### ${stamp()}`, "", ...answer.split("\n").map((line) => `> ${line}`), ""]; - if (header === -1) return `${text.replace(/\n+$/, "")}\n\n## Interview\n\n${entry.join("\n")}`; - let insertAt = header + 1; - while (insertAt < lines.length && !/^##\s+/.test(lines[insertAt])) insertAt++; - lines.splice(insertAt, 0, ...entry); - return lines.join("\n"); -} diff --git a/src/intercom-package.d.ts b/src/intercom-package.d.ts deleted file mode 100644 index d317fd8..0000000 --- a/src/intercom-package.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "pi-intercom" { - import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; - export default function intercom(pi: ExtensionAPI): void; -} diff --git a/src/intercom.ts b/src/intercom.ts deleted file mode 100644 index 0c36c1a..0000000 --- a/src/intercom.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { randomUUID } from "node:crypto"; -import type { ExtensionAPI, ExtensionContext, SessionStartEvent } from "@earendil-works/pi-coding-agent"; -import type { IntercomExtensionChannel, IntercomExtensionEvent } from "pi-intercom/extension-api.ts"; - -export type Role = "worker" | "supervisor"; -export interface View { id: string; text: string; reason: string; through?: string; backgroundQuiet: boolean } -interface Message { binding: string; role: Role; kind: "hello" | "view" | "steer" | "received"; id: string; text?: string; reason?: string; failure?: string; ready?: boolean; reply?: boolean; through?: string; backgroundQuiet?: boolean } -const STATE = "pi-goals-intercom"; - -export class GoalIntercom { - private channel?: IntercomExtensionChannel; - private ctx?: ExtensionContext; - private stopped = false; - private registered = false; - private binding = ""; - private role: Role = "worker"; - private ready = false; - private failure?: string; - private peer?: string; - private peerReady = false; - private peerFailure?: string; - private pending = new Map<string, Message>(); - private received = new Set<string>(); - private inbox = new Map<string, Message>(); - private deliveryTimer?: ReturnType<typeof setTimeout>; - private idleChecks = 0; - private delivering?: string; - private compacting = false; - private waiters = new Set<(error?: Error) => void>(); - latestView?: View; - acknowledgedEntry?: string; - onView: (view: View) => void = () => {}; - onSteer: (text: string) => void = () => {}; - onConnectionChange: (ctx: ExtensionContext) => void = () => {}; - - constructor(private pi: ExtensionAPI) { - pi.events.on("intercom:extension-registry-ready", () => this.register()); - this.register(); - pi.on("session_start", async (event, ctx) => { - this.ctx = ctx; - if (!this.channel) await this.loadIntercom(event, ctx); - }); - pi.on("message_start", async event => { - if (event.message.role !== "user") return; - const content = event.message.content; - const text = typeof content === "string" ? content : content.filter(part => part.type === "text").map(part => part.text).join("\n"); - for (const message of this.inbox.values()) { - if (text !== this.deliveryText(message)) continue; - this.inbox.delete(message.id); - this.received.add(message.id); - this.record("in", message); - if (this.connected) this.publish({ binding: this.binding, role: this.role, kind: "received", id: message.id }); - this.delivering = undefined; - this.idleChecks = 0; - this.scheduleDelivery(0); - break; - } - }); - pi.on("session_before_compact", async () => { this.compacting = true; }); - pi.on("session_compact", async () => { this.compacting = true; this.resumeDelivery(); }); - pi.on("session_compact_failed", async () => { this.compacting = true; this.resumeDelivery(); }); - pi.on("agent_settled", async () => this.resumeDelivery()); - pi.on("session_shutdown", async () => { - if (this.deliveryTimer) clearTimeout(this.deliveryTimer); - this.stopped = true; - this.peerReady = false; - for (const wake of this.waiters) wake(); - }); - } - - configure(binding: string, role: Role, ctx: ExtensionContext, ready = role === "worker"): void { - for (const wake of this.waiters) wake(new Error("Supervision readiness wait cancelled by reconfiguration.")); - this.binding = binding; - this.role = role; - this.ctx = ctx; - this.ready = ready; - this.failure = undefined; - this.peer = undefined; - this.peerReady = false; - this.peerFailure = undefined; - this.pending.clear(); - this.received.clear(); - this.inbox.clear(); - this.delivering = undefined; - this.idleChecks = 0; - if (this.deliveryTimer) clearTimeout(this.deliveryTimer); - this.deliveryTimer = undefined; - this.latestView = undefined; - this.acknowledgedEntry = undefined; - for (const entry of ctx.sessionManager.getBranch?.() ?? ctx.sessionManager.getEntries()) { - if (entry.type !== "custom" || entry.customType !== STATE) continue; - const record = entry.data as { direction: string; message: Message }; - const message = record.message; - if (message.binding !== binding) continue; - if (record.direction === "out" && message.kind === "steer") this.pending.set(message.id, message); - if (record.direction === "ack") { - this.pending.delete(message.id); - if (message.through) this.acknowledgedEntry = message.through; - } - if (record.direction === "queued") this.inbox.set(message.id, message); - if (record.direction === "in") { this.received.add(message.id); this.inbox.delete(message.id); } - if (message.kind === "view") this.latestView = { id: message.id, text: message.text!, reason: message.reason!, through: message.through, backgroundQuiet: message.backgroundQuiet === true }; - } - this.hello(); - this.scheduleDelivery(0); - } - - // End this plan's binding without disposing the session's transport. - detach(): void { - if (this.deliveryTimer) clearTimeout(this.deliveryTimer); - this.deliveryTimer = undefined; - this.inbox.clear(); - this.delivering = undefined; - this.ready = false; - this.hello(); - this.binding = ""; - this.peer = undefined; - this.peerReady = false; - this.latestView = undefined; - this.pending.clear(); - for (const wake of this.waiters) wake(new Error("Supervision readiness wait cancelled: plan detached.")); - if (this.ctx) this.onConnectionChange(this.ctx); - } - - failReady(reason: string): void { this.failure = reason; this.setReady(false); } - markReady(): void { this.failure = undefined; this.setReady(true); this.resumeDelivery(); } - markNotReady(): void { this.setReady(false); } - private setReady(ready: boolean): void { - if (this.stopped) return; - this.ready = ready; - this.hello(); - if (this.ctx) this.onConnectionChange(this.ctx); - } - get ended(): boolean { return this.stopped; } - get bound(): boolean { return !this.stopped && Boolean(this.binding); } - get peerPresent(): boolean { return Boolean(this.bound && this.peer && this.channel?.snapshot().connected); } - get connected(): boolean { return this.ready && this.peerPresent && this.peerReady; } - - // Startup can wait for the supervisor while the worker is still in planning/model recovery. - async waitReady(timeoutMs = 300_000, { peerOnly = false } = {}): Promise<void> { - const ready = () => this.connected || (peerOnly && this.peerPresent && this.peerReady); - if (this.peerFailure) throw new Error(this.peerFailure); - if (ready()) return; - await new Promise<void>((resolve, reject) => { - const finish = (error?: Error) => { - if (!error && !ready() && !this.stopped) return; - clearTimeout(timer); this.waiters.delete(finish); - if (error) reject(error); - else if (this.stopped) reject(new Error("Session ended while waiting for Intercom readiness.")); - else resolve(); - }; - const timer = setTimeout(() => { this.waiters.delete(finish); reject(new Error("Supervisor did not become ready through pi-intercom; inspect its pane.")); }, timeoutMs); - this.waiters.add(finish); - finish(); - }); - } - - view(text: string, reason: string, through?: string, backgroundQuiet = false): View { - if (!this.bound) throw new Error("No active supervision binding for a worker view."); - const id = randomUUID(); - const message: Message = { binding: this.binding, role: this.role, kind: "view", id, text: `${text}\n\nworker view id: ${id}`, reason, through, backgroundQuiet }; - this.record("out", message); - this.latestView = { id, text: message.text!, reason, through, backgroundQuiet }; - if (this.connected) this.publish(message); - return this.latestView; - } - - steer(text: string): string { - if (!this.connected) throw new Error("Worker is disconnected; no instruction was sent."); - const message: Message = { binding: this.binding, role: this.role, kind: "steer", id: randomUUID(), text }; - this.record("out", message); - this.pending.set(message.id, message); - this.publish(message); - return message.id; - } - - // The inbox is persisted before handoff. Receipt means Pi started the user message, not model judgment or execution. - private deliveryText(message: Message): string { return message.kind === "view" ? message.text! : `[supervisor] ${message.text!}`; } - resumeDelivery(): void { - if (!this.ctx?.hasPendingMessages?.()) this.delivering = undefined; - this.idleChecks = 0; - this.scheduleDelivery(0); // Pi 0.85.1 isIdle includes compaction; check it after the success/failure hook. - } - private scheduleDelivery(delay: number): void { - if (this.deliveryTimer) clearTimeout(this.deliveryTimer); - this.deliveryTimer = undefined; - if (!this.bound || !this.inbox.size) return; - this.deliveryTimer = setTimeout(() => { this.deliveryTimer = undefined; this.deliverNext(); }, delay); - } - private deliverNext(): void { - if (!this.bound || !this.ready || !this.inbox.size) return; - // Accepted-but-not-yet-presented messages must not be submitted again behind a running turn. - if (this.ctx?.hasPendingMessages?.()) return; - if (this.compacting && !this.ctx?.isIdle?.()) { - if (++this.idleChecks <= 300) this.scheduleDelivery(1000); - else this.ctx?.ui.notify("Supervision message retained while Pi is busy. Use /goals reconnect when ready to retry delivery.", "warning"); - return; - } - if (this.delivering && !this.compacting) return; - this.compacting = false; - const message = this.inbox.values().next().value!; - this.delivering = message.id; - try { - if (message.kind === "view") this.onView({ id: message.id, text: message.text!, reason: message.reason!, through: message.through, backgroundQuiet: message.backgroundQuiet === true }); - else this.onSteer(message.text!); - } catch (error) { this.delivering = undefined; this.ctx?.ui.notify(`Supervision message retained: ${String(error)} Use /goals reconnect to retry.`, "warning"); } - } - - private record(direction: string, message: Message): void { this.pi.appendEntry(STATE, { direction, message }); } - private publish(message: Message): void { - if (this.stopped) throw new Error("Intercom session ended."); - if (Buffer.byteLength(JSON.stringify(message)) > 16_000) throw new Error("Supervisor message exceeds the Intercom payload limit."); - if (!this.channel?.snapshot().supported) throw new Error("pi-intercom broker does not support extension channels."); - this.channel.publish(message, { audience: "capable" }); - } - private hello(reply = false): void { - if (!this.stopped && this.binding && this.channel?.snapshot().connected) this.publish({ binding: this.binding, role: this.role, kind: "hello", id: "hello", ready: this.ready, failure: this.failure, reply }); - } - private receive(event: IntercomExtensionEvent): void { - if (this.stopped) return; - if (event.type === "connection") { - if (!event.connected) { - if (this.peerReady) this.ctx?.ui.notify("Goal supervision disconnected from pi-intercom.", "warning"); - this.peer = undefined; this.peerReady = false; - } - else this.hello(); - if (this.ctx) this.onConnectionChange(this.ctx); - return; - } - if (event.type === "session_left" && event.sessionId === this.peer) { - this.peer = undefined; this.peerReady = false; - this.ctx?.ui.notify("Goal supervision peer disconnected; reconnect the existing session.", "warning"); - if (this.ctx) this.onConnectionChange(this.ctx); - return; - } - if (event.type === "session_joined") { this.hello(); return; } - if (event.type !== "message") return; - const message = event.payload as Message; - if (!message || message.binding !== this.binding || message.role !== (this.role === "worker" ? "supervisor" : "worker")) return; - if (message.kind === "hello") { - if (this.peer && this.peer !== event.fromSessionId) throw new Error("Two peers claim this supervision binding. Stop the duplicate session."); - const changed = !this.peer || this.peerReady !== Boolean(message.ready); - this.peer = event.fromSessionId; - this.peerReady = Boolean(message.ready); - this.peerFailure = message.failure; - if (message.failure) { - this.ctx?.ui.notify(message.failure, "error"); - for (const wake of this.waiters) wake(new Error(message.failure)); - } - // Every request gets one reply, even if only the sender forgot its peer. - // Replies never elicit hellos; own-ready transitions also trigger replay here. - if (!message.reply) this.hello(true); - if (this.peerReady && this.ready) { - if (this.role === "worker" && this.latestView) this.publish({ binding: this.binding, role: this.role, kind: "view", ...this.latestView }); - for (const pending of this.pending.values()) this.publish(pending); - } - if (this.connected && this.inbox.size && !this.deliveryTimer) this.scheduleDelivery(0); - if (changed && this.ctx) this.onConnectionChange(this.ctx); - for (const wake of this.waiters) wake(); - return; - } - if (event.fromSessionId !== this.peer || !this.ready) return; - if (message.kind === "received") { - this.pending.delete(message.id); - const through = message.id === this.latestView?.id ? this.latestView.through : undefined; - if (through) this.acknowledgedEntry = through; - this.record("ack", { ...message, through }); - return; - } - if (this.received.has(message.id)) { - if (message.kind === "steer" || (message.kind === "view" && message.reason !== "started")) this.publish({ binding: this.binding, role: this.role, kind: "received", id: message.id }); - return; - } - if (message.kind === "view" && this.role === "supervisor") { - this.latestView = { id: message.id, text: message.text!, reason: message.reason!, through: message.through, backgroundQuiet: message.backgroundQuiet === true }; - if (message.reason === "started") { this.record("in", message); return; } - } else if (message.kind !== "steer" || this.role !== "worker") return; - if (this.inbox.has(message.id)) return; - if (this.inbox.size >= 64) { this.ctx?.ui.notify("Supervision inbox is full; message was not acknowledged. Use /goals reconnect after pending review finishes.", "error"); return; } - this.record("queued", message); - this.inbox.set(message.id, message); - if (this.compacting) this.ctx?.ui.notify("Supervision message retained during compaction; delivery will retry automatically.", "info"); - if (!this.delivering) this.deliverNext(); - } - private register(): void { - if (this.stopped || this.registered) return; - this.pi.events.emit("intercom:extension-register", { - namespace: "pi-goals", ownerEligible: false, - onReady: (channel: IntercomExtensionChannel) => { if (this.stopped) return; this.registered = true; this.channel = channel; this.hello(); }, - onEvent: (event: IntercomExtensionEvent) => { - try { this.receive(event); } - catch (error) { if (!this.stopped) this.ctx?.ui.notify(`Goal Intercom error: ${String(error)}`, "error"); } - }, - }); - } - private async loadIntercom(event: SessionStartEvent, ctx: ExtensionContext): Promise<void> { - if (this.pi.getAllTools().some(tool => tool.name === "intercom")) throw new Error("Installed pi-intercom has no extension channel; update it before continuing."); - const starts: Array<(event: SessionStartEvent, ctx: ExtensionContext) => unknown> = []; - const api = { ...this.pi, on: (name: string, handler: (...args: any[]) => any) => { - if (name === "session_start") starts.push(handler); - else this.pi.on(name as Parameters<ExtensionAPI["on"]>[0], handler); - } } as ExtensionAPI; - const { default: intercom } = await import("pi-intercom"); - intercom(api); - for (const start of starts) await start(event, ctx); - this.register(); - } -} diff --git a/src/prompts.ts b/src/prompts.ts index cb76ac9..3d50c8e 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -1,23 +1,4 @@ -/** - * pi-goals v2 — all model-facing text, in flow order. - * - * Design: the plan file is for LLMs and the human, not for TypeScript. No parser and no schema; - * the skeleton below is a convention the drafting prompt teaches. The main session implements it, - * while a visible forked Pi session supervises through pi-intercom. - * - * The worker resync receives the whole plan. Supervisor reviews receive outcome/preferences/goals; - * startup and compaction add the full active plan before appendices/history (see plan-view.ts). - * - * Flow: planning → worker resync → supervisor orientation → check-ins → steering → approval → - * worker sign-off. Dynamic gate errors stay beside their checks; these prompts ask for judgment. - * - * The goal's test is the DISCRIMINATOR: the concrete observation that tells real success from the - * named subtle failure mode. Evidence is empty at planning and filled at sign-off. - */ - -/* ───────────────────────────────────────────────────────────────────────── - * 1. planDrafting — SETUP, plan mode (read-only: edit/write blocked except the plan file) - * ──────────────────────────────────────────────────────────────────────── */ +// Pi/OpenAI: Planning, approval, supervision, reminders, completion and recovery. export const planDrafting = `\ You are in plan mode. Help the user express what they want this project to achieve in a short judgeable plan. Seek to understand their underlying goals, infer ordinary details, and use their applicable AGENTS.md instructions, relevant skills, and project context to interpret the request correctly. Do not silently substitute your own goals or expand the agreed scope. @@ -142,155 +123,15 @@ Conventions: When the goals are drafted, present them and say the plan is final. Do not begin execution.`; -export function planningState(planPath: string): string { - return `\ -[PLANNING MODE] -The plan at ${planPath} is the only file you may change. Use read-only repository tools or web search -when either can resolve a fact. Ask the human to confirm unresolved interpretation, outcome, task, -scope, or a choice that needs their approval. Batch independent high-impact questions in one short, -self-contained round with relevant context and a recommendation. Record unanswered questions as -unknown and still present Ready when the requested work is otherwise executable. Do not draft a -placeholder goal without a concrete object, observable result, settled scope, and required approval. Do not execute -work, mark a goal [/] or [x], or sign off a goal. The plan is not approved until the human selects -Ready.`; -} - -/* ───────────────────────────────────────────────────────────────────────── - * 2. resync — EXEC, one-shot at session start and after a compaction: the WHOLE file back, - * appendix included. Modelled on pi-goal-x's [POST-COMPACTION RESYNC] one-shot. This is the - * only place the below-the-fold sections are pushed; otherwise the agent reads them on demand. - * ──────────────────────────────────────────────────────────────────────── */ -export function resync(plan: string, planRel: string, why: string): string { - return `\ -<system-reminder> -${why} This is the whole plan file (${planRel}), appendix included. You are the implementation worker. -Keep the high-level goal and human intent stable and do the work directly. A visible read-only Pi -session supervises you through pi-intercom. The human's latest message outranks the plan: if it -changes scope, amend the plan rather than preserving an obsolete decision. - -${plan} -</system-reminder>`; -} - -/* ───────────────────────────────────────────────────────────────────────── - * 3. Supervisor orientation: short each review, full at startup/after compaction. - * ──────────────────────────────────────────────────────────────────────── */ -export function supervisorOpening(planPath: string): string { - return `Your job is to be a diligent supervisor, autonomously extending the user's agency by correctly understanding their goals and preferences. Supervise the worker according to ${planPath}, which the user helped write. Inspect and diagnose directly. Delegate changes to the worker through SteerWorker; do not take over implementation or alter shared state.`; -} - -// Pi/OpenAI: User intent/autonomy adapted from https://www.anthropic.com/constitution; outcome focus from @monotykamary/pi-supervisor. -export function supervisorPrompt(planPath: string): string { - return `${supervisorOpening(planPath)} - -At startup and after compaction, read the applicable AGENTS.md instructions and relevant skills to understand the user's goals, preferences, and working standards. Do not assume a particular project or workflow. Read the plan's appendices when needed. - -Understand the user's immediate request without interpreting it too literally or too liberally. Consider their final goals and the background standards and preferences the work should meet. Use good planning, taste, context, and high-level perspective. Infer ordinary implementation details, but do not silently replace the agreed outcome or invent restrictions. - -Protect the user's epistemic autonomy and rational agency. Make consequential uncertainty and disagreement visible. Respect their authorized decisions without requiring them to justify reasonable preferences; voice concerns without substituting your preferences for theirs. - -You are the visible pi-goals supervisor for ${planPath}. You are a stronger reviewer with normal Pi tools and extensions. Your inspection-only role is an instruction, not an enforced sandbox: tool availability does not authorize taking over the worker's changes. The other Pi session is the implementation worker and keeps the full conversation. You keep the high-level intent from the compacted planning conversation and worker views. The complete plan at ${planPath} is the source of truth; read it directly after every compaction. - -Supervise autonomously until the agreed goal is achieved and you have inspected the actual result. Use judgment: identify the missing user-visible result, decide the next useful action, and supervise it through to delivery. Approval records support this work; they are not the outcome. Seek justified confidence, not certainty at any cost. Investigate uncertainty with the cheapest useful check, then decide. Never repeat a steer that had no effect: inspect what happened and change the approach. Do not prolong completed work for optional polish. - -The worker stopping is not a reason for you to stop. Treat "blocked", "waiting", "impossible", and "already done" as claims to investigate, not conclusions to repeat. Check the evidence and whether the claimed dependency is real. Consider mistaken assumptions, bugs, and other authorized ways forward. If progress stalls, diagnose why and use SteerWorker to send a useful next instruction instead of repeating status checks. Keep independent work moving when it does not depend on the blocker. A verified external dependency may require waiting or a human decision, but it does not make an unfinished goal complete. - -Keep authorized work moving. Resolve technical choices within the agreed scope yourself. If idle with unfinished goals, use SteerWorker to resume useful work; a recap alone does not restart the worker. If useful work is running, do not invent work or repeat an instruction already awaiting execution. Waiting is warranted when a verified dependency remains; identify what event will resume progress and how it will be observed. Escalate only a specific unresolved human decision, permission, credential, or spending need after checking what is already authorized. Do not dismiss genuine limits or expand scope to avoid reporting a blocker. - -At each review, give a brief visible recap of how work is tracking against the goal: what the evidence shows and your judgment about the next step. Add perspective rather than repeating status. Distinguish observations from guesses. Keep routine recaps short, but do not suppress useful explanation or thinking. Do not edit files or execute the worker's work. - -Ground consequential judgments in verbatim evidence with a source path or link and enough surrounding context to check the interpretation. Keep the observation separate from your inference. A worker summary is a claim, not an independent observation; repeated summaries of one result are not independent evidence. Say what evidence would change your mind. Missing evidence stays unknown until you inspect where it should be. - -Check the actual deliverable against the user's goal. Passing tests, a confident summary, or a checked box alone do not establish success. Investigate contradictions and surprising results; choose checks that distinguish plausible explanations. Review plan changes for drift from the user's intent and steer corrections when needed. - -Only if the evidence establishes completion, use ApproveGoal and direct the worker to CompleteGoal. Otherwise send the next useful instruction with SteerWorker, or explain the verified dependency preventing progress. Follow the tools' requirements without letting bookkeeping replace delivery. Once the agreed work is complete, give a short assessment and stop. -- Pi/OpenAI`; -} - -export function supervisorReviewContext(planPath: string, shortPlan: string): string { - return `${supervisorOpening(planPath)}\n\nCurrent agreed plan (reread for every review):\n${shortPlan}\n\nJudge progress against this outcome and its discriminators. A completed artifact or task is not completion unless it satisfies the agreed goal.`; -} - -export function supervisorOrientation(planPath: string, fullPlan: string): string { - return `${supervisorPrompt(planPath)}\n\nFull active plan:\n${fullPlan}`; -} - -export function supervisorCompaction(planPath: string, initial: boolean): string { - return initial - ? `Preserve the user's high-level intent, decisions, unresolved risks, and the supervisor's remit. The canonical plan is ${planPath}; it remains available directly and must not be replaced by this summary.` - : `Keep the user's high-level intent, current plan state, unresolved risks, approval decisions, and the supervisor's own concise findings. Remove old worker views and implementation detail. The canonical plan remains ${planPath}.`; -} - -/* 4. Check-ins: decide whether work is on track, then act when needed. */ -export type SupervisorReviewReason = "ready" | "settled" | "turns" | "interval" | "started" | "plan"; - -export const supervisorReadyReview = "Check the agreed outcome and decide the next useful action. Use SteerWorker to send the worker a concrete starting instruction; do not repeat one already being acted on."; -export const supervisorStartedReview = "The worker has begun a turn. Check whether its direction fits the agreed goal; let productive work continue and use SteerWorker only if a correction is needed."; -export const supervisorPeriodicReview = "Is the worker on track toward the user's intended outcome? Check for drift, mistaken assumptions, or wasted effort. Use SteerWorker to send a correction where useful; otherwise let productive work continue without interruption."; -export const supervisorStoppedReview = "Inspect the results and judge whether the agreed goal is actually achieved. If unfinished, investigate why the worker stopped and use SteerWorker to send the next useful instruction and resume work. If a verified dependency prevents progress, establish what will resume it and how that will be observed. Do not treat stopping as completion. Consider ApproveGoal only after the results satisfy the goal."; -export const supervisorPlanChangeReview = "Assess plan changes against the user's intent and preferences. Manual checkbox edits are claims, not proof of completion. Inspect the actual result before accepting a claim; use SteerWorker to send corrections when the plan or work has drifted. Preserve authorized changes."; - -export function supervisorCheckIn(reason: SupervisorReviewReason, idle: boolean): string { - // These status prefixes are also read by approval checks; keep them unchanged. - const state = reason === "ready" ? "is ready to begin" : idle ? "stopped" : "is still working"; - const task = reason === "ready" ? supervisorReadyReview : idle ? supervisorStoppedReview : reason === "started" ? supervisorStartedReview : supervisorPeriodicReview; - return `The worker ${state}.\n\n${reason === "plan" ? `${supervisorPlanChangeReview}\n\n` : ""}${task}`; -} - -export function supervisorPlanReview(claims: string[], changes: string[], diff: string): string { - return `${supervisorPlanChangeReview}\nClaims awaiting supervisor judgment: ${claims.join(", ") || "none"}\nGoal-state changes:\n${changes.join("\n") || "none"}\nPlan diff since the previous published view:\n${diff}`; -} - -/* 5. Steering: a visible message is an assessment; this tool sends an actionable instruction. */ -export const steerWorkerDescription = "Send one concrete instruction to the implementation worker. Use it to resume useful work after a stop, request a needed check, or correct drift toward the agreed goal. A recap alone does not send an instruction. Do not interrupt productive work or repeat ineffective steering without changing the approach."; -export const steerWorkerInstructionDescription = "The next useful action and its purpose toward the agreed goal; include the check or result needed to assess progress."; -export function workerInstructionSent(id: string): string { - return `Worker instruction ${id} sent through pi-intercom. Receipt and execution are not confirmed by this result.`; -} - -/* 6. Approval: the supervisor's acceptance action AFTER judgment, not a request to judge. */ -export const approveGoalDescription = "Use only after judging that the actual result satisfies the user's intended outcome and the goal's discriminator. This tool records your acceptance; its mechanical checks cannot establish success. If the goal is unmet or evidence is insufficient, do not approve: use SteerWorker to request the next useful work or check.\n\nRequirements: inspect the current goal, repository, evidence, and a saved nonempty verification-output file, with a current stopped worker view and no active work. force overrides only dirty-worktree rejection and requires a reason; later Git/content changes invalidate approval."; -export const approveGoalParameters = { - goal: "Exact text after goal: in the plan, whose intended outcome you have judged achieved.", - verifyOutputPath: "Nonempty repository-relative file containing the verification output you inspected against the goal's discriminator.", - force: "Accept this exact inspected dirty worktree, without bypassing any other approval gate.", - reason: "Required with force:true. Why accepting this inspected worktree state is justified.", -}; -export function goalApprovalRecorded(goal: string, forced?: { reason: string; path: string }): string { - return `Approval recorded for "${goal}".${forced ? ` Forced worktree acceptance: ${forced.reason}. Exact status and content fingerprints saved in ${forced.path}; changes require fresh review.` : ""} Use SteerWorker to tell the worker to call CompleteGoal with this exact goal text. Continue supervising any remaining goals.`; -} - -/* 7. Worker sign-off: consume the supervisor's recorded approval. */ -export const completeGoalDescription = - "Worker-only sign-off after the visible supervisor has judged the goal achieved and recorded approval. " + - "If approval is absent, provide the result and evidence for review rather than calling this tool. " + - "First fill the goal's evidence: list in the " + - "plan file: each item pairs a durable artifact with a short read of it (a quoted+linked log, a " + - "table plus how to read it, a metric plus what it shows -- not a bare claim). Quote verbatim from " + - "output you actually observed; never reconstruct numbers from memory. If you couldn't see an " + - "output, rerun it or write that you couldn't -- an honest gap beats a plausible fabrication. If " + - "the goal names a verify: command, run it and save its output to a file cited in the evidence. " + - "The visible supervisor reads the actual result and saved output to judge whether the discriminator " + - "is satisfied, not merely whether tasks finished or files exist. The read must show success " + - "POSITIVELY happened, not just that failures were avoided. The " + - "supervisor records an approval checkpoint only after it inspected the current plan, repository, " + - "evidence, verify output, and a stopped worker view with no active work. Then the worker calls this " + - "tool with the exact goal text. This tool independently checks that checkpoint " + - "against the exact current goal block, HEAD/tree, and approved repository state before it appends the sign-off to " + - "## Log and ticks the goal [x]. If any check differs, it fails closed and requires a fresh supervisor review."; - -export const completeGoalParamDescription = "The goal's text: the line after 'goal:' in the plan file."; - -/* Package-based prototype, in flow order. Legacy exports above still serve src/index.ts. */ - // Planning and interview. Keep the full drafting guide one-shot rather than repeating it each turn. -export function prototypePlanning(planPath: string): string { +export function planning(planPath: string): string { return `Plan only in ${planPath}; do not implement or launch workers before Ready. Ask material unresolved questions, not a quota or confirmation of ordinary details. Record unknowns and present Ready when the outcome, scope and spending are settled. Preserve the user's exact deliverable, preferences and voice; give each distinct goal a failure mode, discriminator and evidence expectation above ## Log. Record the requested worker model in preferences. When your drafted plan is ready for human review, finish your turn; the interface displays the draft and approval choices automatically. Do not ask the user to type a command to see the proposal. /goals review reopens it on request; /goals exit preserves the draft.`; } -export function prototypePlanningSeed(objective: string, planPath: string): string { - return `Enter a planning conversation focused on the user's goals. ${objective ? `Initial idea: ${objective}.` : "Ask what the user wants to achieve; they do not need to supply a finished objective."} Read any existing plan at ${planPath} first, then discuss and draft it with the user. Do not infer approval to implement from starting this conversation. ${prototypePlanning(planPath)}\n\n${planDrafting}`; +export function planningSeed(objective: string, planPath: string): string { + return `Enter a planning conversation focused on the user's goals. ${objective ? `Initial idea: ${objective}.` : "Ask what the user wants to achieve; they do not need to supply a finished objective."} Read any existing plan at ${planPath} first, then discuss and draft it with the user. Do not infer approval to implement from starting this conversation. ${planning(planPath)}\n\n${planDrafting}`; } -export const prototypePlanDocument = (objective: string) => `# Goal plan\n\n## Objective\n${objective}\n\n## Goals\n\n## Log\n`; -export const prototypeDiscuss = "Discuss the current draft in ordinary chat. Do not launch a worker or reopen the review menu until requested."; +export const planDocument = (objective: string) => `# Goal plan\n\n## Objective\n${objective}\n\n## Goals\n\n## Log\n`; +export const discuss = "Discuss the current draft in ordinary chat. Do not launch a worker or reopen the review menu until requested."; // Ready and explicit child attachment: stock lineage-only sessions do not inherit the shared plan. export const attachGoalPlanDescription = "Delegated goals-worker only: attach the absolute plan path explicitly supplied in your task. Read it without rewriting it. Restores the worker widget and plan context; grants no parent completion authority. No discovery or worker launch."; @@ -303,15 +144,15 @@ export function readyApproved(workerName: string, planPath: string, notedWorker: } // Supervision and turn-event upkeep (not a scheduled wake-up). -export function prototypeSupervisor(workerName: string, planPath: string, supervisorId: string): string { +export function supervisor(workerName: string, planPath: string, supervisorId: string): string { return `You are the goal supervisor in the main chat for ${planPath}. Inspect actual artifacts, saved verification, applicable AGENTS.md and skills yourself; delegate implementation to '${workerName}'. Keep authorized work moving to the requested outcome, not merely approval paperwork. Investigate blocked/waiting/done claims and change ineffective instructions. Give brief visible assessments with judgment. You may maintain the plan but must not weaken the goal to accept worker output. Use stock subagent for launch and subagent_resume with the returned sessionFile only after confirming the worker stopped. A stored handle is not proof of liveness; missing runtime state is not proof it stopped. Use pi-intercom list/status to identify the actual live child session before live steering; receipt alone does not prove action. Give each worker your Intercom session ID ${supervisorId}; require its completion report through Intercom while its pane stays open. A recap alone sends no instruction. Record '- worker session:' and '- worker intercom session:' in plan preferences from actual launch results and received-message identity; never confuse the runtime ID with the Intercom ID. Ensure the child calls AttachGoalPlan with the supplied path. Inspect results before CompleteGoal, then continue only unfinished goals. Use the worker model requested in plan preferences, verify the resolved model, and report unavailable choices instead of silently substituting. Keep normal tools, not edxeth's restricted orchestrator mode. After reload or compaction reread the plan. Failed compaction, exhausted credits or lost connection do not erase progress: diagnose the actual error, restore an available authorized model/credits and resume the same saved session; never restart long work. Stock edxeth can crash the parent when a worker exits after parent reload: preserve drafts and stop workers before /reload. If it already happened, restart the saved parent session; do not repeat completed work.`; } -export function prototypeUpkeep(planPath: string): string { +export function upkeep(planPath: string): string { return `Plan upkeep: update task ticks, evidence and Log in ${planPath} when you have new progress to record. Preserve agreed goals and discriminators. If already reviewing evidence, finish that review rather than repeat a status recap. This turn-event reminder does not resume paused work.`; } -export function prototypePlanContext(mode: string, path: string | undefined, text: string): string { +export function planContext(mode: string, path: string | undefined, text: string): string { return `Current goal mode: ${mode}. Earlier role messages are historical; this current role governs.\nPlan: ${path ?? "not attached"}\n${text}`; } export function planChangedReview(planPath: string): string { @@ -330,8 +171,8 @@ export function scheduleCheckIn(sessionId: string, planPath: string): string { } // Completion and runtime errors. Tool returns are model-facing too. -export const prototypeCompleteGoalDescription = "Parent supervisor or solo self-verification only. Inspect the actual artifact and saved verification first; cite nonempty evidence files and describe what you observed. Exact goal subject required. Manual ticks and worker reports are claims; ignored/uncommitted evidence is allowed. This records judgment, not an independent judge."; -export const prototypeMessages = { +export const completeGoalDescription = "Parent supervisor or solo self-verification only. Inspect the actual artifact and saved verification first; cite nonempty evidence files and describe what you observed. Exact goal subject required. Manual ticks and worker reports are claims; ignored/uncommitted evidence is allowed. This records judgment, not an independent judge."; +export const messages = { noPlan: "no plan attached", emptyPlan: "empty plan (save may be in progress)", completionUnavailable: "Completion is available only to the active parent supervisor or solo worker.", diff --git a/src/prototype.ts b/src/prototype.ts deleted file mode 100644 index fe08750..0000000 --- a/src/prototype.ts +++ /dev/null @@ -1,437 +0,0 @@ -/** Opt-in prototype: the existing chat plans and supervises an edxeth interactive worker. */ -import { createHash } from "node:crypto"; -import { type FSWatcher, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs"; -import { dirname, isAbsolute, join, resolve } from "node:path"; -import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; -import { Type } from "typebox"; -import { foldPlan, GOAL_LINE } from "./plan.js"; -import { planViews } from "./plan-view.js"; -import { - attachGoalPlanDescription, - attachNotice, - childPlanAttached, - childPlanRole, - completionLog, - completionResult, - emptyEvidence, - evidenceUnavailable, - goalToolBlocked, - manualReview, - pausedRole, - pauseExitNotice, - planChangedReview, - planUnavailable, - prototypeCompleteGoalDescription, - prototypeDiscuss, - prototypeMessages, - prototypePlanContext, - prototypePlanDocument, - prototypePlanning, - prototypePlanningSeed, - prototypeSupervisor, - prototypeUpkeep, - readyApproved, - removeGoalSchedule, - resumeNotice, - scheduleCheckIn, - soloNotice, - soloRole, -} from "./prompts.js"; - -const STATE = "pi-goals-main-supervisor-v1"; -const WORKER = "goals-worker"; -type Mode = "chat" | "planning" | "supervising" | "paused" | "solo"; -type GoalStatus = "open" | "active" | "done" | "cancelled"; -interface State { - mode: Mode; - plan?: string; - worker?: { id?: string; sessionFile: string }; - workerStopped?: boolean; - signoffs: Record<string, { evidence: string[]; observation: string }>; - child?: boolean; -} -const initial = (): State => ({ mode: "chat", signoffs: {} }); -const digest = (text: string) => createHash("sha256").update(text).digest("hex"); -const key = (text: string) => text.trim().toLowerCase(); -function goals(text: string) { - return foldPlan(text).split("\n").flatMap((line, index) => { - const match = GOAL_LINE.exec(line); - if (!match) return []; - const box = match[1].toLowerCase(); - return [{ subject: match[2].trim(), status: (box === "x" ? "done" : box === "/" ? "active" : box === "-" ? "cancelled" : "open") as GoalStatus, index }]; - }); -} -const result = (text: string) => ({ content: [{ type: "text" as const, text }], details: {} }); - -export default function mainSupervisor(pi: ExtensionAPI) { - let state = initial(); - let generation = 0; - let workerRevision = 0; - let launchPending = false; - let notice = true; - let planWatcher: FSWatcher | undefined; - let planEditTimer: ReturnType<typeof setTimeout> | undefined; - let planHash = ""; - const childEnvironment = process.env.PI_SUBAGENT_AGENT === WORKER; - const save = () => pi.appendEntry(STATE, state); - // Missing, empty and failed reads are unavailable snapshots, never an empty authoritative plan. - const readPlan = () => { - try { - if (!state.plan) throw new Error(prototypeMessages.noPlan); - const text = readFileSync(state.plan, "utf8"); - if (!text.trim()) throw new Error(prototypeMessages.emptyPlan); - return { text }; - } catch (error) { return { error: planUnavailable(state.plan, error) }; } - }; - const planText = () => { - const snapshot = readPlan(); - if (snapshot.text === undefined) throw new Error(snapshot.error); - return snapshot.text; - }; - let turnsStale = 0; - let lastWorkingSet = ""; - const checkIn = (ctx: ExtensionContext) => scheduleCheckIn(ctx.sessionManager.getSessionId(), state.plan ?? ""); - const hasScheduleTool = () => pi.getAllTools().some((tool) => tool.name === "schedule_prompt"); - const notedPlanValue = (prefix: string) => { - const snapshot = readPlan(); - if (snapshot.text === undefined) return null; - const m = new RegExp(`^\\-\\s*${prefix}:\\s*(.+)$`, "im").exec(foldPlan(snapshot.text)); - return m?.[1]?.trim() ?? null; - }; - function refresh(ctx: ExtensionContext) { - if (state.mode === "chat") { ctx.ui.setStatus("goals", undefined); ctx.ui.setWidget("goals", undefined); return; } - const snapshot = readPlan(); - if (snapshot.text === undefined) { - ctx.ui.setStatus("goals", snapshot.error); - ctx.ui.setWidget("goals", [snapshot.error]); - return; - } - const items = goals(snapshot.text); - // Reopened/deleted/ambiguous goal identities lose their sign-off. Manual ticks remain claims. - for (const subject of Object.keys(state.signoffs)) { - const matches = items.filter((g) => key(g.subject) === subject); - if (matches.length !== 1 || matches[0].status !== "done") { delete state.signoffs[subject]; save(); } - } - const accepted = items.filter((g) => g.status === "done" && state.signoffs[key(g.subject)]).length; - ctx.ui.setStatus("goals", `goals: ${state.child ? "worker" : state.mode} | ${accepted}/${items.length} reviewed`); - const mark = (status: GoalStatus, signed: boolean) => status === "done" ? (signed ? "✓" : "?") : status === "active" ? "▸" : status === "cancelled" ? "✗" : "○"; - const lines: string[] = items.map((g) => `${mark(g.status, Boolean(state.signoffs[key(g.subject)]))} ${g.subject}`); - if (items.some((g) => g.status === "done" && !state.signoffs[key(g.subject)])) lines.push("? = completion claim; parent review still required"); - ctx.ui.setWidget("goals", lines); - } - function watchPlan(ctx: ExtensionContext) { - planWatcher?.close(); - planWatcher = undefined; - clearTimeout(planEditTimer); - planEditTimer = undefined; - const snapshot = readPlan(); - if (snapshot.text !== undefined) planHash = digest(planViews(snapshot.text).short); - if (state.child || state.mode !== "supervising" || !state.plan) return; - const stamp = generation; - // Watch the directory so atomic plan replacement remains observable. This is an event hook: - // plan-change reviews, not another scheduled loop (the hourly job is schedule_prompt's). A - // short debounce coalesces bursts. Existing high-level plan views exclude maintenance - // (tasks/evidence/Log) while preserving requirement wording and goal checkbox claims. - try { - planWatcher = watch(dirname(state.plan), { persistent: false }, () => { - if (stamp !== generation) return; - if (planEditTimer) clearTimeout(planEditTimer); - planEditTimer = setTimeout(() => { - planEditTimer = undefined; - if (stamp !== generation || state.mode !== "supervising") return; - const snapshot = readPlan(); - if (snapshot.text === undefined) { ctx.ui.notify(snapshot.error!, "warning"); return; } - refresh(ctx); - const hash = digest(planViews(snapshot.text).short); - if (hash === planHash) return; - planHash = hash; - notice = true; - send(planChangedReview(state.plan!)); - }, 150); - }); - planWatcher.on("error", (error) => { planWatcher?.close(); planWatcher = undefined; ctx.ui.notify(`Plan monitoring failed: ${error.message}`, "error"); }); - } catch (error) { ctx.ui.notify(`Plan monitoring unavailable: ${String(error)}`, "error"); } - } - function restore(ctx: ExtensionContext) { - generation++; - state = initial(); - for (const entry of ctx.sessionManager.getBranch()) { - if (entry.type === "custom" && entry.customType === STATE) state = structuredClone(entry.data as State); - } - if (childEnvironment) { - state.child = true; - state.mode = "solo"; - // Lineage-only workers attach the explicit task path using AttachGoalPlan. - save(); - } - notice = true; - turnsStale = 0; - lastWorkingSet = ""; - refresh(ctx); - watchPlan(ctx); - } - function compatible() { - const tools = pi.getAllTools(); - const properties = (name: string) => (tools.find((t) => t.name === name)?.parameters as { properties?: Record<string, unknown> } | undefined)?.properties; - return properties("subagent")?.title && properties("subagent")?.agent && properties("subagent_resume")?.sessionFile && properties("subagent_kill")?.id; - } - function send(content: string, triggerTurn = true) { - // sendMessage(triggerTurn:true) bypasses before_agent_start in Pi 0.85.1. - // A normal saved prompt prepares the current role before starting the turn. - if (triggerTurn) pi.sendUserMessage(`[pi-goals]\n${content}`, { deliverAs: "followUp" }); - else pi.sendMessage({ customType: "pi-goals-supervision", content, display: true }, { deliverAs: "followUp", triggerTurn: false }); - } - async function confirmOwnership(ctx: ExtensionContext, target: string, text: string, solo = true): Promise<boolean> { - if (launchPending) { ctx.ui.notify("Worker launch/resume is still pending; inspect its result before takeover.", "warning"); return false; } - const stamp = generation; - const revision = workerRevision; - const confirmation = solo ? "Worker confirmed stopped" : "Previous supervisor confirmed stopped"; - const choice = await ctx.ui.select(solo ? "Confirm all other writers for the current and target plans are stopped (inspect /subagents and their panes). A missing handle is not proof. Take over in this session?" : "Confirm no other supervisor owns this plan. Preserve any existing worker session and reconnect rather than starting another writer.", [confirmation, "Cancel"]); - if (stamp !== generation || revision !== workerRevision) return false; - if (choice !== confirmation) return false; - if (readFileSync(target, "utf8") !== text) { ctx.ui.notify("Plan changed during takeover; confirm again.", "warning"); return false; } - return true; - } - function enterSolo(ctx: ExtensionContext) { - state.mode = "solo"; state.workerStopped = true; - generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); - send(`${removeGoalSchedule(ctx.sessionManager.getSessionId())}\n\n${soloNotice(state.plan!)}`); - } - const help = "/goals new [initial idea] | review | ready | status | stop | resume | solo | exit | attach <plan.md> [solo] | model <model>\n/subagents opens the worker controls. Stop/exit pause this plan locally; worker termination must be confirmed through subagent_kill or its pane. No forced compaction or model switch; the worker pane's own model is chosen with /model in that pane. Hourly check-ins are one session-bound schedule_prompt job; plan-change reviews are the plan-watcher event hook."; - async function ready(ctx: ExtensionContext, menu: boolean) { - if (state.mode !== "planning") { ctx.ui.notify("Ready applies to a draft; use status or resume.", "warning"); return; } - const text = planText(); - const items = goals(text); - if (!items.length || new Set(items.map((g) => key(g.subject))).size !== items.length) { - ctx.ui.notify("Write a plan with distinct '- [ ] goal: ...' subjects before Ready.", "warning"); return; - } - const stamp = generation; - if (menu) { - const choice = await ctx.ui.select(`Review ${state.plan}`, ["Ready", "Discuss", "Edit", "Cancel"]); - if (stamp !== generation || digest(planText()) !== digest(text)) { ctx.ui.notify("Plan changed during review. Review it again.", "warning"); return; } - if (choice === "Discuss") { send(prototypeDiscuss); return; } - if (choice === "Edit") { - const edited = await ctx.ui.editor("Edit goal plan", text); - if (edited !== undefined && stamp === generation && planText() === text && state.plan) { writeFileSync(state.plan, edited); refresh(ctx); } - return; - } - if (choice !== "Ready") return; - } - if (!compatible()) { ctx.ui.notify("Requires edxeth/pi-subagents 2.9.x, not nicobailon/pi-subagents. Draft preserved; /goals solo is available.", "error"); return; } - state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); - send(`${checkIn(ctx)}\n\n${readyApproved(WORKER, state.plan!, state.worker?.sessionFile, text, ctx.sessionManager.getSessionId())}`); - } - - pi.on("session_start", (_e, ctx) => restore(ctx)); - pi.on("session_tree", (_e, ctx) => restore(ctx)); - pi.on("session_shutdown", () => { generation++; planWatcher?.close(); planWatcher = undefined; clearTimeout(planEditTimer); planEditTimer = undefined; }); - pi.on("session_compact", () => { notice = true; }); - pi.on("turn_end", (_event, ctx) => { - if (!["supervising", "solo"].includes(state.mode)) return; - const snapshot = readPlan(); - if (snapshot.text === undefined) { notice = true; return; } - const workingSet = foldPlan(snapshot.text); - turnsStale = workingSet === lastWorkingSet ? turnsStale + 1 : 0; - lastWorkingSet = workingSet; - refresh(ctx); - if (turnsStale === 8 && goals(snapshot.text).some(g => g.status === "open" || g.status === "active")) { - // Pi queues context-only messages until tool results are appended at turn_end. - // This reaches the next model call in a long run without triggering another run. - pi.sendMessage({ customType: "pi-goals-upkeep", content: prototypeUpkeep(state.plan!), display: false }, { triggerTurn: false }); - } - }); - pi.on("agent_end", (_e, ctx) => { refresh(ctx); if (!planWatcher && state.mode === "supervising") watchPlan(ctx); }); - let proposedDraft = ""; - let proposing = false; - pi.on("agent_settled", async (_e, ctx) => { - if (state.child || state.mode !== "planning" || !ctx.hasUI || proposing) return; - const text = planText(); - const version = `${state.plan}:${digest(text)}`; - if (!goals(text).length || version === proposedDraft) return; - proposedDraft = version; - proposing = true; - try { - pi.sendMessage({ customType: "goal-plan-proposal", content: text, display: true }, { triggerTurn: false }); - await ready(ctx, true); - } finally { proposing = false; } - }); - // No context hook. Historical message arrays, native checkpoints and model selection are untouched. - pi.on("before_agent_start", (event, ctx) => { - if (state.mode === "chat") return; - const snapshot = readPlan(); - if (snapshot.text === undefined) { - notice = true; // Retry resync on the next turn; do not consume a failed snapshot. - return { systemPrompt: `${event.systemPrompt}\n\n${state.child ? childPlanRole : ""}\n${snapshot.error}` }; - } - const role = state.child ? childPlanRole : state.mode === "supervising" - ? prototypeSupervisor(WORKER, state.plan!, ctx.sessionManager.getSessionId()) - : state.mode === "planning" ? prototypePlanning(state.plan!) : state.mode === "paused" ? pausedRole : soloRole; - const content = notice ? prototypePlanContext(state.child ? "worker" : state.mode, state.plan, snapshot.text) - : undefined; - if (content) turnsStale = 0; - notice = false; - return { systemPrompt: `${event.systemPrompt}\n\n${role}`, ...(content ? { message: { customType: "pi-goals-plan", content, display: false } } : {}) }; - }); - pi.on("tool_call", (event) => { - if (state.child || !["subagent", "subagent_resume"].includes(event.toolName)) return; - // Solo means this chat took over implementation: no concurrent writer may be delegated. - if (state.mode === "planning" || state.mode === "paused" || state.mode === "solo") return { block: true, reason: goalToolBlocked(state.mode) }; - if (state.plan) { launchPending = true; state.workerStopped = false; workerRevision++; save(); } - }); - pi.on("tool_result", (event) => { - if (state.child || !state.plan || !["subagent", "subagent_resume"].includes(event.toolName)) return; - launchPending = false; - if (event.isError) return; - const details = event.details as { id?: string; sessionFile?: string } | undefined; - if (details?.id && details.sessionFile) { state.worker = { id: details.id, sessionFile: details.sessionFile }; state.workerStopped = false; workerRevision++; save(); } - }); - - pi.registerCommand("goals", { - description: "Goal plan actions: new, review, ready, status, stop, resume, solo, attach, model, exit", - getArgumentCompletions: (prefix) => ["new", "review", "ready", "status", "stop", "resume", "solo", "attach", "model", "exit", "help"].filter((verb) => verb.startsWith(prefix)).map((verb) => ({ value: verb, label: verb })), - handler: async (args, ctx) => { - try { - if (state.child) { ctx.ui.notify("This is the delegated worker. Goal approval belongs to its parent.", "info"); return; } - let command = args.trim(); - if (!command) { - const actions = ["status — Show current plan", "new — New plan", "attach — Open an existing plan", "review — Review current plan", "ready — Approve draft", "stop — Pause work", "resume — Continue paused work", "solo — Work in this session", "model — Set worker model", "exit — Leave goal mode", "help — Show commands"]; - const before = generation; - const choice = await ctx.ui.select("Goal plan actions", actions); - if (!choice || before !== generation) return; - command = choice.split(" — ")[0]; - if (["attach", "model"].includes(command)) { - const value = await ctx.ui.editor(command === "attach" ? "Plan path (optional: solo)" : "Worker model (provider/model)", ""); - if (!value?.trim() || before !== generation) return; - command += ` ${value.trim()}`; - } - } - if (command === "help") { ctx.ui.notify(help, "info"); return; } - if (command === "status") { - refresh(ctx); - ctx.ui.notify([ - `Mode: ${state.mode}`, - `Plan: ${state.plan ?? "none"}`, - `Preferred worker model (plan): ${notedPlanValue("preferred worker model") ?? "not stated; use /goals model <model>"}`, - `Recorded worker session: ${state.worker?.sessionFile ?? "not recorded"}`, - notedPlanValue("worker session") ? `Worker session noted in plan: ${notedPlanValue("worker session")}` : "", - `Hourly check-in: schedule_prompt job ${JSON.stringify(`goals-${ctx.sessionManager.getSessionId()}`)} (list/remove via schedule_prompt; plan-change reviews are the plan-watcher event hook)`, - "Liveness is owned by edxeth; inspect /subagents.", - ].filter(Boolean).join("\n"), "info"); - return; - } - if (command === "review" && state.mode === "supervising") { notice = true; send(manualReview(state.plan ?? "")); return; } - if (command === "review" || command === "ready") { await ready(ctx, command === "review"); return; } - if (command === "model" || command.startsWith("model ")) { - if (!state.plan || !goals(planText()).length) { ctx.ui.notify("Register a goal plan first.", "warning"); return; } - const ref = command.slice("model".length).trim(); - if (!ref) { ctx.ui.notify("Use /goals model <provider/model>; no preference changed.", "info"); return; } - const lines = planText().split("\n"); - const pref = `- preferred worker model: ${ref || "(none specified)"}`; - const found = lines.findIndex((line) => /^-\s*preferred worker model:/i.test(line)); - if (found >= 0) lines[found] = pref; - else { const title = lines.findIndex((line) => /^#\s/.test(line)); lines.splice(title >= 0 ? title + 1 : 0, 0, pref); } - writeFileSync(state.plan, lines.join("\n")); - planHash = digest(planViews(planText()).short); - refresh(ctx); - ctx.ui.notify(ref ? `Preferred worker model set to ${ref} in plan preferences. The supervisor selects it at launch and verifies the resolved model; the worker pane's own model is chosen with /model in that pane.` : "Preferred worker model cleared.", "info"); - return; - } - if (command === "attach" || command.startsWith("attach ")) { - const rest = command.slice("attach".length).trim(); - const [raw, kind, extra] = rest.split(/\s+/); - const solo = kind === "solo"; - if (extra || (kind && !solo)) { ctx.ui.notify("Use /goals attach <path-to-plan.md> [solo].", "warning"); return; } - if (!raw) { ctx.ui.notify("Use /goals attach <path-to-plan.md> [solo].", "info"); return; } - const target = isAbsolute(raw) ? raw : resolve(ctx.cwd, raw); - let text: string; - try { text = readFileSync(target, "utf8"); } catch { ctx.ui.notify(`Cannot read plan at ${target}.`, "error"); return; } - if (!goals(text).length) { ctx.ui.notify(`${target} has no '- [ ] goal:' lines; attach a judgeable plan.`, "warning"); return; } - if (!solo && ((state.worker && !state.workerStopped) || state.mode === "supervising")) { ctx.ui.notify("Exit and resolve the existing worker before replacing the plan. The current plan is preserved.", "warning"); return; } - const noted = /^-\s*worker session:\s*(\S+)/im.exec(foldPlan(text))?.[1]; - if (!(await confirmOwnership(ctx, target, text, solo))) return; - const retained = target === state.plan ? state.signoffs : {}; - const worker = noted ? { sessionFile: resolve(ctx.cwd, noted) } : state.workerStopped ? state.worker : undefined; - state = { mode: solo ? "solo" : "planning", plan: target, signoffs: retained, worker, workerStopped: solo || (!noted && state.workerStopped) }; - generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); - if (solo) enterSolo(ctx); - else send(attachNotice(target, false, noted)); - return; - } - if (command === "stop" || command === "exit") { - if (state.mode === "planning") { - if (command === "stop") { ctx.ui.notify("A draft cannot pause; use /goals exit to leave planning with the draft preserved.", "warning"); return; } - // Planning exit must not get the model trapped re-planning or lose the draft. - state.mode = "chat"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); - ctx.ui.notify(`Planning exited; draft preserved at ${state.plan}. No implementation was approved or started. Reconnect with /goals attach ${state.plan}.`, "info"); - return; - } - state.mode = command === "stop" ? "paused" : "chat"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); - send(`${removeGoalSchedule(ctx.sessionManager.getSessionId())}\n\n${pauseExitNotice(state.worker, command === "exit")}`, Boolean(state.worker) || hasScheduleTool()); - return; - } - if (command === "resume") { - if (state.mode !== "paused" || !state.plan) { ctx.ui.notify("Only a paused approved plan can resume. A draft needs Ready.", "warning"); return; } - if (!compatible()) { ctx.ui.notify("edxeth tools unavailable; plan remains paused.", "error"); return; } - state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); - send(`${checkIn(ctx)}\n\n${resumeNotice(WORKER, state.plan, state.worker)}`); - return; - } - if (command === "solo") { - if (!state.plan || !goals(planText()).length) { ctx.ui.notify("Register a goal plan first.", "warning"); return; } - if (!(await confirmOwnership(ctx, state.plan, planText()))) return; - enterSolo(ctx); - return; - } - if (command !== "new" && !command.startsWith("new ")) { ctx.ui.notify(`Unknown or incomplete command. ${help}`, "warning"); return; } - const objective = command.slice(4).trim(); - if ((state.worker && !state.workerStopped) || state.mode === "supervising") { ctx.ui.notify("Exit and resolve the existing worker before replacing the plan. The current plan is preserved.", "warning"); return; } - const path = join(ctx.cwd, ".pi", "plan", `${ctx.sessionManager.getSessionId()}-main.md`); - mkdirSync(dirname(path), { recursive: true }); - // Never overwrite an earlier plan at this session path; the model can revise it after inspection. - try { writeFileSync(path, prototypePlanDocument(objective), { flag: "wx" }); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; } - state = { mode: "planning", plan: path, signoffs: {}, worker: state.worker, workerStopped: state.workerStopped }; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx); - send(prototypePlanningSeed(objective, path)); - } catch (error) { ctx.ui.notify(String(error), "error"); } - }, - }); - pi.registerTool({ - name: "AttachGoalPlan", label: "Attach delegated plan", description: attachGoalPlanDescription, - parameters: Type.Object({ path: Type.String() }), - async execute(_id, params, _signal, _update, ctx) { - if (!state.child) return result(prototypeMessages.childAttachOnly); - try { - if (!isAbsolute(params.path) || !goals(readFileSync(params.path, "utf8")).length) return result(prototypeMessages.invalidAttachment); - } catch { return result(prototypeMessages.invalidAttachment); } - state.plan = params.path; generation++; notice = true; save(); refresh(ctx); - return result(childPlanAttached(params.path)); - }, - }); - pi.registerTool({ - name: "CompleteGoal", label: "Review goal evidence", - description: prototypeCompleteGoalDescription, - parameters: Type.Object({ goal: Type.String(), evidence: Type.Array(Type.String(), { minItems: 1 }), observation: Type.String({ minLength: 1 }) }), - async execute(_id, params, signal, _update, ctx) { - if (state.child || !["supervising", "solo"].includes(state.mode)) return result(prototypeMessages.completionUnavailable); - if (signal?.aborted) return result(prototypeMessages.cancelled); - const snapshot = readPlan(); - if (snapshot.text === undefined) return result(snapshot.error!); - const text = snapshot.text; - const matches = goals(text).filter((g) => g.status !== "cancelled" && key(g.subject) === key(params.goal)); - if (matches.length !== 1 || !state.plan) return result(prototypeMessages.uniqueGoal); - const evidence = params.evidence.map((file) => isAbsolute(file) ? file : resolve(ctx.cwd, file)); - try { for (const file of evidence) if (!readFileSync(file).length) throw new Error(emptyEvidence(file)); } - catch (error) { return result(evidenceUnavailable(error)); } - const lines = text.split("\n"); - lines[matches[0].index] = lines[matches[0].index].replace(/\[[ xX/-]\]/, "[x]"); - let log = lines.findIndex(line => /^##\s+Log\s*$/i.test(line)); - if (log === -1) { lines.push("", "## Log"); log = lines.length - 1; } - lines.splice(log + 1, 0, "", completionLog(params.goal, params.observation, evidence, state.mode === "solo")); - writeFileSync(state.plan, `${lines.join("\n").trimEnd()}\n`); - state.signoffs[key(matches[0].subject)] = { evidence, observation: params.observation }; - planHash = digest(planViews(planText()).short); - save(); refresh(ctx); - const remaining = goals(planText()).some((goal) => goal.status !== "cancelled" && (goal.status !== "done" || !state.signoffs[key(goal.subject)])); - return result(completionResult(matches[0].subject, ctx.sessionManager.getSessionId(), remaining, state.mode === "solo")); - }, - }); -} diff --git a/src/role-models.ts b/src/role-models.ts deleted file mode 100644 index 3f0f706..0000000 --- a/src/role-models.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; -import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; - -export type ModelRole = "planning" | "worker" | "supervisor"; -interface Choice { provider: string; id: string } - -// Pi/OpenAI: Per-role model memory, adapted from cecb1e9; preferences stay in the project. -export class RoleModels { - private role?: ModelRole; - private ctx?: ExtensionContext; - private switching = false; - private stopped = false; - - constructor(private pi: ExtensionAPI) { - pi.on("session_shutdown", async () => { this.stopped = true; this.leave(); }); - pi.on("model_select", async (event) => { - if (this.switching || event.source === "restore" || !this.role || !this.ctx) return; - this.save(this.role, this.ctx, event.model); - }); - } - - leave(): void { this.role = undefined; } - - async enter(role: ModelRole, ctx: ExtensionContext, useCurrent = false): Promise<void> { - if (this.stopped) throw new Error("Role model session ended."); - this.role = role; - this.ctx = ctx; - let choice: Choice | undefined; - if (!useCurrent) { - try { choice = JSON.parse(readFileSync(this.path(role, ctx), "utf8")); } - catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } - } - if (choice && (!choice.provider || !choice.id)) throw new Error(`Invalid saved ${role} model.`); - const model = choice ? ctx.modelRegistry.find(choice.provider, choice.id) : ctx.model; - if (!model) throw new Error(`${role} model is unavailable. Select an available model with /model, then retry. Saved choice was not replaced.`); - this.switching = true; - try { - if (!await this.pi.setModel(model)) throw new Error(`${role} model ${model.provider}/${model.id} is unavailable or unauthenticated. Saved choice was not replaced.`); - if (this.stopped) throw new Error("Role model session ended."); - this.save(role, ctx, model); - ctx.ui.notify(`${role} model: ${model.provider}/${model.id}`, "info"); - } finally { this.switching = false; } - } - - private path(role: ModelRole, ctx: ExtensionContext): string { - return join(ctx.cwd, ".pi", "pi-goals", "models", `${role}.json`); - } - private save(role: ModelRole, ctx: ExtensionContext, model: Choice): void { - const path = this.path(role, ctx); - mkdirSync(join(ctx.cwd, ".pi", "pi-goals", "models"), { recursive: true }); - const temporary = `${path}.${process.pid}.tmp`; - writeFileSync(temporary, `${JSON.stringify({ provider: model.provider, id: model.id })}\n`); - renameSync(temporary, path); - } -} diff --git a/src/supervisor-session.ts b/src/supervisor-session.ts deleted file mode 100644 index 9403178..0000000 --- a/src/supervisor-session.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; -import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; -import { Text } from "@earendil-works/pi-tui"; -import { Type } from "typebox"; -import { approvalPath, goalBlock, hashGoalBlock, repositoryState, verifyOutputPath, writeApproval } from "./approval.js"; -import { GoalIntercom } from "./intercom.js"; -import { planViews } from "./plan-view.js"; -import { approveGoalDescription, approveGoalParameters, goalApprovalRecorded, steerWorkerDescription, steerWorkerInstructionDescription, supervisorCompaction, supervisorOrientation, supervisorReviewContext, workerInstructionSent } from "./prompts.js"; -import { RoleModels } from "./role-models.js"; - -const BOOTSTRAPPED = "pi-goals-visible-supervisor-v2"; -const COMPACT_AT_TOKENS = 100_000; - -const ROLE_STATE = "pi-goals-supervisor-binding"; - -export interface SupervisorConfig { - workerSessionId: string; - ownerSessionId: string; - planPath: string; - approvalId: string; -} - -function result(text: string, isError = false) { - return { content: [{ type: "text" as const, text }], details: {}, isError }; -} - -function requiredEnv(name: string): string { - const value = process.env[name]?.trim(); - if (!value) throw new Error(`${name} is required in a pi-goals supervisor session.`); - return value; -} - -function config(): SupervisorConfig { - return { - workerSessionId: requiredEnv("PI_GOALS_WORKER_ID"), - ownerSessionId: requiredEnv("PI_GOALS_OWNER_SESSION_ID"), - planPath: resolve(requiredEnv("PI_GOALS_PLAN_PATH")), - approvalId: requiredEnv("PI_GOALS_APPROVAL_ID"), - }; -} - -function hasEvidenceEntry(block: string): boolean { - const lines = block.split("\n"); - for (let index = 0; index < lines.length; index++) { - const evidence = /^\s*[-*]\s+evidence:\s*(.*)$/i.exec(lines[index]); - if (!evidence) continue; - if (evidence[1].trim() && !/^\(empty until sign-off\)$/i.test(evidence[1].trim())) return true; - const indent = lines[index].match(/^\s*/)?.[0].length ?? 0; - for (let child = index + 1; child < lines.length; child++) { - const childIndent = lines[child].match(/^\s*/)?.[0].length ?? 0; - if (lines[child].trim() && childIndent <= indent) break; - const entry = /^\s+[-*]\s+(.+?)\s*$/.exec(lines[child]); - if (entry?.[1].trim() && !/^\(empty until sign-off\)$/i.test(entry[1].trim())) return true; - } - } - return false; -} - -function latestWorkerView(ctx: ExtensionContext): string | null { - for (const entry of [...ctx.sessionManager.getBranch()].reverse()) { - const message = (entry as { type?: string; message?: { role?: string; content?: unknown[] } }).message; - if ((entry as { type?: string }).type !== "message" || message?.role !== "user" || !Array.isArray(message.content)) continue; - for (const part of message.content) { - const text = (part as { type?: string; text?: string }).type === "text" ? (part as { text?: string }).text : undefined; - if (text?.startsWith("The worker ")) return text; - } - } - return null; -} - -export function isVisibleSupervisor(): boolean { - return process.env.PI_GOALS_ROLE === "supervisor"; -} - -export function restoredSupervisor(entries: readonly { type: string; customType?: string; data?: unknown }[]): SupervisorConfig | undefined { - const savedEntry = entries.filter(entry => entry.type === "custom" && entry.customType === ROLE_STATE).at(-1); - if (savedEntry) { - const saved = savedEntry.data as SupervisorConfig | undefined; - if (!saved || ![saved.workerSessionId, saved.ownerSessionId, saved.planPath, saved.approvalId].every(value => typeof value === "string" && value.trim())) throw new Error("Saved supervisor binding is incomplete. Restore its original session metadata before resuming; worker mode was not enabled."); - return saved; - } - const legacyEntry = entries.filter(entry => entry.type === "custom" && entry.customType === BOOTSTRAPPED).at(-1); - if (!legacyEntry) return; - const legacy = legacyEntry.data as Partial<SupervisorConfig> | undefined; - const binding = entries.filter(entry => entry.type === "custom" && entry.customType === "pi-goals-state").map(entry => entry.data as { approvalId?: string }).filter(state => state?.approvalId).at(-1)?.approvalId; - if (!legacy || ![legacy.workerSessionId, legacy.planPath, binding].every(value => typeof value === "string" && value.trim())) throw new Error("Saved supervisor role has no complete binding. Restore its original session metadata before resuming. Worker mode was not enabled."); - return { workerSessionId: legacy.workerSessionId!, ownerSessionId: legacy.workerSessionId!, planPath: legacy.planPath!, approvalId: binding! }; -} - -export function registerVisibleSupervisor(pi: ExtensionAPI, restored?: SupervisorConfig): void { - const settings = restored ?? config(); - let compacting = false; - let startupTimer: ReturnType<typeof setTimeout> | undefined; - let startupChecks = 0; - pi.on("session_shutdown", async () => { if (startupTimer) clearTimeout(startupTimer); }); - let repeatFullPrompt = true; - pi.on("session_compact", async () => { repeatFullPrompt = true; }); - let bootstrapping = false; - let warnedUnknownUsage = false; - let modelError: string | null = null; - const intercom = new GoalIntercom(pi); - const models = new RoleModels(pi); - intercom.onView = (view) => pi.sendUserMessage(view.text, { deliverAs: "followUp" }); - pi.on("session_compact_failed", async event => { - if (!startupTimer) return; - clearTimeout(startupTimer); - startupTimer = undefined; - intercom.failReady(`${event.aborted ? "Supervisor compaction was cancelled" : event.errorMessage ?? "Supervisor compaction failed"}. Use /goals supervise in its session to retry.`); - }); - - const bootstrap = async (ctx: ExtensionContext): Promise<void> => { - if (bootstrapping || intercom.ended) return; - const entries = ctx.sessionManager.getEntries(); - bootstrapping = true; - try { - if (!entries.some((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === BOOTSTRAPPED)) { - pi.appendEntry(BOOTSTRAPPED, { version: 2, workerSessionId: settings.workerSessionId, planPath: settings.planPath }); - } - intercom.markReady(); - } catch (error) { - ctx.ui.notify(`Supervisor startup failed: ${error instanceof Error ? error.message : String(error)}`, "error"); - } finally { bootstrapping = false; } - }; - - const bootstrapAfterInitialCompaction = (ctx: ExtensionContext): void => { - if (intercom.ended) return; - // A reload does not terminate Pi's in-flight compaction. Never start a competing one. - if (!ctx.isIdle()) { - if (++startupChecks > 300) { intercom.failReady("Supervisor startup is still busy. Inspect its session, then use /goals supervise to retry."); return; } - startupTimer = setTimeout(() => bootstrapAfterInitialCompaction(ctx), 1000); - return; - } - startupTimer = undefined; - const tokens = ctx.getContextUsage()?.tokens; - const resumed = ctx.sessionManager.getEntries().some((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === BOOTSTRAPPED); - const latestContext = ctx.sessionManager.getEntries().filter(entry => entry.type === "message" || entry.type === "compaction").at(-1); - if (resumed || latestContext?.type === "compaction" || (typeof tokens === "number" && tokens < COMPACT_AT_TOKENS)) { - void bootstrap(ctx); - return; - } - compacting = true; - ctx.compact({ - customInstructions: supervisorCompaction(settings.planPath, true), - onComplete: () => { - compacting = false; - if (intercom.ended) return; - ctx.ui.notify("Supervisor planning context compacted before work started.", "info"); - void bootstrap(ctx); - }, - onError: (error) => { - compacting = false; - if (intercom.ended) return; - if (/^(Already compacted|Nothing to compact)/.test(error.message)) { void bootstrap(ctx); return; } - intercom.failReady(`Supervisor startup compaction failed: ${error.message}. Use /goals reconnect in the supervisor pane.`); - ctx.ui.notify(`Supervisor startup compaction failed: ${error.message}`, "error"); - }, - }); - }; - - const start = async (ctx: ExtensionContext): Promise<void> => { - if (startupTimer) clearTimeout(startupTimer); - startupTimer = undefined; - startupChecks = 0; - modelError = "Supervisor model restoration is pending."; - if (!ctx.sessionManager.getEntries().some(entry => entry.type === "custom" && entry.customType === ROLE_STATE)) pi.appendEntry(ROLE_STATE, settings); - intercom.configure(settings.approvalId, "supervisor", ctx); - try { - await models.enter("supervisor", ctx, process.env.PI_GOALS_MODEL_EXPLICIT === "1"); - modelError = null; - setImmediate(() => { if (!intercom.ended) bootstrapAfterInitialCompaction(ctx); }); - } catch (error) { - modelError = String(error); - if (!intercom.ended) { - intercom.failReady(`Supervisor paused: ${modelError}. Select /model in its session, then /goals supervise.`); - ctx.ui.notify(`Supervisor paused: ${modelError} Select /model, then /goals reconnect.`, "error"); - } - } - }; - pi.on("session_start", async (_event, ctx) => start(ctx)); - pi.registerCommand("goals", { - description: "Reconnect this saved supervisor role and pairing: /goals supervise (or reconnect)", - handler: async (args, ctx) => { - if (!["reconnect", "supervise"].includes(args.trim())) { ctx.ui.notify("This is the supervisor session. Use /goals supervise here; /goals work and /goals noplan belong to the worker. No role or plan was changed.", "info"); return; } - if (!ctx.isIdle() || compacting) { ctx.ui.notify("Wait for the supervisor to settle before reconnecting.", "warning"); return; } - await start(ctx); - }, - }); - pi.on("before_agent_start", async (_event, ctx) => { - const plan = planViews(readFileSync(settings.planPath, "utf8")); - const message = repeatFullPrompt ? { customType: "pi-goals-supervisor-role", content: supervisorOrientation(settings.planPath, plan.long), display: true } : undefined; - repeatFullPrompt = false; - return { - systemPrompt: `${ctx.getSystemPrompt()}\n\n${supervisorReviewContext(settings.planPath, plan.short)}`, - ...(message ? { message } : {}), - }; - }); - pi.on("agent_settled", async (_event, ctx) => { - if (compacting) return; - const usage = ctx.getContextUsage(); - if (!usage && !warnedUnknownUsage) { - warnedUnknownUsage = true; - ctx.ui.notify("Supervisor context usage unavailable; the custom 100k compaction trigger cannot be checked. Pi's default auto-compaction is unchanged.", "warning"); - } - // Pi reports tokens:null after compaction until a fresh assistant usage sample. - if (typeof usage?.tokens !== "number" || usage.tokens < COMPACT_AT_TOKENS) return; - compacting = true; - ctx.compact({ - customInstructions: supervisorCompaction(settings.planPath, false), - onComplete: () => { - compacting = false; - if (intercom.ended) return; - ctx.ui.notify("Supervisor context compacted at 100k tokens.", "info"); - }, - onError: (error) => { - compacting = false; - if (intercom.ended) return; - ctx.ui.notify(`Supervisor compaction failed: ${error.message}`, "error"); - }, - }); - }); - - pi.registerTool({ - name: "SteerWorker", - label: "Steer worker", - executionMode: "sequential", - description: steerWorkerDescription, - parameters: Type.Object({ instruction: Type.String({ description: steerWorkerInstructionDescription }) }), - renderCall(args, theme) { - return new Text(`${theme.fg("toolTitle", "Supervisor → worker")}\n${args.instruction ?? ""}`, 0, 0); - }, - async execute(_id, params) { - if (modelError) return result(`Supervisor paused: ${modelError} Use /model, then /goals reconnect.`, true); - const instruction = params.instruction.trim(); - if (!instruction) return result("A worker instruction cannot be empty.", true); - const id = intercom.steer(instruction); - return result(workerInstructionSent(id)); - }, - }); - - pi.registerTool({ - name: "ApproveGoal", - label: "Approve goal", - executionMode: "sequential", - description: approveGoalDescription, - parameters: Type.Object({ - goal: Type.String({ description: approveGoalParameters.goal }), - verifyOutputPath: Type.String({ description: approveGoalParameters.verifyOutputPath }), - force: Type.Optional(Type.Boolean({ description: approveGoalParameters.force })), - reason: Type.Optional(Type.String({ description: approveGoalParameters.reason })), - }), - async execute(_id, params, _signal, _onUpdate, ctx) { - if (modelError) return result(`Supervisor paused: ${modelError} Use /model, then /goals reconnect.`, true); - const force = params.force === true; - const reason = params.reason?.trim(); - if (force && !reason) return result("Cannot force approval without an explicit nonempty reason for accepting this worktree state.", true); - const view = latestWorkerView(ctx); - const newest = intercom.latestView; - if (!intercom.connected) return result("Cannot approve: worker supervision is disconnected or not ready. Restore the existing connection before review.", true); - if (!newest) return result("Cannot approve: no worker view has arrived.", true); - if (newest.reason === "started") return result("Cannot approve while the worker is starting or running. Wait for its stopped-worker view.", true); - if (view !== newest.text) return result("Cannot approve this older worker view. A newer view is queued for you; finish this response to receive it. Do not ask the worker to generate another handoff merely to refresh this review.", true); - if (!view?.startsWith("The worker stopped.")) return result("Cannot approve without a current stopped-worker view.", true); - if (!newest.backgroundQuiet) return result("Cannot approve while tracked background work is active or unknown.", true); - const pendingTool = view.match(/^tool calls with no result: (?!none$)(.+)$/m); - const pendingChild = view.match(/^child pi processes still running: (?!none$)(.+)$/m); - if (pendingTool || pendingChild) return result(`Cannot approve while work is active: ${(pendingTool ?? pendingChild)![1]}`, true); - let plan: string; - let repository: ReturnType<typeof repositoryState>; - try { - plan = readFileSync(settings.planPath, "utf8"); - repository = repositoryState(ctx.cwd, force); - } catch (error) { - return result(`Cannot inspect approval inputs: ${error instanceof Error ? error.message : String(error)}`, true); - } - if (!repository.cleanWorktree && !force) return result("Cannot approve with a dirty worktree. Commit only the worker's changes, or inspect preserved changes and use force:true with a reason. Do not commit unrelated changes to satisfy this gate.", true); - const block = goalBlock(plan, params.goal); - if (!block) return result(`Cannot approve: no unique open goal matches "${params.goal}".`, true); - if (!hasEvidenceEntry(block)) return result("Cannot approve without a nonblank evidence entry in the goal block.", true); - const verifiedOutput = verifyOutputPath(repository.repoRoot, params.verifyOutputPath); - if (!verifiedOutput) return result("Cannot approve without a nonempty repository-relative verification-output file.", true); - const path = approvalPath(ctx.cwd, settings.ownerSessionId, params.goal); - writeApproval(path, { - version: 3, verdict: "accept", approvalId: settings.approvalId, goal: params.goal, planPath: settings.planPath, - goalBlockHash: hashGoalBlock(block), repoRoot: repository.repoRoot, head: repository.head, tree: repository.tree, - cleanWorktree: repository.cleanWorktree, ...(force ? { force: { reason: reason!, worktree: repository.worktree! } } : {}), inspected: { plan: true, repository: true, evidence: true, verifyOutput: true }, verifyOutputPath: verifiedOutput, - supervisor: { sessionId: ctx.sessionManager.getSessionId(), runId: null }, timestamp: new Date().toISOString(), - }); - return result(goalApprovalRecorded(params.goal, force ? { reason: reason!, path } : undefined)); - }, - }); -} diff --git a/src/vcc-package.d.ts b/src/vcc-package.d.ts deleted file mode 100644 index 64dbd13..0000000 --- a/src/vcc-package.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -// VCC 0.5.0 ships source only; describe its compiler boundary without typechecking upstream internals. -declare module "@sting8k/pi-vcc/src/core/summarize" { - import type { Message } from "@earendil-works/pi-ai"; - export interface CompileInput { - messages: Message[]; - previousSummary?: string; - fileOps?: { readFiles?: string[]; modifiedFiles?: string[]; createdFiles?: string[] }; - } - export function compile(input: CompileInput): string; -} diff --git a/src/worker-view.ts b/src/worker-view.ts deleted file mode 100644 index eb5ae54..0000000 --- a/src/worker-view.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { compile } from "@sting8k/pi-vcc/src/core/summarize"; -import { type SupervisorReviewReason, supervisorCheckIn } from "./prompts.js"; - -export interface SessionBlock { - type?: string; - id?: string; - name?: string; - text?: string; - thinking?: string; - arguments?: Record<string, unknown>; -} - -export interface SessionMessage { - role?: string; - content?: string | SessionBlock[]; - toolCallId?: string; - toolName?: string; -} - -export interface SessionEntry { - id?: string; - type?: string; - summary?: string; - message?: SessionMessage; -} - -// VCC drops reasoning. Preserve only two recent tails, in place beside the actions they inform. -function recentThinking(messages: SessionMessage[]): SessionMessage[] { - let remaining = 2; - return messages.map(message => ({ - ...message, - // Unanswered/partial calls may have no arguments yet; VCC expects an argument object. - content: Array.isArray(message.content) ? message.content.map(block => block.type === "toolCall" ? { ...block, arguments: block.arguments ?? {} } : { ...block }) : message.content, - })).reverse().map(message => { - if (Array.isArray(message.content)) { - for (const block of [...message.content].reverse()) { - if (block.type === "thinking" && block.thinking && remaining > 0) { - block.type = "text"; - block.text = `(thinking) ${block.thinking.slice(-400)}`; - remaining--; - } - } - } - return message; - }).reverse(); -} - -function compiledView(messages: SessionMessage[]): string { - // Only role/content/tool fields are read by normalize; Pi usage/provider metadata is irrelevant. - const compiled = compile({ messages: recentThinking(messages) as Parameters<typeof compile>[0]["messages"] }) - .replace(/\n*-*\n*Use `vcc_recall`[\s\S]*$/, "").trim(); - const separator = compiled.indexOf("\n\n---\n\n"); - // Keep extracted context and newest actions separately: a long brief must not evict all headers. - if (/^\[(Session Goal|Files And Changes|Commits|Outstanding Context|User Preferences)\]/.test(compiled)) { - if (separator < 0) return bounded(compiled, 1500); - return `${bounded(compiled.slice(0, separator), 1500)}\n\n${bounded(compiled.slice(separator + 7), 4000, true)}`; - } - return bounded(compiled || (messages.length ? "No overview text retained from these messages." : "No new messages."), 5500, true); -} - -function outstandingTools(entries: SessionEntry[]): string[] { - const calls = new Map<string, string>(); - const results = new Set<string>(); - for (const entry of entries) { - for (const block of Array.isArray(entry.message?.content) ? entry.message.content : []) { - if (block.type === "toolCall" && block.id) calls.set(block.id, block.name ?? "unknown"); - } - if (entry.message?.role === "toolResult" && entry.message.toolCallId) results.add(entry.message.toolCallId); - } - return [...calls].filter(([id]) => !results.has(id)).map(([, name]) => name); -} - -function bounded(value: string, bytes: number, tail = false): string { - if (Buffer.byteLength(JSON.stringify(value)) <= bytes) return value; - let size = Math.min(value.length, bytes - 100); - while (Buffer.byteLength(JSON.stringify(tail ? value.slice(-size) : value.slice(0, size))) > bytes - 100) size = Math.floor(size * 0.8); - const notice = "[truncated; inspect source session]"; - return tail ? `${notice}\n${value.slice(-size)}` : `${value.slice(0, size)}\n${notice}`; -} - -export interface ViewContext { - sourceSession: string; - latestDirection: string; - model: string; - contextPercent?: number | null; - since?: string; - background: string; - planReview?: string; -} - -export function workerView(entries: SessionEntry[], reason: SupervisorReviewReason, idle: boolean, context: ViewContext): string { - const compactAt = entries.map(entry => entry.type).lastIndexOf("compaction"); - const since = context.since ? entries.findIndex(entry => entry.id === context.since) : -1; - const from = since >= compactAt ? since + 1 : compactAt + 1; - const fresh = entries.slice(from); - const recent = compiledView(fresh.flatMap(entry => entry.type === "message" && entry.message ? [entry.message] : [])); - const summary = since < compactAt ? entries[compactAt]?.summary : undefined; - const outstanding = outstandingTools(entries.slice(compactAt + 1)); - return `${supervisorCheckIn(reason, idle)}\n\nreview trigger: ${reason}\nsource session: ${bounded(context.sourceSession, 800)}\nworker model: ${bounded(context.model, 300)}${context.contextPercent == null ? "" : `; context used: ${context.contextPercent}%`}\nlatest human direction:\n${bounded(context.latestDirection || "not recorded", 1800)}\ntool calls with no result: ${bounded(outstanding.join(", ") || "none", 500)}\ntracked background work: ${bounded(context.background, 800)}\n\n${context.planReview ? `Plan review:\n${bounded(context.planReview, 1800)}\n\n` : ""}${summary ? `compaction summary (worker account, not independent evidence):\n${bounded(summary, 2500)}\n\n` : ""}new worker overview${since === -1 ? " (initial or reset view)" : " since the last acknowledged view"} (VCC algorithmic compression; local # refs index new messages; tool-result bodies omitted; inspect source for evidence):\n${recent}`; -} diff --git a/test/append-log.test.ts b/test/append-log.test.ts deleted file mode 100644 index 2925d6f..0000000 --- a/test/append-log.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { appendLog } from "../src/index.js"; - -describe("appendLog (the extension's only plan-file write)", () => { - it("creates ## Log at EOF when absent", () => { - const out = appendLog("# Plan\n\n## Goals\n\n1. [ ] goal: x\n", "2026-07-03 10:00 signed off \"x\" (judge accept)"); - expect(out).toContain("## Log\n- 2026-07-03 10:00 signed off"); - }); - - it("appends after the last existing log line, before any following header", () => { - const plan = "# Plan\n\n## Log\n- first\n- second\n\n# Future work\n- later\n"; - const out = appendLog(plan, "third"); - const lines = out.split("\n"); - expect(lines[lines.indexOf("- second") + 1]).toBe("- third"); - expect(out.indexOf("- third")).toBeLessThan(out.indexOf("# Future work")); - }); -}); diff --git a/test/approval.test.ts b/test/approval.test.ts deleted file mode 100644 index 7728443..0000000 --- a/test/approval.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { chmodSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { expect, it } from "vitest"; -import { goalBlock, hashGoalBlock, repositoryState } from "../src/approval.js"; - -it("fingerprints literal unusual paths, binary bytes, symlink targets, modes and deletions", () => { - const cwd = mkdtempSync(join(tmpdir(), "goals-fingerprint-")); - const git = (...args: string[]) => execFileSync("git", args, { cwd }); - try { - git("init", "-q"); - writeFileSync(join(cwd, "tracked"), "original"); git("add", "."); - git("-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-qm", "base"); - const path = " white space\nname "; - writeFileSync(join(cwd, path), Buffer.from([0, 255, 42])); - symlinkSync("absent target", join(cwd, "link")); - const first = repositoryState(cwd, true).worktree!; - expect(first.files.map(file => file.path)).toContain(path); - expect(first.files.find(file => file.path === "link")?.kind).toBe("symlink"); - writeFileSync(join(cwd, path), Buffer.from([0, 254, 42])); - const changed = repositoryState(cwd, true).worktree!; - expect(changed.status).toBe(first.status); - expect(changed.files).not.toEqual(first.files); - chmodSync(join(cwd, path), 0o700); - expect(repositoryState(cwd, true).worktree?.files).not.toEqual(changed.files); - rmSync(join(cwd, "link")); symlinkSync("other target", join(cwd, "link")); - expect(repositoryState(cwd, true).worktree?.files.find(file => file.path === "link")?.contentHash).not.toBe(first.files.find(file => file.path === "link")?.contentHash); - rmSync(join(cwd, "tracked")); - expect(repositoryState(cwd, true).worktree?.files).toContainEqual({ path: "tracked", kind: "missing" }); - } finally { rmSync(cwd, { recursive: true, force: true }); } -}); - -it("hashes only the current goal, excluding the log, interview, and their historical goal text", () => { - const goal = "1. [ ] goal: output\n - evidence: output.txt\n"; - const before = `${goal}\n## Log\n- first entry\n`; - const after = `${goal}\n## Log\n- later entry\n\n${goal}\n## Interview\n> new notes\n`; - expect(goalBlock(before, "output")).toBe(goal.trimEnd()); - expect(hashGoalBlock(goalBlock(before, "output")!)).toBe(hashGoalBlock(goalBlock(after, "output")!)); - expect(goalBlock(`${goal}\n## Interview\n> notes`, "output")).toBe(goal.trimEnd()); - expect(goalBlock(`${goal}2. [ ] goal: second\n - evidence: second.txt`, "output")).toBe(goal.trimEnd()); - expect(hashGoalBlock(goalBlock(before.replace("output.txt", "changed.txt"), "output")!)).not.toBe(hashGoalBlock(goalBlock(before, "output")!)); -}); diff --git a/test/background.test.ts b/test/background.test.ts deleted file mode 100644 index e49ae4d..0000000 --- a/test/background.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { EventEmitter } from "node:events"; -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { expect, it, vi } from "vitest"; -import { backgroundState } from "../src/background.js"; - -function api(tools: string[], processes?: unknown, subagents?: number) { - const bus = new EventEmitter(); - if (processes !== undefined) bus.on("processes:request:list", request => request.reply(processes)); - if (subagents !== undefined) bus.on("subagents:rpc:v1:request", request => bus.emit(`subagents:rpc:v1:reply:${request.requestId}`, { requestId: request.requestId, success: true, data: { fleet: { version: 1, totalActive: subagents } } })); - return { getAllTools: () => tools.map(name => ({ name })), events: { emit: (name: string, value: unknown) => bus.emit(name, value), on: (name: string, fn: (...args: any[]) => void) => { bus.on(name, fn); return () => { bus.off(name, fn); }; } } } as unknown as ExtensionAPI; -} - -it("reports tracked running work, rather than equating idle agent with finished jobs", async () => { - const active = await backgroundState(api(["process", "subagent"], [{ name: "generation", status: "running" }], 1)); - expect(active.quiet).toBe(false); - expect(active.description).toContain("processes: 1 (generation)"); - expect(active.description).toContain("subagents: 1"); - const finished = await backgroundState(api(["process", "subagent"], [{ status: "exited" }], 0)); - expect(finished.quiet).toBe(true); -}); - -it("distinguishes missing providers from an unavailable installed tracker", async () => { - expect((await backgroundState(api([]))).quiet).toBe(true); - expect(await backgroundState(api(["process"]))).toMatchObject({ quiet: false, description: expect.stringContaining("processes: unknown") }); - vi.useFakeTimers(); - try { - const unavailable = backgroundState(api(["subagent"])); - await vi.advanceTimersByTimeAsync(2000); - expect(await unavailable).toMatchObject({ quiet: false, description: expect.stringContaining("subagents: unknown") }); - } finally { vi.useRealTimers(); } -}); diff --git a/test/fixtures/compaction-delivery.ts b/test/fixtures/compaction-delivery.ts deleted file mode 100644 index 6a3439a..0000000 --- a/test/fixtures/compaction-delivery.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { GoalIntercom } from "../../src/intercom.js"; -import { intercomFixture } from "../intercom-fixture.js"; - -// Real Pi owns compaction; only the external transport is deterministic in this fixture. -export default function compactionDelivery(pi: ExtensionAPI) { - const wire = intercomFixture(); - const link = new GoalIntercom({ ...pi, events: wire.events } as ExtensionAPI); - const role = process.env.PI_GOALS_TEST_DELIVERY_ROLE === "worker" ? "worker" : "supervisor"; - link.onSteer = text => pi.sendUserMessage(`[supervisor] ${text}`, { deliverAs: "steer" }); - link.onView = view => pi.sendUserMessage(view.text, { deliverAs: "followUp" }); - pi.on("session_start", async (_event, ctx) => link.configure("compaction-pair", role, ctx, true)); - pi.on("session_before_compact", async event => { - wire.receive({ binding: "compaction-pair", role: role === "worker" ? "supervisor" : "worker", kind: role === "worker" ? "steer" : "view", id: "retained-evidence", text: "Retained evidence arrived during manual compaction.", reason: "settled" }); - await new Promise(resolve => setTimeout(resolve, 100)); - if (process.env.PI_GOALS_TEST_COMPACTION === "cancel") return { cancel: true }; - if (process.env.PI_GOALS_TEST_COMPACTION === "failure") return; // Local model fails only its summarization request. - return { compaction: { summary: "Previous task and result preserved.", firstKeptEntryId: event.preparation.firstKeptEntryId, tokensBefore: event.preparation.tokensBefore } }; - }); -} diff --git a/test/fixtures/intercom-worker.ts b/test/fixtures/intercom-worker.ts deleted file mode 100644 index c721b96..0000000 --- a/test/fixtures/intercom-worker.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { GoalIntercom } from "../../src/intercom.js"; -import { workerView } from "../../src/worker-view.js"; - -export default function worker(pi: ExtensionAPI): void { - const link = new GoalIntercom(pi); - link.onSteer = instruction => pi.sendUserMessage(`[supervisor] ${instruction}`, { deliverAs: "steer" }); - pi.on("session_start", async (_event, ctx) => { - link.configure("native-pair-test", "worker", ctx); - void link.waitReady(8000).then(() => { - link.view(workerView(ctx.sessionManager.getBranch(), "settled", true, { - sourceSession: ctx.sessionManager.getSessionFile()!, latestDirection: "Inspect actual outputs.", - model: "offline/test", background: "No tracked work in this fixture.", - }), "settled", undefined, true); - }).catch(error => { if (!link.ended) ctx.ui.notify(String(error), "error"); }); - }); -} diff --git a/test/fixtures/profile-tools.ts b/test/fixtures/profile-tools.ts deleted file mode 100644 index 0688408..0000000 --- a/test/fixtures/profile-tools.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { Type } from "typebox"; - -// Auto-discovered by the isolated native supervisor profile, not passed with -e. -export default function profileTools(pi: ExtensionAPI) { - pi.registerTool({ - name: "profile_inspection", - label: "Profile inspection", - description: "Inspect the test profile marker.", - parameters: Type.Object({}), - async execute() { - return { content: [{ type: "text", text: "Profile loaded." }], details: {} }; - }, - }); -} diff --git a/test/fixtures/subagent-schema.ts b/test/fixtures/subagent-schema.ts new file mode 100644 index 0000000..10e045d --- /dev/null +++ b/test/fixtures/subagent-schema.ts @@ -0,0 +1,16 @@ +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { Type } from "typebox"; + +// Pi/gpt-6-astra: compatibility schemas only; this RPC fixture must never launch a worker. +export default function subagentSchema(pi: ExtensionAPI): void { + for (const [name, parameters] of [ + ["subagent", Type.Object({ agent: Type.String(), title: Type.String() })], + ["subagent_resume", Type.Object({ sessionFile: Type.String() })], + ["subagent_kill", Type.Object({ id: Type.String() })], + ] as const) { + pi.registerTool({ + name, label: name, description: "Schema-only RPC fixture; do not execute.", parameters, + async execute() { throw new Error("Worker execution forbidden in RPC review test"); }, + }); + } +} diff --git a/test/fold.test.ts b/test/fold.test.ts index f6b3eef..b4507c9 100644 --- a/test/fold.test.ts +++ b/test/fold.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { foldPlan, openSubtasks } from "../src/index.js"; +import { foldPlan } from "../src/plan.js"; const plan = `# Plan @@ -49,20 +49,3 @@ describe("foldPlan (current goals are above ## Log; durable memory is below it)" expect(foldPlan(draft)).toBe(draft.trimEnd()); }); }); - -describe("openSubtasks (the widget shows the next action, so the plan IS the task list)", () => { - const active = plan.split("\n").findIndex((l) => l.includes("goal: Implement the cache layer")); - - it("lists the active goal's open and in-progress subtasks, stopping at the next goal", () => { - expect(openSubtasks(plan, active)).toEqual(["eviction policy", "bench p95"]); - }); - - it("does not leak subtasks from the goal below", () => { - expect(openSubtasks(plan, active)).not.toContain("write the readme"); - }); -}); - -it("does not show historical Log subtasks under the last active goal", () => { - const plan = "1. [/] goal: current\n - [ ] current task\n\n## Log\n - [ ] historical task\n"; - expect(openSubtasks(plan, 0)).toEqual(["current task"]); -}); diff --git a/test/force-approval-flow.test.ts b/test/force-approval-flow.test.ts deleted file mode 100644 index 821b50a..0000000 --- a/test/force-approval-flow.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { expect, it, vi } from "vitest"; -import { approvalPath, readApproval } from "../src/approval.js"; -import { registerWorker as goals } from "../src/index.js"; -import { registerVisibleSupervisor } from "../src/supervisor-session.js"; -import { pairedIntercomFixture } from "./paired-intercom-fixture.js"; - -const goal = "inspect outputs"; -const reason = "Inspected preserved notebook edits and historical outputs; unrelated to this goal."; -const settle = () => new Promise(resolve => setImmediate(resolve)); - -async function setup() { - const cwd = mkdtempSync(join(tmpdir(), "goals-force-approval-")); - const git = (...args: string[]) => execFileSync("git", args, { cwd, encoding: "utf8" }).trim(); - writeFileSync(join(cwd, "verify.txt"), "PASS: actual output checked\n"); - writeFileSync(join(cwd, "notebook.py"), "original\n"); - git("init", "-q"); git("add", "."); - git("-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-qm", "verified output"); - const head = git("rev-parse", "HEAD"); - const planPath = join(cwd, ".pi/plan/worker-v1.md"); - mkdirSync(join(cwd, ".pi/plan"), { recursive: true }); - const plan = `1. [ ] goal: ${goal}\n - evidence: verify.txt\n\n## Log\n`; - writeFileSync(planPath, plan); - const wire = pairedIntercomFixture(); - function runtime(role: "worker" | "supervisor") { - const entries: any[] = role === "worker" ? [{ type: "custom", customType: "pi-goals-state", data: { phase: "working", approvalId: "force-binding", planVersion: 1, supervisorPaneId: "fixture-only" } }] : []; - const branch: any[] = []; - const hooks = new Map<string, any>(); - const tools = new Map<string, any>(); - const ctx = { - cwd, hasUI: true, isIdle: vi.fn(() => true), model: { provider: "test", id: "model" }, - modelRegistry: { find: (provider: string, id: string) => ({ provider, id }) }, - getSystemPrompt: () => "base", getContextUsage: () => ({ tokens: 10 }), - sessionManager: { getSessionId: () => role, getSessionFile: () => join(cwd, `${role}.jsonl`), getEntries: () => entries, getBranch: () => [...entries, ...branch] }, - ui: { notify: vi.fn(), setWidget: vi.fn(), setStatus: vi.fn(), theme: { fg: (_kind: string, text: string) => text } }, - }; - const pi = { - events: wire[role].events, getAllTools: vi.fn((): any[] => []), getActiveTools: () => ["read", "ApproveGoal", "SteerWorker"], setActiveTools: () => {}, - on: (name: string, fn: any) => { const prior = hooks.get(name); hooks.set(name, async (...args: any[]) => { await prior?.(...args); return fn(...args); }); }, - registerTool: (tool: any) => tools.set(tool.name, tool), registerCommand: () => {}, setModel: async () => true, - appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }), - sendUserMessage: (text: string) => { - const message = { role: "user", content: [{ type: "text", text }] }; - branch.push({ type: "message", message }); - void hooks.get("message_start")?.({ message }); - }, - }; - (role === "worker" ? goals : registerVisibleSupervisor)(pi as unknown as ExtensionAPI); - return { pi, ctx, hooks, branch, tools }; - } - const worker = runtime("worker"); - vi.stubEnv("PI_GOALS_WORKER_ID", "worker"); vi.stubEnv("PI_GOALS_OWNER_SESSION_ID", "worker"); - vi.stubEnv("PI_GOALS_APPROVAL_ID", "force-binding"); vi.stubEnv("PI_GOALS_PLAN_PATH", planPath); - const supervisor = runtime("supervisor"); - await worker.hooks.get("session_start")({}, worker.ctx); - await supervisor.hooks.get("session_start")({}, supervisor.ctx); - await settle(); await settle(); - writeFileSync(join(cwd, "notebook.py"), "preserved user edit\n"); - writeFileSync(join(cwd, "historical output.txt"), "historical result\n"); - const approve = (params: object = { force: true, reason }) => supervisor.tools.get("ApproveGoal").execute("approve", { goal, verifyOutputPath: "verify.txt", ...params }, undefined, undefined, supervisor.ctx); - const complete = () => worker.tools.get("CompleteGoal").execute("complete", { goal }, undefined, undefined, worker.ctx); - return { - cwd, git, head, planPath, plan, worker, supervisor, approve, complete, - checkpoint: approvalPath(cwd, "worker", goal), - close: async () => { await worker.hooks.get("session_shutdown")(); await supervisor.hooks.get("session_shutdown")(); vi.unstubAllEnvs(); rmSync(cwd, { recursive: true, force: true }); }, - }; -} - -it("force ApproveGoal -> CompleteGoal accepts only the reviewed dirty state without committing or modifying it", async () => { - const flow = await setup(); - try { - expect((await flow.approve({})).isError).toBe(true); - for (const reason of [undefined, "", " "]) expect((await flow.approve({ force: true, reason })).content[0].text).toContain("nonempty reason"); - expect(existsSync(flow.checkpoint)).toBe(false); - const status = flow.git("status", "--porcelain=v1"); - expect((await flow.approve()).isError).toBe(false); - const record = readApproval(flow.checkpoint)!; - expect(record.cleanWorktree).toBe(false); - expect(record.force?.reason).toBe(reason); - expect(record.force?.worktree.status).toContain("?? historical output.txt\0"); - expect(record.force?.worktree.files).toEqual(expect.arrayContaining([ - expect.objectContaining({ path: "notebook.py", kind: "file", contentHash: expect.stringMatching(/^[a-f0-9]{64}$/) }), - expect.objectContaining({ path: "historical output.txt", kind: "file", contentHash: expect.stringMatching(/^[a-f0-9]{64}$/) }), - ])); - expect((await flow.complete()).isError).toBe(false); - expect(readFileSync(flow.planPath, "utf8")).toContain(`[x] goal: ${goal}`); - expect(readFileSync(join(flow.cwd, "notebook.py"), "utf8")).toBe("preserved user edit\n"); - expect(readFileSync(join(flow.cwd, "historical output.txt"), "utf8")).toBe("historical result\n"); - expect(flow.git("rev-parse", "HEAD")).toBe(flow.head); - expect(flow.git("status", "--porcelain=v1")).toBe(status); - console.log("Force UAT: paired real handlers accepted unchanged tracked + untracked dirty content; Git HEAD and user files stayed unchanged."); - } finally { await flow.close(); } -}); - -it("keeps ordinary clean approval unchanged and rechecks runtime state at forced completion", async () => { - const flow = await setup(); - try { - expect((await flow.approve()).isError).toBe(false); - flow.worker.pi.getAllTools.mockReturnValue([{ name: "process" }]); - expect((await flow.complete()).isError).toBe(true); // Installed tracker is now unavailable. - flow.worker.pi.getAllTools.mockReturnValue([]); - writeFileSync(join(flow.cwd, "notebook.py"), "original\n"); - rmSync(join(flow.cwd, "historical output.txt")); - expect((await flow.complete()).isError).toBe(true); // Cleaning up also changes the accepted state. - expect((await flow.approve({})).isError).toBe(false); - expect(readApproval(flow.checkpoint)?.force).toBeUndefined(); - expect((await flow.complete()).isError).toBe(false); - } finally { await flow.close(); } -}); - -it.each(["tracked content", "untracked content", "new untracked", "deleted untracked", "index only", "index contents", "rename", "HEAD", "goal"])("invalidates forced approval after changed %s", async change => { - const flow = await setup(); - try { - expect((await flow.approve()).isError).toBe(false); - if (change === "index contents") { - flow.git("add", "notebook.py"); - writeFileSync(join(flow.cwd, "notebook.py"), "separate worktree\n"); - expect((await flow.approve()).isError).toBe(false); - } - const status = flow.git("status", "--porcelain=v1"); - if (change === "tracked content") writeFileSync(join(flow.cwd, "notebook.py"), "changed user edits\n"); - if (change === "untracked content") writeFileSync(join(flow.cwd, "historical output.txt"), "changed old output\n"); - if (change === "new untracked") writeFileSync(join(flow.cwd, "another.txt"), "new"); - if (change === "deleted untracked") rmSync(join(flow.cwd, "historical output.txt")); - if (change === "index only") flow.git("add", "notebook.py"); - if (change === "index contents") { - writeFileSync(join(flow.cwd, "notebook.py"), "different staged bytes\n"); - flow.git("add", "notebook.py"); - writeFileSync(join(flow.cwd, "notebook.py"), "separate worktree\n"); - expect(flow.git("status", "--porcelain=v1")).toBe(status); - } - if (change === "rename") renameSync(join(flow.cwd, "notebook.py"), join(flow.cwd, "renamed.py")); - if (change === "HEAD") flow.git("-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "--allow-empty", "-qm", "new head"); - if (change === "goal") writeFileSync(flow.planPath, flow.plan.replace("evidence: verify.txt", "evidence: different.txt")); - if (change.endsWith(" content")) expect(flow.git("status", "--porcelain=v1")).toBe(status); - expect((await flow.complete()).isError).toBe(true); - expect(readFileSync(flow.planPath, "utf8")).toContain(`[ ] goal: ${goal}`); - } finally { await flow.close(); } -}); - -it.each(["evidence", "verification", "stopped view", "tool call", "unknown tracker", "active tracker"])("force does not bypass the %s gate", async gate => { - const flow = await setup(); - try { - if (gate === "evidence") writeFileSync(flow.planPath, flow.plan.replace("evidence: verify.txt", "evidence: (empty until sign-off)")); - if (gate === "verification") writeFileSync(join(flow.cwd, "verify.txt"), ""); - if (gate === "stopped view") { flow.worker.ctx.isIdle.mockReturnValue(false); await flow.worker.hooks.get("agent_start")({}, flow.worker.ctx); } - if (gate === "tool call") flow.worker.branch.push({ type: "message", message: { role: "assistant", content: [{ type: "toolCall", id: "pending", name: "edit" }] } }); - if (gate.endsWith("tracker")) { - flow.worker.pi.getAllTools.mockReturnValue([{ name: "process" }]); - if (gate === "active tracker") { - const emit = flow.worker.pi.events.emit; - flow.worker.pi.events.emit = (name, request) => { if (name === "processes:request:list") { request.reply([{ status: "running", name: "existing-job" }]); return true; } return emit(name, request); }; - } - } - await flow.worker.hooks.get("agent_settled")({}, flow.worker.ctx); await settle(); - const response = await flow.approve(); - expect(response.isError).toBe(true); - expect(existsSync(flow.checkpoint)).toBe(false); - if (gate.endsWith("tracker")) expect(response.content[0].text).toContain("background work"); - if (gate === "tool call") expect(response.content[0].text).toContain("work is active: edit"); - } finally { await flow.close(); } -}); diff --git a/test/goals-flow.test.ts b/test/goals-flow.test.ts deleted file mode 100644 index eda82bd..0000000 --- a/test/goals-flow.test.ts +++ /dev/null @@ -1,642 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { approvalPath, goalBlock, hashGoalBlock, repositoryState, writeApproval } from "../src/approval.js"; -import { GoalIntercom } from "../src/intercom.js"; -import { intercomFixture } from "./intercom-fixture.js"; -import { pairedIntercomFixture } from "./paired-intercom-fixture.js"; - -const openSupervisorPane = vi.fn(async () => "pane-2"); -const closeSupervisorPane = vi.fn(async () => undefined); -const shutdowns: Array<() => Promise<void>> = []; -vi.mock("../src/herdr.js", () => ({ openSupervisorPane, closeSupervisorPane })); -const { registerWorker: piGoalsExtension, isMainSession } = await import("../src/index.js"); - -function setup(selectChoices: Array<string | undefined>, editorChoices: Array<string | undefined> = [], events?: ExtensionAPI["events"]) { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-flow-")); - writeFileSync(join(cwd, ".gitignore"), ".pi/\n"); - writeFileSync(join(cwd, "verify.txt"), "PASS\n"); - execFileSync("git", ["init", "-q"], { cwd }); - execFileSync("git", ["add", ".gitignore", "verify.txt"], { cwd }); - execFileSync("git", ["-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-qm", "initial"], { cwd }); - const transport = intercomFixture(); - const commands = new Map<string, any>(); - const hooks = new Map<string, any>(); - const tools = new Map<string, any>(); - const entries: Array<{ type: string; customType: string; data: unknown }> = []; - const messages: Array<{ content: string; display?: boolean }> = []; - const notifications: string[] = []; - const ctx = { - cwd, - hasUI: true, - isIdle: vi.fn(() => true), - getSystemPrompt: () => "base prompt", - getContextUsage: () => ({ percent: 25 }), - model: { provider: "test", id: "tiny" }, - modelRegistry: { find: (provider: string, id: string) => ({ provider, id }) }, - sessionManager: { - getSessionId: () => "session-a", - getSessionFile: () => join(cwd, "session.jsonl"), - getEntries: () => entries, - getBranch: () => [], - }, - ui: { - theme: { fg: (_kind: string, text: string) => text }, - setStatus: vi.fn(), - setWidget: vi.fn(), - notify: (text: string) => notifications.push(text), - select: async () => selectChoices.shift(), - editor: async () => editorChoices.shift(), - }, - }; - openSupervisorPane.mockImplementation(async () => "pane-2"); - const pi = { - events: events ?? transport.events, - registerCommand: (name: string, command: any) => commands.set(name, command), - on: (name: string, handler: any) => { - const prior = hooks.get(name); - hooks.set(name, async (...args: any[]) => { await prior?.(...args); return handler(...args); }); - }, - appendEntry: (customType: string, data: unknown) => { if (customType === "pi-goals-state") entries.push({ type: "custom", customType, data }); }, - registerTool: (tool: any) => tools.set(tool.name, tool), - getAllTools: () => [], - setModel: vi.fn(async () => true), - sendMessage: (message: { content: string; display?: boolean }) => messages.push(message), - sendUserMessage: (content: string) => messages.push({ content }), - }; - piGoalsExtension(pi as unknown as ExtensionAPI); - shutdowns.push(() => hooks.get("session_shutdown")()); - return { pi, commands, ctx, cwd, entries, hooks, messages, notifications, tools, transport }; -} - -function writePlan(cwd: string, content: string): string { - const path = join(cwd, ".pi/plan/session-a-v1.md"); - mkdirSync(join(cwd, ".pi/plan"), { recursive: true }); - writeFileSync(path, content); - return path; -} - -function approvedPlan(cwd: string): string { - return writePlan(cwd, "# Plan\n\n## Goals\n\n1. [ ] goal: make the file\n - discriminator: output exists\n - evidence:\n - `result.txt`: contains ok\n\n## Log\n"); -} - -afterEach(async () => { - for (const shutdown of shutdowns.splice(0)) await shutdown(); - vi.useRealTimers(); - openSupervisorPane.mockClear(); - closeSupervisorPane.mockClear(); -}); - -describe("/goals flow", () => { - it("reports idleness and keeps manual completion claims supervised without reverting edits", async () => { - vi.useFakeTimers({ toFake: ["setInterval", "clearInterval"] }); - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const path = approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const views = () => flow.transport.sent.filter(message => message.kind === "view"); - await vi.advanceTimersByTimeAsync(60 * 60_000); - expect(views().at(-1)?.text).toMatch(/^The worker stopped\./); - writeFileSync(path, readFileSync(path, "utf8").replace("[ ] goal:", "[/] goal:")); - flow.ctx.isIdle.mockReturnValue(false); - await flow.hooks.get("agent_start")({}, flow.ctx); - expect(views().at(-1)?.text).toMatch(/^The worker is still working\./); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(views().at(-1)?.text).toMatch(/^The worker is still working\./); - flow.ctx.isIdle.mockReturnValue(true); - writeFileSync(path, readFileSync(path, "utf8").replace("[/] goal:", "[x] goal:")); - await flow.hooks.get("agent_settled")({}, flow.ctx); - await flow.hooks.get("turn_end")({}, flow.ctx); - const count = views().length; - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", signedOffGoals: [] }); - expect(views().at(-1)?.text).toContain("make the file: [/] -> [x]; manual completion claim, no CompleteGoal sign-off recorded"); - expect(views().at(-1)?.text).toContain("use SteerWorker to send the next useful instruction and resume work"); - expect(views().at(-1)?.text).toContain("Manual checkbox edits are claims, not proof of completion"); - expect(readFileSync(path, "utf8")).toContain("[x] goal:"); - expect(flow.ctx.ui.setStatus).toHaveBeenLastCalledWith("pi-goals", expect.stringContaining("0/1 goals · 1 claimed, awaiting review")); - expect(flow.ctx.ui.setWidget).toHaveBeenLastCalledWith("pi-goals-widget", [expect.stringContaining("claimed complete; awaiting supervisor review")]); - const binding = (flow.entries.at(-1)?.data as any).approvalId; - flow.transport.receive({ binding, role: "supervisor", kind: "steer", id: "review-claim", text: "Reopen the goal; verify the missing output first." }); - expect(flow.messages.at(-1)?.content).toBe("[supervisor] Reopen the goal; verify the missing output first."); - await flow.hooks.get("session_start")({}, flow.ctx); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", signedOffGoals: [] }); - await vi.advanceTimersByTimeAsync(60 * 60_000); - expect(views().length).toBeGreaterThan(count); - } finally { - await flow.hooks.get("session_shutdown")(); - vi.useRealTimers(); - rmSync(flow.cwd, { recursive: true, force: true }); - } - }); - it("wakes review for an external non-checkbox plan edit, including atomic replacement", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const path = approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const before = readFileSync(path, "utf8"); - writeFileSync(`${path}.tmp`, before.replace("output exists", "output contains exact required bytes")); - renameSync(`${path}.tmp`, path); - await vi.waitFor(() => { - const view = flow.transport.sent.filter(message => message.kind === "view").at(-1); - expect(view?.reason).toBe("plan"); - expect(view?.text).toContain("Assess plan changes against the user's intent and preferences"); - expect(view?.text).toContain("- - discriminator: output exists"); - expect(view?.text).toContain("+ - discriminator: output contains exact required bytes"); - }); - const count = flow.transport.sent.length; - await flow.hooks.get("session_shutdown")(); - writeFileSync(path, before); - await new Promise(resolve => setTimeout(resolve, 250)); - expect(flow.transport.sent).toHaveLength(count); - } finally { await flow.hooks.get("session_shutdown")(); rmSync(flow.cwd, { recursive: true, force: true }); } - }); - - it("coalesces active-worker plan edits into its settled review", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const path = approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - flow.ctx.isIdle.mockReturnValue(false); - const before = readFileSync(path, "utf8"); - writeFileSync(path, before.replace("output exists", "intermediate discriminator")); - writeFileSync(path, before.replace("output exists", "final discriminator")); - await new Promise(resolve => setTimeout(resolve, 250)); - expect(flow.transport.sent.filter(message => message.kind === "view" && message.reason === "plan")).toHaveLength(0); - flow.ctx.isIdle.mockReturnValue(true); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const view = flow.transport.sent.filter(message => message.kind === "view").at(-1); - expect(view?.text).toContain("- - discriminator: output exists"); - expect(view?.text).toContain("+ - discriminator: final discriminator"); - expect(view?.text).not.toContain("intermediate discriminator"); - } finally { await flow.hooks.get("session_shutdown")(); rmSync(flow.cwd, { recursive: true, force: true }); } - }); - - it("restores sign-off markers but clears one when a goal is reopened", async () => { - const flow = setup([]); - try { - const path = writePlan(flow.cwd, "1. [x] goal: first\n2. [ ] goal: second\n"); - flow.entries.push({ type: "custom", customType: "pi-goals-state", data: { phase: "working", approvalId: "binding", planVersion: 1, signedOffGoals: ["first"], previousPlan: readFileSync(path, "utf8") } }); - await flow.hooks.get("session_start")({}, flow.ctx); - expect(flow.ctx.ui.setStatus).toHaveBeenLastCalledWith("pi-goals", expect.stringContaining("1/2 goals")); - writeFileSync(path, "1. [/] goal: first\n2. [ ] goal: second\n"); - await flow.hooks.get("turn_end")({}, flow.ctx); - expect(flow.entries.at(-1)?.data).toMatchObject({ signedOffGoals: [] }); - writeFileSync(path, "1. [x] goal: first\n2. [ ] goal: second\n"); - await flow.hooks.get("agent_settled")({}, flow.ctx); - await flow.hooks.get("turn_end")({}, flow.ctx); - expect(flow.ctx.ui.setStatus).toHaveBeenLastCalledWith("pi-goals", expect.stringContaining("0/2 goals · 1 claimed, awaiting review")); - } finally { await flow.hooks.get("session_shutdown")(); rmSync(flow.cwd, { recursive: true, force: true }); } - }); - - it("preserves drafts, records the interview, and keeps planning read-only", async () => { - const flow = setup(["Refine"], ["Keep two columns."]); - try { - await flow.commands.get("goals").handler("first objective", flow.ctx); - const first = writePlan(flow.cwd, "# Plan\n\n## Goals\n\n1. [ ] goal: preserve this\n\n## Interview\n"); - await flow.hooks.get("input")({ text: "Preserve column order.", source: "interactive" }, flow.ctx); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(readFileSync(first, "utf8")).toContain("> Preserve column order."); - expect(readFileSync(first, "utf8")).toContain("> Keep two columns."); - expect(flow.messages.at(-1)?.content).toContain("Revise the plan at"); - expect((await flow.hooks.get("tool_call")({ toolName: "edit", input: { path: "README.md" } }, flow.ctx))?.block).toBe(true); - - await flow.commands.get("goals").handler("second objective", flow.ctx); - expect(readFileSync(first, "utf8")).toContain("preserve this"); - expect(flow.messages.at(-1)?.content).toContain("session-a-v2.md"); - } finally { - rmSync(flow.cwd, { recursive: true, force: true }); - } - }); - - it("forks a visible supervisor on Ready and keeps the main session as worker", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const planPath = approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(openSupervisorPane).toHaveBeenCalledWith(expect.objectContaining({ - cwd: flow.cwd, - sourceSessionFile: join(flow.cwd, "session.jsonl"), - workerSessionId: "session-a", - planPath, - }), expect.any(Function)); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", supervisorPaneId: "pane-2" }); - expect(flow.messages.at(-1)?.content).toBe("The plan is approved. Begin implementation as the worker."); - const prompt = await flow.hooks.get("before_agent_start")({}, flow.ctx); - expect(prompt.systemPrompt).toContain("implementation worker"); - expect(prompt.systemPrompt).toContain("stronger read-only supervisor"); - } finally { - rmSync(flow.cwd, { recursive: true, force: true }); - } - }); - - it("starts work only after the supervisor launcher resolves", async () => { - const flow = setup(["Ready"]); - try { - let ready: (() => void) | undefined; - openSupervisorPane.mockImplementationOnce(() => new Promise((resolve) => { ready = () => resolve("pane-2"); })); - await flow.commands.get("goals").handler("make the file", flow.ctx); - approvedPlan(flow.cwd); - const starting = flow.hooks.get("agent_settled")({}, flow.ctx); - await new Promise((resolve) => setImmediate(resolve)); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "planning" }); - ready!(); - await starting; - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", supervisorPaneId: "pane-2" }); - } finally { - rmSync(flow.cwd, { recursive: true, force: true }); - } - }); - - it("delivers an Intercom instruction to the worker", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const binding = (flow.entries.at(-1)?.data as { approvalId: string }).approvalId; - flow.transport.receive({ binding, role: "supervisor", kind: "steer", id: "steer-1", text: "Run the focused test." }); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(flow.messages.some((message) => message.content === "[supervisor] Run the focused test.")).toBe(true); - } finally { - rmSync(flow.cwd, { recursive: true, force: true }); - } - }); - - it("closes the supervisor on clear but keeps the plan file", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const planPath = approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const binding = (flow.entries.at(-1)?.data as any).approvalId; - await flow.commands.get("goals").handler("clear", flow.ctx); - const messageCount = flow.messages.length; - flow.transport.receive({ binding, role: "supervisor", kind: "steer", id: "late-cleared", text: "Obsolete instruction." }); - await flow.commands.get("goals").handler("restart", flow.ctx); - await flow.commands.get("goals").handler("reconnect", flow.ctx); - expect(flow.messages).toHaveLength(messageCount); - expect(openSupervisorPane).toHaveBeenCalledTimes(1); - expect(closeSupervisorPane).toHaveBeenCalledWith("pane-2"); - expect(readFileSync(planPath, "utf8")).toContain("make the file"); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null, supervisorPaneId: null, planVersion: null }); - } finally { - rmSync(flow.cwd, { recursive: true, force: true }); - } - }); - - it("accepts only an approval for the exact clean commit and goal block", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const planPath = approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const goal = "make the file"; - const plan = readFileSync(planPath, "utf8"); - const block = goalBlock(plan, goal)!; - const repository = repositoryState(flow.cwd); - const approvalId = (flow.entries.at(-1)?.data as { approvalId: string }).approvalId; - writeApproval(approvalPath(flow.cwd, "session-a", goal), { - version: 3, verdict: "accept", approvalId, goal, planPath, - goalBlockHash: hashGoalBlock(block), repoRoot: repository.repoRoot, - head: repository.head, tree: repository.tree, cleanWorktree: true, - inspected: { plan: true, repository: true, evidence: true, verifyOutput: true }, - verifyOutputPath: "verify.txt", - supervisor: { sessionId: "supervisor", runId: null }, timestamp: new Date().toISOString(), - }); - writeFileSync(planPath, `${plan}- Appended manual log after approval.\n1. [ ] goal: make the file\n2. [ ] goal: historical only\n`); - const controller = new AbortController(); - const beforeCancel = readFileSync(planPath, "utf8"); - const cancelled = flow.tools.get("CompleteGoal").execute("cancelled", { goal }, controller.signal, undefined, flow.ctx); - controller.abort(); // Cancel while the background-state lookup yields. - expect((await cancelled).isError).toBe(true); - expect(readFileSync(planPath, "utf8")).toBe(beforeCancel); - expect((await flow.tools.get("CompleteGoal").execute("already-cancelled", { goal }, controller.signal, undefined, flow.ctx)).isError).toBe(true); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", signedOffGoals: [] }); - const signed = await flow.tools.get("CompleteGoal").execute("id", { goal }, undefined, undefined, flow.ctx); - expect(signed.isError).toBe(false); - expect(readFileSync(planPath, "utf8")).toContain("1. [x] goal: make the file"); - expect(readFileSync(planPath, "utf8")).toContain("1. [ ] goal: make the file"); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null, signedOffGoals: [goal] }); - expect(flow.ctx.ui.setWidget).toHaveBeenLastCalledWith("pi-goals-widget", ["✔ complete"]); - await flow.hooks.get("session_start")({}, flow.ctx); - expect(flow.ctx.ui.setWidget).toHaveBeenLastCalledWith("pi-goals-widget", ["✔ complete"]); - const completedViews = flow.transport.sent.filter(message => message.kind === "view").length; - flow.transport.receive({ binding: approvalId, role: "supervisor", kind: "hello", id: "hello", ready: true }); - await new Promise(resolve => setImmediate(resolve)); - expect(flow.transport.sent.filter(message => message.kind === "view")).toHaveLength(completedViews); - } finally { - rmSync(flow.cwd, { recursive: true, force: true }); - } - }); -}); - -describe("process role", () => { - it("keeps subagent children and visible supervisors out of the worker extension", () => { - expect(isMainSession(false)).toBe(true); - expect(isMainSession(true)).toBe(false); - }); -}); - -function restoredPlan(flow: ReturnType<typeof setup>, phase: "working" | "planning" = "working") { - const path = approvedPlan(flow.cwd); - flow.entries.push({ type: "custom", customType: "pi-goals-state", data: { phase, approvalId: "restored-binding", supervisorPaneId: "owned-pane", planVersion: 1 } }); - return path; -} - -it.each(["working", "planning"] as const)("restores %s linkage even when its remembered model is unavailable, and supports explicit recovery", async phase => { - const flow = setup([]); - try { - const path = restoredPlan(flow, phase); - const plan = readFileSync(path, "utf8"); - const role = phase === "working" ? "worker" : "planning"; - mkdirSync(join(flow.cwd, ".pi/pi-goals/models"), { recursive: true }); - const modelPath = join(flow.cwd, `.pi/pi-goals/models/${role}.json`); - writeFileSync(modelPath, JSON.stringify({ provider: "gone", id: "expired" })); - flow.ctx.modelRegistry.find = vi.fn().mockReturnValue(undefined); - await expect(flow.hooks.get("session_start")({}, flow.ctx)).resolves.toBeUndefined(); - expect(flow.pi.setModel).not.toHaveBeenCalled(); - expect(readFileSync(modelPath, "utf8")).toContain("expired"); - expect(flow.transport.sent.filter(message => message.kind === "hello" && message.ready)).toHaveLength(0); - expect(flow.ctx.ui.setStatus).toHaveBeenLastCalledWith("pi-goals", "goals paused"); - if (phase === "working") expect(flow.transport.sent).toContainEqual(expect.objectContaining({ kind: "hello", binding: "restored-binding" })); - expect((await flow.hooks.get("tool_call")({ toolName: "edit", input: { path: "code.ts" } }, flow.ctx)).block).toBe(true); - expect(await flow.hooks.get("tool_call")({ toolName: "read", input: { path: "code.ts" } }, flow.ctx)).toBeUndefined(); - expect(await flow.hooks.get("input")({ source: "interactive", text: "Why are we paused?" }, flow.ctx)).toBeUndefined(); - const signoff = await flow.tools.get("CompleteGoal").execute("id", { goal: "make the file" }, undefined, undefined, flow.ctx); - expect(signoff.isError).toBe(true); - flow.ctx.modelRegistry.find = (provider, id) => ({ provider, id }); - await flow.hooks.get("model_select")({ source: "set", model: { provider: "test", id: "chosen" } }, flow.ctx); - await flow.commands.get("goals").handler("reconnect", flow.ctx); - expect(flow.pi.setModel).toHaveBeenLastCalledWith({ provider: "test", id: "chosen" }); - expect(openSupervisorPane).not.toHaveBeenCalled(); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase, approvalId: "restored-binding", planVersion: 1 }); - const injection = await flow.hooks.get("before_agent_start")({}, flow.ctx); - if (phase === "planning") expect(injection.message.content).toContain(path); - else expect(injection.systemPrompt).toContain("implementation worker"); - // Human diagnostic input is retained in the planning interview, never discarded by recovery. - expect(readFileSync(path, "utf8")).toContain(plan.trim()); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("shows a missing resumed supervisor, pauses writes, and automatically unpauses when that peer returns", async () => { - vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] }); - const flow = setup([]); - try { - restoredPlan(flow); - flow.transport.replyToHello(false); - await flow.hooks.get("session_start")({}, flow.ctx); - expect(flow.ctx.ui.setStatus).toHaveBeenLastCalledWith("pi-goals", "goals paused"); - await vi.advanceTimersByTimeAsync(60_000); - expect(flow.notifications.some(text => text.includes("/goals restart"))).toBe(false); - await vi.advanceTimersByTimeAsync(240_000); - expect(flow.notifications.some(text => text.includes("/goals restart"))).toBe(true); - expect((await flow.hooks.get("tool_call")({ toolName: "write", input: { path: "code.ts" } }, flow.ctx)).terminate).toBe(true); - expect(await flow.hooks.get("tool_call")({ toolName: "bash", input: { command: "git status" } }, flow.ctx)).toBeUndefined(); - flow.transport.receive({ binding: "restored-binding", role: "supervisor", kind: "hello", id: "hello", ready: true }); - expect(flow.ctx.ui.setStatus).toHaveBeenLastCalledWith("pi-goals", expect.stringContaining("supervised")); - expect(await flow.hooks.get("tool_call")({ toolName: "write", input: { path: "code.ts" } }, flow.ctx)).toBeUndefined(); - expect(openSupervisorPane).not.toHaveBeenCalled(); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("exits planning without deleting the draft or approving implementation", async () => { - const flow = setup([]); - try { - await flow.commands.get("goals").handler("draft", flow.ctx); - const path = approvedPlan(flow.cwd); - const before = readFileSync(path, "utf8"); - await flow.commands.get("goals").handler("noplan", flow.ctx); - expect(readFileSync(path, "utf8")).toBe(before); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null, planVersion: 1 }); - expect(openSupervisorPane).not.toHaveBeenCalled(); - expect(flow.messages.some(message => message.content.includes("Begin implementation"))).toBe(false); - expect(await flow.hooks.get("tool_call")({ toolName: "write", input: { path: "arbitrary.txt" } }, flow.ctx)).toBeUndefined(); - await flow.commands.get("goals").handler("work", flow.ctx); - expect(flow.notifications.at(-1)).toContain("No approved worker pairing"); - await flow.commands.get("goals").handler("supervise", flow.ctx); - expect(flow.notifications.at(-1)).toContain("worker session"); - expect(openSupervisorPane).not.toHaveBeenCalled(); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("publishes one fresh view after an accepted-view peer reload, but never after clear", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("draft", flow.ctx); - const path = approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const views = () => flow.transport.sent.filter(message => message.kind === "view"); - const original = views().at(-1)!; - flow.transport.receive({ binding: original.binding, role: "supervisor", kind: "received", id: original.id }); - flow.transport.event({ type: "session_left", sessionId: "peer" }); - writeFileSync(path, readFileSync(path, "utf8").replace("output exists", "current result must exist")); - const previousIds = new Set(views().map(view => view.id)); - flow.transport.receive({ binding: original.binding, role: "supervisor", kind: "hello", id: "hello", ready: true, reply: true }); - await new Promise(resolve => setImmediate(resolve)); - expect(views().filter(view => !previousIds.has(view.id))).toHaveLength(1); - expect(views().at(-1)!.id).not.toBe(original.id); - expect(views().at(-1)!.text).toContain("The worker stopped."); - expect(views().at(-1)!.text).toContain("current result must exist"); - await flow.commands.get("goals").handler("clear", flow.ctx); - const afterClear = views().length; - flow.transport.receive({ binding: original.binding, role: "supervisor", kind: "hello", id: "hello", ready: true }); - await new Promise(resolve => setImmediate(resolve)); - expect(views()).toHaveLength(afterClear); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("reconnects an approved worker with work without making a new pairing", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("draft", flow.ctx); - approvedPlan(flow.cwd); - await flow.hooks.get("agent_settled")({}, flow.ctx); - const state = flow.entries.at(-1)?.data as any; - await flow.commands.get("goals").handler("work", flow.ctx); - expect(flow.entries.at(-1)?.data).toMatchObject({ approvalId: state.approvalId, phase: "working" }); - expect(openSupervisorPane).toHaveBeenCalledTimes(1); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it.each(["launch", "model"])("rejects plan content changes during Ready %s without replacing its pane", async (stage) => { - const flow = setup(["Ready", "Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const path = approvedPlan(flow.cwd); - const mutate = () => writeFileSync(path, readFileSync(path, "utf8").replace("make the file", "make a different report")); - if (stage === "launch") openSupervisorPane.mockImplementationOnce(async () => { mutate(); return "pane-2"; }); - else flow.pi.setModel.mockImplementationOnce(async () => { mutate(); return true; }); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "planning", supervisorPaneId: "pane-2" }); - expect(flow.messages.some(message => message.content.includes("Begin implementation"))).toBe(false); - expect(flow.notifications.join("\n")).toContain("plan changed after Ready"); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(openSupervisorPane).toHaveBeenCalledTimes(1); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working" }); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("allows five minutes for stale Ready retries, without replacing the pane automatically", async () => { - vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] }); - const flow = setup(["Ready", "Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - approvedPlan(flow.cwd); - flow.transport.replyToHello(false); - openSupervisorPane.mockImplementationOnce(async (_input: any, opened: any) => { opened("failed-pane"); throw new Error("pane run failed"); }); - await flow.hooks.get("agent_settled")({}, flow.ctx); - expect(flow.notifications.at(-1)).toContain("failed-pane"); - const retry = flow.hooks.get("agent_settled")({}, flow.ctx); - await vi.advanceTimersByTimeAsync(60_000); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "planning" }); - await vi.advanceTimersByTimeAsync(240_000); - await retry; - expect(openSupervisorPane).toHaveBeenCalledTimes(1); - expect(closeSupervisorPane).not.toHaveBeenCalled(); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "planning", supervisorPaneId: "failed-pane" }); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("explicitly restarts only the tracked pane, keeps the plan, and invalidates old approval binding", async () => { - const flow = setup([]); - try { - const path = restoredPlan(flow); - const before = readFileSync(path, "utf8"); - await flow.hooks.get("session_start")({}, flow.ctx); - const checkpoint = approvalPath(flow.cwd, "session-a", "make the file"); - mkdirSync(join(flow.cwd, ".pi/pi-goals/approvals"), { recursive: true }); - writeFileSync(checkpoint, "old checkpoint"); - await flow.commands.get("goals").handler("restart", flow.ctx); - expect(closeSupervisorPane).toHaveBeenCalledExactlyOnceWith("owned-pane"); - expect(openSupervisorPane).toHaveBeenCalledTimes(1); - expect(readFileSync(path, "utf8")).toBe(before); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", planVersion: 1 }); - expect((flow.entries.at(-1)?.data as any).approvalId).not.toBe("restored-binding"); - expect(() => readFileSync(checkpoint)).toThrow(); - await flow.commands.get("goals").handler("clear", flow.ctx); - expect(await flow.hooks.get("tool_call")({ toolName: "write", input: { path: "unrelated.ts" } }, flow.ctx)).toBeUndefined(); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("does not persist startup results or launch work after session shutdown", async () => { - const flow = setup(["Ready"]); - try { - let finish: (() => void) | undefined; - openSupervisorPane.mockImplementationOnce(() => new Promise(resolve => { finish = () => resolve("late-pane"); })); - await flow.commands.get("goals").handler("make the file", flow.ctx); - approvedPlan(flow.cwd); - const starting = flow.hooks.get("agent_settled")({}, flow.ctx); - await new Promise(resolve => setImmediate(resolve)); - await flow.hooks.get("session_shutdown")(); - const entries = flow.entries.length; - const messages = flow.messages.length; - finish!(); - await starting; - expect(flow.entries).toHaveLength(entries); - expect(flow.messages).toHaveLength(messages); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("keeps a failed Ready model not-ready and recovers the same real supervisor binding", async () => { - const wire = pairedIntercomFixture(); - const flow = setup(["Ready", "Ready"], [], wire.worker.events as ExtensionAPI["events"]); - const supervisorEntries: any[] = []; - const supervisor = new GoalIntercom({ events: wire.supervisor.events, on: () => {}, appendEntry: (customType: string, data: unknown) => supervisorEntries.push({ type: "custom", customType, data }) } as unknown as ExtensionAPI); - const supervisorCtx = { sessionManager: { getEntries: () => supervisorEntries }, ui: { notify: vi.fn() } }; - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - approvedPlan(flow.cwd); - mkdirSync(join(flow.cwd, ".pi/pi-goals/models"), { recursive: true }); - writeFileSync(join(flow.cwd, ".pi/pi-goals/models/worker.json"), JSON.stringify({ provider: "gone", id: "expired" })); - flow.ctx.modelRegistry.find = () => undefined as any; - openSupervisorPane.mockImplementationOnce(async (input: any) => { - supervisor.configure(input.approvalId, "supervisor", supervisorCtx as any, true); - return "pane-2"; - }); - await flow.hooks.get("agent_settled")({}, flow.ctx); - await new Promise(resolve => setImmediate(resolve)); - const binding = (flow.entries.at(-1)?.data as any).approvalId; - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "planning", planVersion: 1 }); - expect(wire.worker.sent.filter(message => message.kind === "hello" && message.ready)).toHaveLength(0); - expect(supervisor.connected).toBe(false); - expect(() => supervisor.steer("Must wait.")).toThrow("disconnected"); - await flow.hooks.get("model_select")({ source: "set", model: { provider: "test", id: "chosen" } }, flow.ctx); - flow.ctx.modelRegistry.find = (provider, id) => ({ provider, id }); - await flow.commands.get("goals").handler("reconnect", flow.ctx); - expect(supervisor.connected).toBe(false); // Planning is not implementation readiness. - await flow.hooks.get("agent_settled")({}, flow.ctx); - await new Promise(resolve => setImmediate(resolve)); - expect(supervisor.connected).toBe(true); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", approvalId: binding, planVersion: 1 }); - expect(openSupervisorPane).toHaveBeenCalledTimes(1); - expect(closeSupervisorPane).not.toHaveBeenCalled(); - await flow.commands.get("goals").handler("reconnect", flow.ctx); - supervisor.steer("Recovered instruction."); - await new Promise(resolve => setImmediate(resolve)); - expect(flow.messages.filter(message => message.content === "[supervisor] Recovered instruction.")).toHaveLength(1); - expect(supervisorCtx.ui.notify).not.toHaveBeenCalled(); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("points a present-but-paused peer recovery at the supervisor pane", async () => { - const flow = setup([]); - try { - restoredPlan(flow); - await flow.hooks.get("session_start")({}, flow.ctx); - flow.transport.replyToHello(false); - flow.transport.receive({ binding: "restored-binding", role: "supervisor", kind: "hello", id: "hello", ready: false }); - expect(flow.ctx.ui.setWidget).toHaveBeenLastCalledWith("pi-goals-widget", [expect.stringContaining("Supervisor is present but not ready")]); - const prompt = await flow.hooks.get("before_agent_start")({}, flow.ctx); - expect(prompt.systemPrompt).toContain("/goals reconnect in the supervisor pane"); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("clear during the initial Ready wait cancels immediately and cannot resurrect the plan", async () => { - vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] }); - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - const path = approvedPlan(flow.cwd); - flow.transport.replyToHello(false); - const starting = flow.hooks.get("agent_settled")({}, flow.ctx); - await new Promise(resolve => setImmediate(resolve)); - expect(flow.entries.at(-1)?.data).toMatchObject({ supervisorPaneId: "pane-2" }); - await flow.commands.get("goals").handler("clear", flow.ctx); - await starting; // No timer advancement: detach must cancel the five-minute wait. - const entryCount = flow.entries.length; - await vi.advanceTimersByTimeAsync(300_000); - expect(flow.entries).toHaveLength(entryCount); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null, planVersion: null, approvalId: null }); - expect(flow.messages.some(message => message.content.includes("Begin implementation"))).toBe(false); - expect(flow.ctx.ui.setWidget).toHaveBeenLastCalledWith("pi-goals-widget", undefined); - expect(readFileSync(path, "utf8")).toContain("make the file"); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); - -it("clear before the launcher resolves rejects late pane callbacks without restoring the binding", async () => { - const flow = setup(["Ready"]); - try { - await flow.commands.get("goals").handler("make the file", flow.ctx); - approvedPlan(flow.cwd); - let finish!: () => void; - openSupervisorPane.mockImplementationOnce((_input: any, opened: any) => new Promise((resolve, reject) => { - finish = () => { try { opened("late-pane"); resolve("late-pane"); } catch (error) { reject(error); } }; - })); - const starting = flow.hooks.get("agent_settled")({}, flow.ctx); - await new Promise(resolve => setImmediate(resolve)); - await flow.commands.get("goals").handler("clear", flow.ctx); - const entryCount = flow.entries.length; - finish(); await starting; - expect(flow.entries).toHaveLength(entryCount); - expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null, planVersion: null, approvalId: null }); - expect(flow.messages.some(message => message.content.includes("Begin implementation"))).toBe(false); - } finally { rmSync(flow.cwd, { recursive: true, force: true }); } -}); diff --git a/test/prototype.test.ts b/test/goals.test.ts similarity index 94% rename from test/prototype.test.ts rename to test/goals.test.ts index dba0e2b..e82e449 100644 --- a/test/prototype.test.ts +++ b/test/goals.test.ts @@ -3,8 +3,8 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { afterEach, expect, it, vi } from "vitest"; +import goalsExtension from "../src/index.js"; import { scheduleCheckIn } from "../src/prompts.js"; -import prototype from "../src/prototype.js"; const roots: string[] = []; const shutdowns: Array<() => void> = []; @@ -38,7 +38,7 @@ function fixture(child = false) { { name: "subagent_kill", parameters: { properties: { id: {} } } }, ]), }; - prototype(pi as unknown as ExtensionAPI); + goalsExtension(pi as unknown as ExtensionAPI); hooks.get("session_start")({}, ctx); const command = (value: string) => commands.get("goals").handler(value, ctx); const path = join(cwd, ".pi/plan/copy-only-main.md"); @@ -155,6 +155,27 @@ it("reloads a paused plan without launching, and retains the public worker sessi expect(readFileSync(f.path, "utf8")).toContain("first output"); }); +it.each(["FIRST OUTPUT", "renamed output", "duplicate", "historical"])("completion uses exact current subjects (%s)", async (subject) => { + const f = fixture(); await f.draft(); await f.command("ready"); + const evidence = join(f.ctx.cwd, "verification.txt"); writeFileSync(evidence, "PASS"); + const suffix = subject === "duplicate" ? "- [ ] goal: first output\n" : ""; + const history = "## Log\n- [ ] goal: first output\n"; + writeFileSync(f.path, "- [ ] goal: first output\n - [ ] unrelated task\n" + suffix + history); + const before = readFileSync(f.path, "utf8"); + await f.tools.get("CompleteGoal").execute("c", { goal: subject === "duplicate" || subject === "historical" ? "first output" : subject, evidence: [evidence], observation: "Read actual output" }, undefined, undefined, f.ctx); + const after = readFileSync(f.path, "utf8"); + if (subject === "renamed output" || subject === "duplicate") expect(after).toBe(before); + else { expect(after).toContain("- [x] goal: first output"); expect(after.split("## Log")[1]).toContain("\n- [ ] goal: first output\n"); expect(after).toContain("- [ ] unrelated task"); } +}); + +it("rejects an existing zero-byte evidence file", async () => { + const f = fixture(); await f.draft(); await f.command("ready"); + const evidence = join(f.ctx.cwd, "empty.log"); writeFileSync(evidence, ""); + const before = readFileSync(f.path, "utf8"); + const result = await f.tools.get("CompleteGoal").execute("c", { goal: "first output", evidence: [evidence], observation: "claim" }, undefined, undefined, f.ctx); + expect(result.content[0].text).toContain("Empty evidence"); expect(readFileSync(f.path, "utf8")).toBe(before); +}); + it("requires actual nonempty evidence, distinguishes manual ticks, and retains signoffs on reload", async () => { const f = fixture(); await f.draft(); await f.command("ready"); const complete = (goal: string, evidence: string[], signal?: AbortSignal) => f.tools.get("CompleteGoal").execute("t", { goal, evidence, observation: "Inspected exact saved bytes" }, signal, undefined, f.ctx); @@ -572,7 +593,7 @@ it("uses scheduler storage for ownership and the real public user controls", () }); it("keeps interactive workers open and supplies the supervisor identity for Intercom reports", async () => { - const agent = readFileSync(new URL("../prototype/agents/goals-worker.md", import.meta.url), "utf8"); + const agent = readFileSync(new URL("../agents/goals-worker.md", import.meta.url), "utf8"); expect(agent).toContain("auto-exit: false"); const f = fixture(); await f.draft(); await f.command("ready"); expect(f.messages.at(-1).message.content).toContain("supervisor Intercom session copy-only"); diff --git a/test/herdr.test.ts b/test/herdr.test.ts deleted file mode 100644 index 56e76dc..0000000 --- a/test/herdr.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { closeSupervisorPane, openSupervisorPane, supervisorCommand } from "../src/herdr.js"; - -function input() { - return { - cwd: "/repo", sourceSessionFile: "/sessions/worker.jsonl", workerSessionId: "worker-12345678", - planPath: "/repo/.pi/plan/worker-v1.md", approvalId: "approval-1", extensionPath: "/repo/src/index.ts", model: "provider/supervisor", - }; -} - -afterEach(() => vi.unstubAllEnvs()); - -describe("supervisor pane command", () => { - it("forks the planning session with pi-goals owning its Intercom dependency", () => { - const command = supervisorCommand(input()); - expect(command).toContain("'PI_GOALS_ROLE=supervisor'"); - expect(command).toContain("'PI_GOALS_APPROVAL_ID=approval-1'"); - expect(command).toContain("'pi' '-e' '/repo/src/index.ts'"); - expect(command).not.toMatch(/--no-(?:extensions|skills|prompt-templates|themes|context-files)/); - expect(command).toContain("'--fork' '/sessions/worker.jsonl'"); - expect(command).toContain("'--model' 'provider/supervisor'"); - expect(command).not.toContain("pi-supervise"); - }); - - it("accepts Herdr's text version output and stale pane cleanup", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-herdr-")); - const bin = join(cwd, "herdr"); - writeFileSync(bin, `#!/bin/sh -if [ "$1" = "--version" ]; then echo "herdr 0.8.2"; exit 0; fi -if [ "$1" = "pane" ] && [ "$2" = "split" ]; then echo '{"pane_id":"new-pane"}'; exit 0; fi -if [ "$1" = "pane" ] && [ "$2" = "run" ]; then if [ "$HERDR_SMOKE_RUN_FAIL" = "1" ]; then echo "run failed" >&2; exit 1; fi; echo '{}'; exit 0; fi -if [ "$1" = "pane" ] && [ "$2" = "close" ]; then echo '{"error":{"code":"PANE_GONE"}}' >&2; exit 1; fi -exit 2 -`); - chmodSync(bin, 0o755); - vi.stubEnv("HERDR_ENV", "1"); - vi.stubEnv("HERDR_BIN_PATH", bin); - try { - await expect(openSupervisorPane({ ...input(), cwd }, vi.fn())).resolves.toBe("new-pane"); - await expect(closeSupervisorPane("new-pane")).resolves.toBeUndefined(); - vi.stubEnv("HERDR_SMOKE_RUN_FAIL", "1"); - await expect(openSupervisorPane({ ...input(), cwd }, vi.fn())).rejects.toThrow("run failed"); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); -}); diff --git a/test/intercom-broker.test.ts b/test/intercom-broker.test.ts deleted file mode 100644 index f09f729..0000000 --- a/test/intercom-broker.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { spawn } from "node:child_process"; -import { once } from "node:events"; -import { mkdtempSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { IntercomClient } from "pi-intercom/broker/client.ts"; -import { expect, it, vi } from "vitest"; -import { GoalIntercom } from "../src/intercom.js"; - -it("exchanges readiness, views and exact advice over a real isolated pi-intercom broker", async () => { - const directory = mkdtempSync(join(tmpdir(), "goals-intercom-")); - vi.stubEnv("PI_CODING_AGENT_DIR", directory); - const broker = spawn(process.execPath, [resolve("node_modules/tsx/dist/cli.mjs"), resolve("node_modules/pi-intercom/broker/broker.ts")], { env: { ...process.env }, stdio: ["ignore", "pipe", "pipe"] }); - const clients: IntercomClient[] = []; - const shutdowns: Array<() => void> = []; - try { - await new Promise<void>((resolveReady, reject) => { - const timer = setTimeout(() => reject(new Error("Isolated broker did not start.")), 5000); - broker.stdout.on("data", chunk => { if (String(chunk).includes("Intercom broker started")) { clearTimeout(timer); resolveReady(); } }); - broker.once("exit", code => { clearTimeout(timer); reject(new Error(`Broker exited: ${code}`)); }); - }); - async function endpoint(role: "worker" | "supervisor") { - const client = new IntercomClient(); - clients.push(client); - await client.connect({ name: role, cwd: directory, model: "test", pid: process.pid, startedAt: Date.now(), lastActivity: Date.now(), extensions: [{ namespace: "pi-goals", ownerEligible: false }] }); - const entries: any[] = []; - const api = { - on: (name: string, callback: () => void) => { if (name === "session_shutdown") shutdowns.push(callback); }, - appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }), - events: { - on() {}, - emit: (_name: string, registration: any) => { - client.on("extension_message", message => registration.onEvent({ type: "message", fromSessionId: message.fromSessionId, payload: message.payload })); - client.on("disconnected", () => registration.onEvent({ type: "connection", connected: false, supported: true })); - client.onBrokerMessage(message => { if (message.type === "session_left") registration.onEvent(message); }); - registration.onReady({ - snapshot: () => ({ connected: client.isConnected(), supported: client.supportsFeature("extension-bus-v1") }), - publish: (payload: unknown) => client.sendExtensionMessage({ type: "extension_publish", namespace: "pi-goals", audience: "capable", payload }), - }); - }, - }, - }; - const link = new GoalIntercom(api as unknown as ExtensionAPI); - link.configure("isolated-binding", role, { sessionManager: { getEntries: () => entries }, ui: { notify() {} } } as any); - return { link, client }; - } - const worker = await endpoint("worker"); - const supervisor = await endpoint("supervisor"); - expect(worker.link.connected).toBe(false); - supervisor.link.markReady(); - await Promise.all([worker.link.waitReady(3000), supervisor.link.waitReady(3000)]); - const viewed = new Promise<string>(resolveView => { supervisor.link.onView = view => resolveView(view.text); }); - const view = worker.link.view("The worker stopped.\nModal uses a remote GPU.", "settled"); - expect(await viewed).toBe(view.text); - const advice = "Check the Modal dependency. Keep the local GPU queue paused."; - const received = new Promise<string>(resolveAdvice => { worker.link.onSteer = resolveAdvice; }); - supervisor.link.steer(advice); - expect(await received).toBe(advice); - console.log("Intercom broker: readiness confirmed; exact worker view and supervisor advice received."); - } finally { - for (const shutdown of shutdowns) await shutdown(); - for (const client of clients) await client.disconnect(); - if (broker.exitCode === null) { broker.kill("SIGTERM"); await once(broker, "exit"); } - vi.unstubAllEnvs(); - rmSync(directory, { recursive: true, force: true }); - } -}, 15_000); diff --git a/test/intercom-compaction.test.ts b/test/intercom-compaction.test.ts deleted file mode 100644 index dfb8f12..0000000 --- a/test/intercom-compaction.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { afterEach, expect, it, vi } from "vitest"; -import { GoalIntercom } from "../src/intercom.js"; -import { intercomFixture } from "./intercom-fixture.js"; - -const shutdowns: Array<() => void> = []; -function setup(role: "worker" | "supervisor", entries: any[] = []) { - const fixture = intercomFixture(); - const hooks = new Map<string, any>(); - const ctx = { isIdle: vi.fn(() => true), hasPendingMessages: vi.fn(() => false), sessionManager: { getEntries: () => entries }, ui: { notify: vi.fn() } }; - const pi = { events: fixture.events, on: (name: string, hook: any) => hooks.set(name, hook), appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }) }; - const link = new GoalIntercom(pi as unknown as ExtensionAPI); - link.configure("binding", role, ctx as any, true); - const delivered = vi.fn(); - link.onSteer = delivered; - link.onView = view => delivered(view.text); - shutdowns.push(() => hooks.get("session_shutdown")()); - const receive = (id: string, text: string) => fixture.receive({ binding: "binding", role: role === "worker" ? "supervisor" : "worker", kind: role === "worker" ? "steer" : "view", reason: "settled", id, text }); - const accept = (text: string) => hooks.get("message_start")({ message: { role: "user", content: role === "worker" ? `[supervisor] ${text}` : text } }); - return { link, hooks, ctx, entries, fixture, delivered, receive, accept }; -} -afterEach(() => { for (const stop of shutdowns.splice(0)) stop(); vi.useRealTimers(); }); - -it.each(["worker", "supervisor"] as const)("retains %s messages across successful, failed and cancelled manual compaction", async role => { - vi.useFakeTimers(); - const r = setup(role); - for (const [id, event] of [["success", "session_compact"], ["failure", "session_compact_failed"], ["cancelled", "session_compact_failed"]]) { - r.ctx.isIdle.mockReturnValue(false); - await r.hooks.get("session_before_compact")({}); - r.receive(id, `evidence-${id}`); - await vi.advanceTimersByTimeAsync(60_000); - expect(r.delivered).not.toHaveBeenCalledWith(`evidence-${id}`); - expect(r.fixture.sent.some(m => m.kind === "received" && m.id === id)).toBe(false); - await r.hooks.get(event)({ aborted: id === "cancelled" }); - await vi.advanceTimersByTimeAsync(0); // Another extension may still be handling session_compact. - expect(r.delivered).not.toHaveBeenCalledWith(`evidence-${id}`); - r.ctx.isIdle.mockReturnValue(true); - await vi.advanceTimersByTimeAsync(1000); - expect(r.delivered).toHaveBeenCalledWith(`evidence-${id}`); - await r.accept(`evidence-${id}`); - expect(r.fixture.sent.some(m => m.kind === "received" && m.id === id)).toBe(true); - } -}); - -it("retains distinct deltas through reload and does not duplicate delayed presentation", async () => { - vi.useFakeTimers(); - const first = setup("supervisor"); - first.ctx.isIdle.mockReturnValue(false); - await first.hooks.get("session_before_compact")({}); - first.receive("one", "first independent evidence"); - first.receive("two", "second independent evidence"); - await first.hooks.get("session_shutdown")(); - const resumed = setup("supervisor", [...first.entries]); - await vi.advanceTimersByTimeAsync(0); - expect(resumed.delivered.mock.calls).toEqual([["first independent evidence"]]); - resumed.ctx.hasPendingMessages.mockReturnValue(true); - resumed.link.resumeDelivery(); - resumed.receive("one", "first independent evidence"); - await vi.advanceTimersByTimeAsync(60_000); - expect(resumed.delivered).toHaveBeenCalledTimes(1); - resumed.ctx.hasPendingMessages.mockReturnValue(false); - await resumed.accept("first independent evidence"); - await vi.advanceTimersByTimeAsync(0); - expect(resumed.delivered.mock.calls).toEqual([["first independent evidence"], ["second independent evidence"]]); - await resumed.accept("second independent evidence"); - resumed.receive("two", "second independent evidence"); - expect(resumed.delivered).toHaveBeenCalledTimes(2); -}); diff --git a/test/intercom-fixture.ts b/test/intercom-fixture.ts deleted file mode 100644 index 3be85aa..0000000 --- a/test/intercom-fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -export function intercomFixture() { - let autoHello = true; - let registration: any; - const sent: any[] = []; - let connected = true; - const receive = (payload: any, fromSessionId = "peer") => registration.onEvent({ type: "message", fromSessionId, payload }); - return { - sent, receive, - replyToHello: (value: boolean) => { autoHello = value; }, - event: (event: any) => registration.onEvent(event), - connect: (value: boolean) => { connected = value; registration.onEvent({ type: "connection", connected: value, supported: true }); }, - events: { - on: () => () => {}, - emit: (name: string, value: any) => { - if (name !== "intercom:extension-register") return false; - registration = value; - value.onReady({ - snapshot: () => ({ connected, supported: true }), - publish: (message: any) => { - sent.push(message); - if (message.kind === "hello" && !message.reply && autoHello) queueMicrotask(() => receive({ ...message, role: message.role === "worker" ? "supervisor" : "worker", ready: true, reply: true })); - }, - }); - return true; - }, - }, - }; -} diff --git a/test/intercom-handshake.test.ts b/test/intercom-handshake.test.ts deleted file mode 100644 index 0cd1165..0000000 --- a/test/intercom-handshake.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { expect, it, vi } from "vitest"; -import { GoalIntercom } from "../src/intercom.js"; -import { pairedIntercomFixture } from "./paired-intercom-fixture.js"; - -function endpoint(transport: ReturnType<typeof pairedIntercomFixture>["worker"]) { - const entries: any[] = []; - const ctx = { sessionManager: { getEntries: () => entries }, ui: { notify: vi.fn() } }; - const hooks = new Map<string, any>(); - const link = new GoalIntercom({ events: transport.events, on: (name: string, hook: any) => hooks.set(name, hook), appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }) } as unknown as ExtensionAPI); - return { link, ctx, entries, accept: (text: string) => hooks.get("message_start")({ message: { role: "user", content: text } }) }; -} -const settle = () => new Promise(resolve => setImmediate(resolve)); - -it("re-handshakes unchanged peers in either direction without hello ping-pong or lost advice", async () => { - const wire = pairedIntercomFixture(); - const worker = endpoint(wire.worker); - const supervisor = endpoint(wire.supervisor); - worker.link.configure("binding", "worker", worker.ctx as any); - supervisor.link.configure("binding", "supervisor", supervisor.ctx as any); - supervisor.link.markReady(); - await settle(); - expect(worker.link.connected && supervisor.link.connected).toBe(true); - const deliver = vi.fn(); worker.link.onSteer = deliver; - for (const side of [worker, supervisor, worker]) { - const before = wire.worker.sent.length + wire.supervisor.sent.length; - side.link.configure("binding", side === worker ? "worker" : "supervisor", side.ctx as any, true); - await side.link.waitReady(100); - await settle(); - expect(worker.link.connected && supervisor.link.connected).toBe(true); - expect(wire.worker.sent.length + wire.supervisor.sent.length - before).toBe(2); - } - const before = wire.worker.sent.length + wire.supervisor.sent.length; - worker.link.configure("binding", "worker", worker.ctx as any, true); - supervisor.link.configure("binding", "supervisor", supervisor.ctx as any, true); - await settle(); - expect(worker.link.connected && supervisor.link.connected).toBe(true); - expect(wire.worker.sent.length + wire.supervisor.sent.length - before).toBe(4); - supervisor.link.steer("Read actual output."); - await settle(); - expect(deliver).toHaveBeenCalledExactlyOnceWith("Read actual output."); - expect(worker.ctx.ui.notify).not.toHaveBeenCalled(); - expect(supervisor.ctx.ui.notify).not.toHaveBeenCalled(); -}); - -it("replays pending advice and views across either role's own readiness transition", async () => { - const wire = pairedIntercomFixture(); - const worker = endpoint(wire.worker), supervisor = endpoint(wire.supervisor); - worker.link.configure("binding", "worker", worker.ctx as any); - supervisor.link.configure("binding", "supervisor", supervisor.ctx as any, true); - await settle(); - wire.supervisor.drop = message => message.kind === "steer"; - supervisor.link.steer("Pending advice."); - wire.supervisor.drop = () => false; - const deliver = vi.fn(); worker.link.onSteer = deliver; - supervisor.link.markNotReady(); await settle(); - expect(worker.link.connected).toBe(false); - supervisor.link.markReady(); await settle(); - expect(deliver).toHaveBeenCalledExactlyOnceWith("Pending advice."); - await worker.accept("[supervisor] Pending advice."); - worker.link.markNotReady(); await settle(); - const onView = vi.fn(); supervisor.link.onView = onView; - worker.link.view("Fresh view.", "settled"); - expect(onView).not.toHaveBeenCalled(); - worker.link.markReady(); await settle(); - expect(onView).toHaveBeenCalledTimes(1); - await supervisor.accept(onView.mock.calls[0][0].text); - expect(worker.link.connected && supervisor.link.connected).toBe(true); - wire.worker.connect(false); wire.worker.connect(true); await settle(); - expect(worker.link.connected && supervisor.link.connected).toBe(true); - expect(deliver).toHaveBeenCalledTimes(1); - expect(onView).toHaveBeenCalledTimes(1); -}); - -it("cancels pending waits immediately on detach or reconfiguration", async () => { - const wire = pairedIntercomFixture(); - const worker = endpoint(wire.worker); - worker.link.configure("binding", "worker", worker.ctx as any); - const cancelled = expect(worker.link.waitReady()).rejects.toThrow("plan detached"); - worker.link.detach(); await cancelled; - worker.link.configure("next", "worker", worker.ctx as any); - const replaced = expect(worker.link.waitReady()).rejects.toThrow("reconfiguration"); - worker.link.configure("third", "worker", worker.ctx as any); await replaced; -}); diff --git a/test/intercom.test.ts b/test/intercom.test.ts deleted file mode 100644 index c18943c..0000000 --- a/test/intercom.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { describe, expect, it, vi } from "vitest"; -import { GoalIntercom } from "../src/intercom.js"; -import { intercomFixture } from "./intercom-fixture.js"; - -function setup(role: "worker" | "supervisor", entries: any[] = []) { - const fixture = intercomFixture(); - const hooks = new Map<string, any>(); - const ctx = { isIdle: vi.fn(() => true), hasPendingMessages: vi.fn(() => false), sessionManager: { getEntries: () => entries }, ui: { notify: vi.fn() } }; - const api = { events: fixture.events, on: (name: string, hook: any) => hooks.set(name, hook), appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }) }; - const link = new GoalIntercom(api as unknown as ExtensionAPI); - link.configure("binding", role, ctx as any); - return { link, fixture, entries, ctx, hooks }; -} - -describe("pi-intercom transport", () => { - it("receives exact advice once, acknowledges it and rejects unrelated peers", async () => { - const runtime = setup("worker"); - await runtime.link.waitReady(); - const instruction = "Check the Modal dependency.\nKeep the local queue paused."; - const delivered = vi.fn(); - runtime.link.onSteer = delivered; - const message = { binding: "binding", role: "supervisor", kind: "steer", id: "instruction", text: instruction }; - runtime.fixture.receive({ ...message, binding: "other" }); - runtime.fixture.receive(message, "wrong-peer"); - expect(delivered).not.toHaveBeenCalled(); - runtime.fixture.receive(message); - expect(runtime.fixture.sent.filter(message => message.kind === "received")).toHaveLength(0); - await runtime.hooks.get("message_start")({ message: { role: "user", content: `[supervisor] ${instruction}` } }); - runtime.fixture.receive(message); - expect(delivered).toHaveBeenCalledExactlyOnceWith(instruction); - expect(runtime.fixture.sent.filter(message => message.kind === "received")).toHaveLength(2); - await runtime.hooks.get("session_shutdown")(); - runtime.fixture.receive({ ...message, id: "late" }); - expect(delivered).toHaveBeenCalledTimes(1); - }); - - it("restores an unacknowledged steer on reconnect and stops replay after acknowledgment", async () => { - const first = setup("supervisor"); - first.link.markReady(); - await first.link.waitReady(); - const id = first.link.steer("Read the full output."); - await first.hooks.get("session_shutdown")(); - const resumed = setup("supervisor", [...first.entries]); - resumed.link.markReady(); - await resumed.link.waitReady(); - const retries = resumed.fixture.sent.filter(message => message.kind === "steer"); - expect(retries.length).toBeGreaterThan(0); - for (const retry of retries) expect(retry).toMatchObject({ id, text: "Read the full output." }); - resumed.fixture.receive({ binding: "binding", role: "worker", kind: "received", id }); - resumed.fixture.connect(false); - expect(resumed.link.connected).toBe(false); - expect(() => resumed.link.steer("Must not send.")).toThrow("disconnected"); - resumed.fixture.connect(true); - await resumed.link.waitReady(); - expect(resumed.fixture.sent.filter(message => message.kind === "steer")).toHaveLength(retries.length); - }); - - it("advances the incremental overview only after acknowledgment", async () => { - const runtime = setup("worker"); - await runtime.link.waitReady(); - const view = runtime.link.view("The worker stopped.", "settled", "entry-1", true); - expect(runtime.link.acknowledgedEntry).toBeUndefined(); - runtime.fixture.receive({ binding: "binding", role: "supervisor", kind: "received", id: view.id }); - expect(runtime.link.acknowledgedEntry).toBe("entry-1"); - const resumed = setup("worker", [...runtime.entries]); - expect(resumed.link.acknowledgedEntry).toBe("entry-1"); - }); - - it("cancels a readiness wait on shutdown", async () => { - const runtime = setup("worker"); - await runtime.link.waitReady(); - runtime.fixture.connect(false); - const wait = runtime.link.waitReady(); - const rejection = expect(wait).rejects.toThrow("Session ended"); - await runtime.hooks.get("session_shutdown")(); - await rejection; - }); -}); - -it("does not acknowledge a synchronous handoff failure, and retries the instruction", async () => { - vi.useFakeTimers(); - const runtime = setup("worker"); - await runtime.link.waitReady(); - const delivery = vi.fn().mockImplementationOnce(() => { throw new Error("Delivery unavailable"); }); - runtime.link.onSteer = delivery; - const message = { binding: "binding", role: "supervisor", kind: "steer", id: "retry", text: "Inspect evidence." }; - runtime.fixture.receive(message); - expect(runtime.fixture.sent.filter(m => m.kind === "received")).toHaveLength(0); - expect(runtime.entries.filter(e => e.data.direction === "in")).toHaveLength(0); - runtime.link.resumeDelivery(); - await vi.advanceTimersByTimeAsync(0); - expect(delivery).toHaveBeenCalledTimes(2); - await runtime.hooks.get("message_start")({ message: { role: "user", content: "[supervisor] Inspect evidence." } }); - expect(runtime.fixture.sent.filter(m => m.kind === "received")).toHaveLength(1); - await runtime.hooks.get("session_shutdown")(); - vi.useRealTimers(); -}); - -it("reports a peer startup failure immediately and recovers on its next ready hello", async () => { - const runtime = setup("worker"); - await runtime.link.waitReady(); - runtime.fixture.receive({ binding: "binding", role: "supervisor", kind: "hello", id: "hello", reply: true, ready: false, failure: "Compaction cancelled; use /goals supervise." }); - await expect(runtime.link.waitReady()).rejects.toThrow("Compaction cancelled"); - runtime.fixture.receive({ binding: "binding", role: "supervisor", kind: "hello", id: "hello", reply: true, ready: true }); - await runtime.link.waitReady(); - expect(runtime.link.connected).toBe(true); - await runtime.hooks.get("session_shutdown")(); -}); - -it("detaches a completed binding and ignores its late advice without replay errors or false acceptance", async () => { - const runtime = setup("worker"); - await runtime.link.waitReady(); - const delivery = vi.fn(); - runtime.link.onSteer = delivery; - runtime.link.detach(); - runtime.fixture.receive({ binding: "binding", role: "supervisor", kind: "steer", id: "late", text: "Obsolete advice." }); - expect(runtime.link.connected).toBe(false); - expect(delivery).not.toHaveBeenCalled(); - expect(runtime.ctx.ui.notify).not.toHaveBeenCalled(); - expect(runtime.fixture.sent.filter(m => m.kind === "received")).toHaveLength(0); - expect(runtime.fixture.sent.at(-1)).toMatchObject({ kind: "hello", ready: false }); -}); diff --git a/test/native-compaction-delivery.test.ts b/test/native-compaction-delivery.test.ts deleted file mode 100644 index 8dbe1c9..0000000 --- a/test/native-compaction-delivery.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { spawn } from "node:child_process"; -import { once } from "node:events"; -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; -import { createServer } from "node:http"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; -import { expect, it } from "vitest"; - -it.each(["worker", "supervisor"] as const)("real Pi preserves %s delivery through compaction success, failure and cancellation", async role => { - for (const outcome of ["success", "failure", "cancel"]) { - const cwd = mkdtempSync(join(tmpdir(), "goals-native-compaction-")); - const agentDir = join(cwd, "agent"); - mkdirSync(agentDir); - writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ compaction: { enabled: false, keepRecentTokens: 1000, reserveTokens: 1000 }, retry: { enabled: false } })); - let requests = 0; - const server = createServer(async (req, res) => { - for await (const _chunk of req) { /* consume local request */ } - requests++; - if (outcome === "failure" && requests === 3) { res.writeHead(400); res.end(JSON.stringify({ error: { message: "deterministic compaction failure" } })); return; } - res.writeHead(200, { "content-type": "text/event-stream" }); - res.end(`data: ${JSON.stringify({ choices: [{ index: 0, delta: { content: "Inspected the supplied context." }, finish_reason: null }] })}\n\ndata: ${JSON.stringify({ choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 8000, completion_tokens: 10, total_tokens: 8010 } })}\n\ndata: [DONE]\n\n`); - }); - await new Promise<void>(done => server.listen(0, "127.0.0.1", done)); - const address = server.address(); - if (!address || typeof address === "string") throw new Error("No local model port"); - const child = spawn(resolve("node_modules/.bin/pi"), ["--mode", "rpc", "--no-extensions", "--model", "offline/test", "-e", resolve("test/fixtures/offline-model.ts"), "-e", resolve("test/fixtures/compaction-delivery.ts")], { cwd, env: { ...process.env, PI_CODING_AGENT_DIR: agentDir, PI_GOALS_OFFLINE_MODEL_URL: `http://127.0.0.1:${address.port}`, PI_GOALS_TEST_DELIVERY_ROLE: role, PI_GOALS_TEST_COMPACTION: outcome } }); - const messages: any[] = []; - let buffer = "", stderr = ""; - child.stdout.on("data", data => { - buffer += data; - while (buffer.includes("\n")) { const n = buffer.indexOf("\n"); const line = buffer.slice(0, n); buffer = buffer.slice(n + 1); if (line.trim()) messages.push(JSON.parse(line)); } - }); - child.stderr.on("data", data => { stderr += data; }); - const send = (message: object) => child.stdin.write(`${JSON.stringify(message)}\n`); - const wait = async (predicate: (message: any) => boolean) => { - for (let i = 0; i < 500; i++) { const found = messages.find(predicate); if (found) return found; await new Promise(done => setTimeout(done, 20)); } - throw new Error(`Timed out ${role}/${outcome}: ${stderr}\n${JSON.stringify(messages.slice(-8))}`); - }; - try { - send({ type: "prompt", id: "seed", message: "Inspect this bounded test context. " + "Saved observation. ".repeat(2000) }); - await wait(m => m.type === "agent_settled"); - messages.length = 0; - send({ type: "prompt", id: "second-turn", message: "Keep this most recent turn for continued work. " + "Recent context. ".repeat(500) }); - await wait(m => m.type === "agent_settled"); - send({ type: "compact", id: "compact" }); - const completion = await wait(m => m.type === "response" && m.id === "compact"); - expect(completion.success, JSON.stringify(completion)).toBe(outcome === "success"); - await wait(m => m.type === "message_start" && m.message?.role === "user" && JSON.stringify(m.message.content).includes("Retained evidence arrived")); - await wait(m => m.type === "message_end" && m.message?.role === "user" && JSON.stringify(m.message.content).includes("Retained evidence arrived")); - send({ type: "get_state", id: "state" }); - const state = await wait(m => m.type === "response" && m.id === "state"); - const transcript = readFileSync(state.data.sessionFile, "utf8"); - expect(transcript).toContain('"direction":"queued"'); - expect(transcript).toContain('"direction":"in"'); - expect(messages.filter(m => m.type === "message_start" && m.message?.role === "user" && JSON.stringify(m.message.content).includes("Retained evidence arrived"))).toHaveLength(1); - expect(messages.filter(m => m.type === "extension_error")).toEqual([]); - console.log(`real Pi ${role}/${outcome}: retained message presented once and saved, no extension errors`); - } finally { - child.kill("SIGTERM"); await once(child, "exit"); - await new Promise<void>(done => server.close(() => done())); - rmSync(cwd, { recursive: true, force: true }); - } - } -}, 45_000); diff --git a/test/native-intercom.test.ts b/test/native-intercom.test.ts deleted file mode 100644 index e0d9893..0000000 --- a/test/native-intercom.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process"; -import { once } from "node:events"; -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; -import { createServer } from "node:http"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; -import { expect, it } from "vitest"; -import { approveGoalDescription, steerWorkerDescription, supervisorStoppedReview } from "../src/prompts.js"; - -// Pi/OpenAI: RPC drives test inputs only; the two sessions communicate exclusively through Intercom. -class Driver { - messages: any[] = []; - stderr = ""; - private waiters: Array<{ predicate: (value: any) => boolean; resolve: (value: any) => void }> = []; - constructor(readonly process: ChildProcessWithoutNullStreams) { - let buffer = ""; - process.stdout.setEncoding("utf8"); - process.stdout.on("data", chunk => { - buffer += chunk; - while (buffer.includes("\n")) { - const newline = buffer.indexOf("\n"); - const line = buffer.slice(0, newline); buffer = buffer.slice(newline + 1); - if (!line.trim()) continue; - const message = JSON.parse(line); - this.messages.push(message); - for (const waiter of [...this.waiters]) if (waiter.predicate(message)) { this.waiters.splice(this.waiters.indexOf(waiter), 1); waiter.resolve(message); } - } - }); - process.stderr.on("data", chunk => { this.stderr += String(chunk); }); - } - send(value: object): void { this.process.stdin.write(`${JSON.stringify(value)}\n`); } - wait(predicate: (value: any) => boolean): Promise<any> { - const found = this.messages.find(predicate); - if (found) return Promise.resolve(found); - return new Promise((resolveWait, reject) => { - const timer = setTimeout(() => reject(new Error(`Pi response timed out. stderr=${this.stderr}\nLast messages=${JSON.stringify(this.messages.slice(-5))}`)), 8000); - this.waiters.push({ predicate, resolve: value => { clearTimeout(timer); resolveWait(value); } }); - }); - } -} - -it("runs a forked Pi supervisor and receives its exact instruction in another Pi session", async () => { - const cwd = mkdtempSync(join(tmpdir(), "goals-native-pair-")); - const agentDir = join(cwd, "agent"); - const advice = "Read the real outputs before declaring completion."; - const children: ChildProcessWithoutNullStreams[] = []; - let worker: Driver | undefined; - let supervisor: Driver | undefined; - let workerFile: string | undefined; - let supervisorFile: string | undefined; - let supervisorTools: string[] = []; - let supervisorRequest: any; - let latestRequest: any; - const server = createServer(async (request, response) => { - let body = ""; - for await (const chunk of request) body += chunk; - const input = JSON.parse(body); - latestRequest = input; - const latest = input.messages.filter((message: any) => !JSON.stringify(message.content).includes("Full active plan:")).at(-1); - const steer = latest.role === "user" && JSON.stringify(latest.content).includes("The worker stopped."); - if (steer) { - supervisorRequest = input; - supervisorTools = input.tools.map((tool: any) => tool.function.name); - } - response.writeHead(200, { "content-type": "text/event-stream" }); - const delta = steer ? { tool_calls: [{ index: 0, id: "test-steer", type: "function", function: { name: "SteerWorker", arguments: JSON.stringify({ instruction: advice }) } }] } : { content: "Test context retained. Actual outputs still need inspection." }; - response.write(`data: ${JSON.stringify({ choices: [{ index: 0, delta, finish_reason: null }] })}\n\n`); - response.write(`data: ${JSON.stringify({ choices: [{ index: 0, delta: {}, finish_reason: steer ? "tool_calls" : "stop" }], usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 } })}\n\n`); - response.end("data: [DONE]\n\n"); - }); - await new Promise<void>(resolveListen => server.listen(0, "127.0.0.1", resolveListen)); - const address = server.address(); - if (!address || typeof address === "string") throw new Error("Offline server did not bind."); - const env = { ...process.env, PI_CODING_AGENT_DIR: agentDir, PI_GOALS_OFFLINE_MODEL_URL: `http://127.0.0.1:${address.port}` }; - const broker = spawn(process.execPath, [resolve("node_modules/tsx/dist/cli.mjs"), resolve("node_modules/pi-intercom/broker/broker.ts")], { env }); - children.push(broker); - try { - await new Promise<void>((resolveReady, reject) => { - const timer = setTimeout(() => reject(new Error("Broker did not start.")), 5000); - broker.stdout.on("data", chunk => { if (String(chunk).includes("Intercom broker started")) { clearTimeout(timer); resolveReady(); } }); - broker.once("exit", code => { clearTimeout(timer); reject(new Error(`Broker exited ${code}`)); }); - }); - const common = ["--mode", "rpc", "--no-extensions", "--model", "offline/test", "-e", resolve("test/fixtures/offline-model.ts")]; - const workerProcess = spawn(resolve("node_modules/.bin/pi"), [...common, "-e", resolve("test/fixtures/intercom-worker.ts")], { cwd, env }); - children.push(workerProcess); - worker = new Driver(workerProcess); - worker.send({ type: "prompt", id: "planning", message: "Retain this planning context for the supervisor fork." }); - await worker.wait(message => message.type === "agent_end"); - worker.send({ type: "get_state", id: "worker-state" }); - const state = await worker.wait(message => message.type === "response" && message.id === "worker-state"); - workerFile = state.data.sessionFile; - expect(workerFile).toBeTruthy(); - writeFileSync(join(cwd, "plan.md"), "1. [ ] goal: inspect actual outputs\n - discriminator: raw output inspected\n"); - mkdirSync(join(agentDir, "extensions"), { recursive: true }); - writeFileSync(join(agentDir, "extensions/profile-tools.ts"), `export { default } from ${JSON.stringify(resolve("test/fixtures/profile-tools.ts"))};`); - // Exercise normal discovery in an isolated profile; never load the user's extensions in this test. - const supervisorProcess = spawn(resolve("node_modules/.bin/pi"), [...common.filter(arg => arg !== "--no-extensions"), "-e", resolve("src/index.ts"), "--fork", workerFile!], { cwd, env: { - ...env, PI_GOALS_ROLE: "supervisor", PI_GOALS_WORKER_ID: state.data.sessionId, PI_GOALS_OWNER_SESSION_ID: state.data.sessionId, - PI_GOALS_PLAN_PATH: join(cwd, "plan.md"), PI_GOALS_APPROVAL_ID: "native-pair-test", PI_GOALS_MODEL_EXPLICIT: "0", - } }); - children.push(supervisorProcess); - supervisor = new Driver(supervisorProcess); - const received = await worker.wait(message => message.type === "message_start" && message.message?.role === "user" && JSON.stringify(message.message.content).includes(`[supervisor] ${advice}`)); - expect(JSON.stringify(received)).toContain(advice); - const result = await supervisor.wait(message => message.type === "tool_execution_end" && message.toolName === "SteerWorker"); - expect(result.isError).toBe(false); - expect(supervisorTools).toContain("SteerWorker"); - expect(supervisorTools).toContain("intercom"); - expect(supervisorTools).toContain("bash"); - expect(supervisorTools).toContain("edit"); - expect(supervisorTools).toContain("write"); - expect(supervisorTools).toContain("profile_inspection"); - expect(JSON.stringify(supervisorRequest.messages)).toContain(supervisorStoppedReview); - const description = (name: string) => supervisorRequest.tools.find((tool: any) => tool.function.name === name).function.description; - expect(description("SteerWorker")).toBe(steerWorkerDescription); - expect(description("ApproveGoal")).toBe(approveGoalDescription); - supervisor.send({ type: "get_state", id: "supervisor-state" }); - const supervisorState = await supervisor.wait(message => message.type === "response" && message.id === "supervisor-state"); - supervisorFile = supervisorState.data.sessionFile; - expect(supervisorFile).not.toBe(workerFile); - expect(readFileSync(supervisorFile!, "utf8")).toContain("Retain this planning context"); - await supervisor.wait(message => message.type === "agent_settled"); - supervisorProcess.kill("SIGTERM"); - await once(supervisorProcess, "exit"); - const freshEnv = Object.fromEntries(Object.entries(env).filter(([name]) => !/^PI_GOALS_(ROLE|WORKER_ID|OWNER_SESSION_ID|PLAN_PATH|APPROVAL_ID|MODEL_EXPLICIT)$/.test(name))); - const resumedProcess = spawn(resolve("node_modules/.bin/pi"), [...common.filter(arg => arg !== "--no-extensions"), "-e", resolve("src/index.ts"), "--session", supervisorFile!], { cwd, env: freshEnv }); - children.push(resumedProcess); - supervisor = new Driver(resumedProcess); - supervisor.send({ type: "prompt", id: "resumed-review", message: "Confirm resumed identity without sending another instruction." }); - await supervisor.wait(message => message.type === "agent_settled"); - const resumedTools = latestRequest.tools.map((tool: any) => tool.function.name); - expect(resumedTools).toContain("ApproveGoal"); - expect(resumedTools).toContain("SteerWorker"); - expect(resumedTools).toContain("profile_inspection"); - expect(resumedTools).not.toContain("CompleteGoal"); - expect(JSON.stringify(latestRequest.messages)).toContain("autonomously extending the user's agency"); - console.log(`Native Pi pair: fork retained planning context; SteerWorker delivered exactly: ${advice}`); - } finally { - if (process.env.PI_GOALS_EVIDENCE_DIR) { - mkdirSync(process.env.PI_GOALS_EVIDENCE_DIR, { recursive: true }); - for (const [name, driver] of [["worker", worker], ["supervisor", supervisor]] as const) if (driver) { - writeFileSync(join(process.env.PI_GOALS_EVIDENCE_DIR, `${name}-events.jsonl`), driver.messages.map(message => JSON.stringify(message)).join("\n")); - writeFileSync(join(process.env.PI_GOALS_EVIDENCE_DIR, `${name}-stderr.txt`), driver.stderr); - } - } - for (const child of children.reverse()) if (child.exitCode === null) { child.kill("SIGTERM"); await once(child, "exit"); } - server.closeAllConnections(); server.close(); - rmSync(cwd, { recursive: true, force: true }); - } -}, 25_000); diff --git a/test/package-agent.test.ts b/test/package-agent.test.ts deleted file mode 100644 index b5090a7..0000000 --- a/test/package-agent.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { describe, expect, it } from "vitest"; - -interface PackageManifest { - files: string[]; - pi: { extensions: string[]; subagents?: unknown }; -} - -describe("package manifest", () => { - it("includes the extension without registering a packaged subagent", () => { - const root = resolve(import.meta.dirname, ".."); - const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")) as PackageManifest; - expect(manifest.files).toEqual(["src", "prototype", "README.md"]); - expect(manifest.pi.extensions).toEqual(["./src/prototype.ts"]); - expect(manifest.private).toBe(true); - expect(manifest.pi.subagents).toBeUndefined(); - }); -}); diff --git a/test/package.test.ts b/test/package.test.ts new file mode 100644 index 0000000..2b32e35 --- /dev/null +++ b/test/package.test.ts @@ -0,0 +1,15 @@ +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { expect, it } from "vitest"; + +it("declares current entry and bundled extension resources that exist after install", () => { + const manifest = JSON.parse(readFileSync("package.json", "utf8")); + expect(manifest.pi.extensions[0]).toBe("./src/index.ts"); + for (const path of manifest.pi.extensions) expect(existsSync(resolve(path)), path).toBe(true); + for (const name of ["pi-subagents", "pi-intercom", "pi-schedule-prompt"]) { + expect(manifest.dependencies[name]).toBeTruthy(); + expect(manifest.bundledDependencies).toContain(name); + } + expect(manifest.dependencies["pi-subagents"]).toContain("953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4"); + expect(existsSync("agents/goals-worker.md")).toBe(true); +}); diff --git a/test/paired-intercom-fixture.ts b/test/paired-intercom-fixture.ts deleted file mode 100644 index 5778cd9..0000000 --- a/test/paired-intercom-fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Wire two real GoalIntercom adapters. This router never invents hello replies. -export function pairedIntercomFixture() { - const receivers = new Map<string, (event: any) => void>(); - const endpoint = (id: string, peer: string) => { - const sent: any[] = []; - let connected = true; - const transport = { - sent, - drop: (_message: any) => false, - connect: (value: boolean) => { connected = value; receivers.get(id)?.({ type: "connection", connected: value, supported: true }); }, - events: { - on: () => () => {}, - emit: (name: string, registration: any) => { - if (name !== "intercom:extension-register") return false; - receivers.set(id, registration.onEvent); - registration.onReady({ - snapshot: () => ({ connected, supported: true }), - publish: (message: any) => { - sent.push(message); - if (sent.length > 200) throw new Error("Handshake did not settle; possible hello loop."); - if (connected && !transport.drop(message)) queueMicrotask(() => receivers.get(peer)?.({ type: "message", fromSessionId: id, payload: message })); - }, - }); - return true; - }, - }, - }; - return transport; - }; - return { worker: endpoint("worker", "supervisor"), supervisor: endpoint("supervisor", "worker") }; -} diff --git a/test/prompts.test.ts b/test/prompts.test.ts index 8437b60..bf16a19 100644 --- a/test/prompts.test.ts +++ b/test/prompts.test.ts @@ -1,34 +1,5 @@ import { describe, expect, it } from "vitest"; -import { approveGoalDescription, completeGoalDescription, goalApprovalRecorded, planDrafting, planningState, resync, steerWorkerDescription, supervisorPeriodicReview, supervisorPlanChangeReview, supervisorReadyReview, supervisorStartedReview, supervisorStoppedReview } from "../src/prompts.js"; - -describe("supervisor event tasks", () => { - it("asks a ready or stopped worker to resume through SteerWorker rather than only review", () => { - expect(supervisorReadyReview).toContain("Use SteerWorker"); - expect(supervisorStoppedReview).toContain("judge whether the agreed goal is actually achieved"); - expect(supervisorStoppedReview).toContain("use SteerWorker to send the next useful instruction and resume work"); - expect(supervisorStoppedReview).toContain("verified dependency"); - expect(supervisorStoppedReview).toContain("only after the results satisfy the goal"); - expect(steerWorkerDescription).toContain("A recap alone does not send an instruction"); - }); - - it("asks if active work is on track and keeps productive work uninterrupted", () => { - expect(supervisorPeriodicReview).toContain("Is the worker on track"); - expect(supervisorPeriodicReview).toContain("let productive work continue without interruption"); - expect(supervisorStartedReview).toContain("only if a correction is needed"); - expect(supervisorPlanChangeReview).toContain("claims, not proof of completion"); - expect(supervisorPlanChangeReview).toContain("Preserve authorized changes"); - }); - - it("defines approval as acceptance after outcome judgment, with mechanics separate", () => { - expect(approveGoalDescription).toMatch(/^Use only after judging that the actual result satisfies/); - expect(approveGoalDescription).toContain("mechanical checks cannot establish success"); - expect(approveGoalDescription).toContain("If the goal is unmet or evidence is insufficient, do not approve"); - expect(approveGoalDescription).toContain("\n\nRequirements:"); - expect(goalApprovalRecorded("repair")).toContain("Use SteerWorker to tell the worker to call CompleteGoal with this exact goal text"); - expect(completeGoalDescription).toContain("Worker-only sign-off"); - expect(completeGoalDescription).not.toContain("direct the worker to run it"); - }); -}); +import { planDrafting } from "../src/prompts.js"; describe("planning prompt", () => { it("requires fact finding or a focused question before a goal", () => { @@ -42,20 +13,9 @@ describe("planning prompt", () => { expect(planDrafting).toContain("Only withhold Ready for an unanswered choice that changes scope, spending, or the user-visible result"); }); - it("restores the same rule after compaction", () => { - expect(planningState(".pi/plan/test.md")).toContain("web search\nwhen either can resolve a fact."); - expect(planningState(".pi/plan/test.md")).toContain("choice that needs their approval"); - expect(planningState(".pi/plan/test.md")).toContain("Record unanswered questions as\nunknown and still present Ready"); - expect(planningState(".pi/plan/test.md")).toContain("self-contained round with relevant context and a recommendation"); - }); - it("anchors work and sign-off to the user-visible result", () => { expect(planDrafting).toContain("## User-visible result"); expect(planDrafting).toContain("Take it from the original request, not from your implementation plan"); expect(planDrafting).toContain("Future work may not defer any artifact or action named there"); - expect(resync("plan", ".pi/plan/test.md", "Compacted.")).toContain("amend the plan rather than preserving an obsolete decision"); - expect(resync("plan", ".pi/plan/test.md", "Compacted.")).toContain("implementation worker"); - expect(completeGoalDescription).toContain("visible supervisor"); - expect(completeGoalDescription).toContain("stopped worker view with no active work"); }); }); diff --git a/test/role-models.test.ts b/test/role-models.test.ts deleted file mode 100644 index 2c8f7b0..0000000 --- a/test/role-models.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { mkdtempSync, readFileSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { expect, it, vi } from "vitest"; -import { RoleModels } from "../src/role-models.js"; - -function runtime(cwd: string) { - const hooks = new Map<string, any>(); - const ctx = { cwd, model: { provider: "test", id: "planner" }, modelRegistry: { find: vi.fn((provider: string, id: string): any => ({ provider, id })) }, ui: { notify: vi.fn() } }; - const pi = { - on: (name: string, fn: any) => hooks.set(name, fn), - setModel: vi.fn(async (model: any) => { ctx.model = model; await hooks.get("model_select")({ source: "set", model }); return true; }), - }; - const models = new RoleModels(pi as unknown as ExtensionAPI); - return { models, ctx, pi, hooks, select: async (id: string, source = "set") => { ctx.model = { provider: "test", id }; await hooks.get("model_select")({ source, model: ctx.model }); } }; -} - -it("remembers each role without automatic switching overwriting another role", async () => { - const cwd = mkdtempSync(join(tmpdir(), "goals-role-models-")); - try { - const first = runtime(cwd); - await first.models.enter("planning", first.ctx as any); - await first.models.enter("worker", first.ctx as any); - await first.select("small-worker"); - await first.models.enter("supervisor", first.ctx as any); - await first.select("astra"); - await first.models.enter("planning", first.ctx as any); - expect(first.ctx.model.id).toBe("planner"); - const resumed = runtime(cwd); - await resumed.models.enter("worker", resumed.ctx as any); - expect(resumed.ctx.model.id).toBe("small-worker"); - await resumed.models.enter("supervisor", resumed.ctx as any); - expect(resumed.ctx.model.id).toBe("astra"); - await resumed.select("restored-default", "restore"); - expect(JSON.parse(readFileSync(join(cwd, ".pi/pi-goals/models/supervisor.json"), "utf8")).id).toBe("astra"); - console.log("Role preferences restored: planning=planner, worker=small-worker, supervisor=astra; restore events did not overwrite the choice."); - } finally { rmSync(cwd, { recursive: true, force: true }); } -}); - -it("fails on an unavailable remembered model without replacing the choice", async () => { - const cwd = mkdtempSync(join(tmpdir(), "goals-role-unavailable-")); - try { - const first = runtime(cwd); - await first.models.enter("supervisor", first.ctx as any); - await first.select("astra"); - const path = join(cwd, ".pi/pi-goals/models/supervisor.json"); - const saved = readFileSync(path, "utf8"); - const resumed = runtime(cwd); - resumed.ctx.modelRegistry.find.mockReturnValue(undefined); - await expect(resumed.models.enter("supervisor", resumed.ctx as any)).rejects.toThrow("unavailable"); - expect(resumed.pi.setModel).not.toHaveBeenCalled(); - expect(readFileSync(path, "utf8")).toBe(saved); - await resumed.models.enter("supervisor", resumed.ctx as any, true); - expect(JSON.parse(readFileSync(path, "utf8")).id).toBe("planner"); - resumed.models.leave(); - await resumed.select("unrelated-model"); - expect(JSON.parse(readFileSync(path, "utf8")).id).toBe("planner"); - } finally { rmSync(cwd, { recursive: true, force: true }); } -}); diff --git a/test/rpc-review.test.ts b/test/rpc-review.test.ts index 661b852..2c65a45 100644 --- a/test/rpc-review.test.ts +++ b/test/rpc-review.test.ts @@ -1,5 +1,6 @@ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process"; -import { mkdtempSync, rmSync } from "node:fs"; +import { once } from "node:events"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; @@ -7,14 +8,17 @@ import { StringDecoder } from "node:string_decoder"; import { describe, expect, it } from "vitest"; type RpcMessage = { type: string; id?: string; method?: string; [key: string]: unknown }; +type ModelRequest = { messages: Array<{ role: string; content: unknown }> }; class RpcClient { readonly messages: RpcMessage[] = []; + stderr = ""; private readonly waiters: Array<{ predicate: (message: RpcMessage) => boolean; resolve: (message: RpcMessage) => void }> = []; constructor(readonly process: ChildProcessWithoutNullStreams) { const decoder = new StringDecoder("utf8"); let buffer = ""; + process.stderr.on("data", (chunk) => { this.stderr += chunk; }); process.stdout.on("data", (chunk) => { buffer += decoder.write(chunk); while (buffer.includes("\n")) { @@ -37,7 +41,14 @@ class RpcClient { waitFor(predicate: (message: RpcMessage) => boolean, after = 0): Promise<RpcMessage> { const existing = this.messages.slice(after).find(predicate); if (existing) return Promise.resolve(existing); - return new Promise((resolvePromise) => this.waiters.push({ predicate, resolve: resolvePromise })); + return new Promise((resolvePromise, reject) => { + const timer = setTimeout(() => { + this.waiters.splice(this.waiters.indexOf(waiter), 1); + reject(new Error(`RPC wait timed out: ${this.stderr}\n${JSON.stringify(this.messages.slice(-12))}`)); + }, 8_000); + const waiter = { predicate, resolve: (message: RpcMessage) => { clearTimeout(timer); resolvePromise(message); } }; + this.waiters.push(waiter); + }); } } @@ -48,70 +59,108 @@ function streamResponse(response: import("node:http").ServerResponse, delta: obj response.end("data: [DONE]\n\n"); } +const isSelect = (message: RpcMessage) => message.type === "extension_ui_request" && message.method === "select"; +const isEditor = (message: RpcMessage) => message.type === "extension_ui_request" && message.method === "editor"; +const systemText = (request: ModelRequest) => request.messages.filter(message => ["system", "developer"].includes(message.role)).map(message => message.content).join("\n"); + describe("RPC review flow", () => { - it("opens Refine's editor before it starts the revision turn", async () => { + it.each(["Edit", "Discuss"])("automatically proposes a draft, handles %s, then enters the supervisor role on Ready", async (choice) => { const cwd = mkdtempSync(join(tmpdir(), "pi-goals-rpc-")); - let requestCount = 0; + const requests: ModelRequest[] = []; + const plan = "# Plan\n\n## Goals\n\n1. [ ] goal: name the output\n - subtle failure mode: the output has no name\n - discriminator: the plan names the output\n\n## Log\n"; let planPath = ""; - const server = createServer((_request, response) => { - requestCount++; - if (requestCount === 1) { + const server = createServer(async (request, response) => { + let body = ""; + for await (const chunk of request) body += chunk; + requests.push(JSON.parse(body)); + if (requests.length === 1) { streamResponse(response, { tool_calls: [{ - index: 0, - id: "write-plan", - type: "function", - function: { - name: "write", - arguments: JSON.stringify({ - path: planPath, - content: "# Plan\n\n## Goals\n\n1. [ ] goal: name the output\n - subtle failure mode: the output has no name\n - discriminator: the plan names the output\n\n## Log\n\n## Interview\n", - }), - }, + index: 0, id: "write-plan", type: "function", + function: { name: "write", arguments: JSON.stringify({ path: planPath, content: plan }) }, }], }, "tool_calls"); return; } - streamResponse(response, { content: "Plan drafted." }, "stop"); + streamResponse(response, { content: "Plan inspected." }, "stop"); }); - await new Promise<void>((resolvePromise) => server.listen(0, "127.0.0.1", resolvePromise)); + await new Promise<void>((done) => server.listen(0, "127.0.0.1", done)); const address = server.address(); if (!address || typeof address === "string") throw new Error("Offline model did not bind a TCP port."); const pi = spawn(resolve("node_modules/.bin/pi"), [ - "--mode", "rpc", "--no-session", "--model", "offline/test", + "--mode", "rpc", "--no-session", "--no-extensions", "--model", "offline/test", "-e", resolve("test/fixtures/offline-model.ts"), + "-e", resolve("test/fixtures/subagent-schema.ts"), "-e", resolve("src/index.ts"), ], { cwd, env: { - // This fixture tests the MAIN extension, even when vitest runs in a delegated session. + // Pi/gpt-6-astra: test the parent role even when vitest itself runs in a worker. ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("PI_SUBAGENT_") && !name.startsWith("PI_GOALS_"))), PI_CODING_AGENT_DIR: join(cwd, ".agent"), PI_GOALS_OFFLINE_MODEL_URL: `http://127.0.0.1:${address.port}`, }, }); const client = new RpcClient(pi); + const exited = once(pi, "exit"); try { client.send({ type: "get_state", id: "state" }); const state = await client.waitFor((message) => message.type === "response" && message.id === "state"); const sessionId = (state.data as { sessionId: string }).sessionId; - planPath = join(cwd, ".pi", "plan", `${sessionId}-v1.md`); + planPath = join(cwd, ".pi", "plan", `${sessionId}-main.md`); - client.send({ type: "prompt", id: "goals", message: "/goals work out the thing" }); - const review = await client.waitFor((message) => message.type === "extension_ui_request" && message.method === "select"); - client.send({ type: "extension_ui_response", id: review.id, value: "Refine" }); - const editor = await client.waitFor((message) => message.type === "extension_ui_request" && message.method === "editor"); - expect(requestCount).toBe(2); + client.send({ type: "prompt", id: "goals", message: "/goals new work out the thing" }); + const review = await client.waitFor(isSelect); + expect(review.options).toEqual(["Ready", "Discuss", "Edit", "Cancel"]); + expect(review.title).toContain(planPath); + const proposal = client.messages.find(message => message.type === "message_end" && (message.message as { customType?: string })?.customType === "goal-plan-proposal"); + expect(proposal?.message).toMatchObject({ content: plan, display: true }); + expect(readFileSync(planPath, "utf8")).toBe(plan); + expect(requests).toHaveLength(2); + expect(systemText(requests[0])).toContain("Plan only in"); - const revisionStart = client.messages.length; - client.send({ type: "extension_ui_response", id: editor.id, value: "Name the produced file." }); - await client.waitFor((message) => message.type === "agent_end", revisionStart); - expect(requestCount).toBe(3); + const choiceStart = client.messages.length; + client.send({ type: "extension_ui_response", id: review.id, value: choice }); + let approvedPlan = plan; + if (choice === "Edit") { + const editor = await client.waitFor(isEditor, choiceStart); + expect(editor.prefill).toBe(plan); + expect(requests).toHaveLength(2); + approvedPlan = plan.replace("the plan names the output", "the plan names output.txt and its exact bytes"); + const editStart = client.messages.length; + client.send({ type: "extension_ui_response", id: editor.id, value: approvedPlan }); + await client.waitFor(message => message.type === "extension_ui_request" && message.method === "setWidget", editStart); + expect(readFileSync(planPath, "utf8")).toBe(approvedPlan); + expect(requests).toHaveLength(2); + } else { + await client.waitFor(message => message.type === "agent_end", choiceStart); + expect(requests).toHaveLength(3); + expect(systemText(requests[2])).toContain("Plan only in"); + expect(JSON.stringify(requests[2].messages.at(-1))).toContain("Discuss the current draft"); + expect(client.messages.slice(choiceStart).filter(isEditor)).toEqual([]); + } + const beforeReady = requests.length; + const reopenStart = client.messages.length; + client.send({ type: "prompt", id: "review", message: "/goals review" }); + const ready = await client.waitFor(isSelect, reopenStart); + expect(requests).toHaveLength(beforeReady); + const readyStart = client.messages.length; + client.send({ type: "extension_ui_response", id: ready.id, value: "Ready" }); + await client.waitFor(message => message.type === "agent_end", readyStart); + expect(requests).toHaveLength(beforeReady + 1); + const supervisor = requests.at(-1)!; + expect(systemText(supervisor)).toContain("You are the goal supervisor in the main chat"); + expect(systemText(supervisor)).not.toContain("Plan only in"); + expect(JSON.stringify(supervisor.messages)).toContain(JSON.stringify(approvedPlan).slice(1, -1)); + expect(client.messages.filter(message => message.type === "tool_execution_start").map(message => message.toolName)).toEqual(["write"]); + expect(client.messages.filter(message => message.type === "extension_error")).toEqual([]); + console.log(`RPC ${choice}: visible automatic proposal; ${choice === "Edit" ? "editor saved exact plan without model call" : "discussion retained planning role without editor"}; Ready request used supervisor role; only write executed.`); } finally { pi.kill(); - server.close(); + await exited; + await new Promise<void>((done) => server.close(() => done())); rmSync(cwd, { recursive: true, force: true }); } - }, 15_000); + }, 25_000); }); diff --git a/test/session-usage.test.ts b/test/session-usage.test.ts new file mode 100644 index 0000000..bd88e54 --- /dev/null +++ b/test/session-usage.test.ts @@ -0,0 +1,34 @@ +import { expect, it } from "vitest"; +import { report, summarize } from "../scripts/session-usage.mjs"; + +const start = "2026-09-10T06:00:00.000Z"; +const end = "2026-09-10T07:00:00.000Z"; +const request = (timestamp: string, model = "a") => ({ + type: "message", timestamp, message: { role: "assistant", provider: "test", model, + usage: { input: 10, cacheRead: 100, cacheWrite: 5, output: 20, reasoning: 8, totalTokens: 135 } }, +}); + +it("excludes inherited history and counts repeated cached input without adding reasoning twice", () => { + const result = summarize([request("2026-09-09T06:00:00.000Z"), request(start), request(end, "b")], start, end); + expect(result).toMatchObject({ calls: 2, input: 20, cacheRead: 200, cacheWrite: 10, output: 40, totalTokens: 270 }); + expect(result.models.map((m: any) => m.model)).toEqual(["test/a", "test/b"]); +}); + +it("uses the latest planning start and the same interval for both sessions", () => { + const supervisor = { entries: [ + { type: "custom", customType: "pi-goals-main-supervisor-v1", timestamp: start, id: "boundary", data: { mode: "planning", plan: "plan.md" } }, + request(start), + ] }; + const result = report(supervisor, { entries: [request(end)] }, end); + expect(result.since).toBe(start); + expect(result.elapsedHours).toBe(1); + expect(result.sessions.map((s: any) => s.output)).toEqual([20, 20]); + expect(() => report({ entries: [] }, { entries: [] }, end)).toThrow("No recorded planning start"); +}); + +it("reports missing usage and rejects invalid recorded token counts", () => { + const missing = { type: "message", timestamp: start, message: { role: "assistant" } }; + expect(summarize([missing], start, end).missingUsage).toBe(1); + const invalid = request(start); invalid.message.usage.input = Number.NaN; + expect(() => summarize([invalid], start, end)).toThrow("Invalid usage.input"); +}); diff --git a/test/supervisor-role.test.ts b/test/supervisor-role.test.ts deleted file mode 100644 index b8a9bf8..0000000 --- a/test/supervisor-role.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, it } from "vitest"; -import { restoredSupervisor } from "../src/supervisor-session.js"; - -const binding = { workerSessionId: "worker", ownerSessionId: "worker", planPath: "/repo/plan.md", approvalId: "pairing" }; -it("restores complete supervisor identity even for a stopped fork without worker state", () => { - expect(restoredSupervisor([{ type: "custom", customType: "pi-goals-supervisor-binding", data: binding }])).toEqual(binding); -}); -it("migrates an older supervisor marker without inventing a pairing", () => { - expect(restoredSupervisor([ - { type: "custom", customType: "pi-goals-state", data: { approvalId: "pairing", phase: null } }, - { type: "custom", customType: "pi-goals-visible-supervisor-v2", data: { workerSessionId: "worker", planPath: "/repo/plan.md" } }, - ])).toEqual(binding); -}); -it("refuses incomplete supervisor identity instead of returning worker mode", () => { - expect(() => restoredSupervisor([{ type: "custom", customType: "pi-goals-supervisor-binding", data: { workerSessionId: "worker" } }])).toThrow("incomplete"); - expect(() => restoredSupervisor([{ type: "custom", customType: "pi-goals-visible-supervisor-v2", data: {} }])).toThrow("Worker mode was not enabled"); - for (const customType of ["pi-goals-supervisor-binding", "pi-goals-visible-supervisor-v2"]) expect(() => restoredSupervisor([{ type: "custom", customType, data: null }])).toThrow(/worker mode was not enabled/i); - expect(restoredSupervisor([{ type: "custom", customType: "pi-goals-state", data: { phase: "working" } }])).toBeUndefined(); -}); diff --git a/test/supervisor-session.test.ts b/test/supervisor-session.test.ts deleted file mode 100644 index a3b65e6..0000000 --- a/test/supervisor-session.test.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { stripVTControlCharacters } from "node:util"; -import { AssistantMessageComponent, type ExtensionAPI, initTheme, ToolExecutionComponent } from "@earendil-works/pi-coding-agent"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { approvalPath } from "../src/approval.js"; -import { approveGoalDescription, approveGoalParameters, goalApprovalRecorded, steerWorkerDescription, steerWorkerInstructionDescription, supervisorCompaction, supervisorOrientation, supervisorReviewContext } from "../src/prompts.js"; -import { registerVisibleSupervisor } from "../src/supervisor-session.js"; -import { intercomFixture } from "./intercom-fixture.js"; - -const shutdowns: Array<() => Promise<void>> = []; - -function setup(cwd: string, planPath: string, tokens: number | null = 10, onCompact: (options: any) => void = (options) => options.onComplete()) { - const transport = intercomFixture(); - vi.stubEnv("PI_GOALS_WORKER_ID", "worker-session"); - vi.stubEnv("PI_GOALS_OWNER_SESSION_ID", "worker-session"); - vi.stubEnv("PI_GOALS_PLAN_PATH", planPath); - vi.stubEnv("PI_GOALS_APPROVAL_ID", "approval-1"); - const hooks = new Map<string, any>(); - const tools = new Map<string, any>(); - const commands = new Map<string, any>(); - const entries: any[] = []; - const messages: string[] = []; - let branch: any[] = []; - let activeTools = ["read", "grep", "bash", "write", "edit", "intercom", "custom_inspection", "custom_action"]; - const ctx = { - cwd, - isIdle: () => true, - getSystemPrompt: () => "base", - model: { provider: "test", id: "supervisor" }, - modelRegistry: { find: (provider: string, id: string) => ({ provider, id }) }, - getContextUsage: () => tokens === null ? undefined : ({ tokens }), - compact: vi.fn(onCompact), - sessionManager: { getEntries: () => entries, getBranch: () => [...entries, ...branch], getSessionId: () => "supervisor-session" }, - ui: { notify: vi.fn() }, - }; - const pi = { - events: transport.events, - on: (name: string, handler: any) => { - const prior = hooks.get(name); - hooks.set(name, async (...args: any[]) => { await prior?.(...args); return handler(...args); }); - }, - registerTool: (tool: any) => tools.set(tool.name, tool), - registerCommand: (name: string, command: any) => commands.set(name, command), - appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }), - sendUserMessage: (text: string) => { messages.push(text); void hooks.get("message_start")?.({ message: { role: "user", content: [{ type: "text", text }] } }); }, - getActiveTools: () => activeTools, - setModel: vi.fn(async () => true), - setActiveTools: (next: string[]) => { activeTools = next; }, - }; - registerVisibleSupervisor(pi as unknown as ExtensionAPI); - shutdowns.push(() => hooks.get("session_shutdown")()); - return { - commands, pi, activeTools: () => activeTools, branch: (value: any[]) => { branch = value; }, ctx, entries, hooks, transport, messages, tools, - ready: () => transport.sent.some(message => message.kind === "hello" && message.role === "supervisor" && message.ready), - start: async () => { await hooks.get("session_start")({}, ctx); await new Promise(resolve => setImmediate(resolve)); }, - view: (id: string, text: string, reason = "settled", backgroundQuiet = true) => { - transport.receive({ binding: "approval-1", role: "worker", kind: "view", id, text, reason, backgroundQuiet }); - return { text }; - }, - }; -} - -afterEach(async () => { - for (const shutdown of shutdowns.splice(0)) await shutdown(); - vi.useRealTimers(); - vi.unstubAllEnvs(); -}); - -describe("visible supervisor session", () => { - it("restores monitoring without removing normal or custom tools or replaying persisted views", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-resume-")); - try { - const first = setup(cwd, join(cwd, "plan.md")); - await first.start(); - const view = first.view("first", "The worker stopped."); - expect(first.messages).toEqual([view.text]); - await first.hooks.get("session_shutdown")(); - const resumed = setup(cwd, join(cwd, "plan.md"), 30_000); - resumed.entries.push(...first.entries); - await resumed.start(); - expect(resumed.activeTools()).toEqual(first.activeTools()); - expect(resumed.activeTools()).toEqual(["read", "grep", "bash", "write", "edit", "intercom", "custom_inspection", "custom_action"]); - expect(resumed.ctx.compact).not.toHaveBeenCalled(); - resumed.view("first", view.text); - expect(resumed.messages).toEqual([]); - const latest = resumed.view("second", "The worker stopped.\nCurrent view."); - expect(resumed.messages).toEqual([latest.text]); - resumed.view("third", "The worker is still working.", "started"); - expect(resumed.messages).toEqual([latest.text]); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - - it("renders all advice in real Pi tool rows, including collapsed and restored rows", () => { - initTheme("dark"); - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-render-")); - try { - const runtime = setup(cwd, join(cwd, "plan.md")); - const tool = runtime.tools.get("SteerWorker"); - const lines = Array.from({ length: 18 }, (_, i) => `Advice ${i + 1}: inspect evidence.`); - const instruction = lines.join("\n"); - for (const restored of [false, true]) { - const row = new ToolExecutionComponent("SteerWorker", "call", restored ? { instruction } : {}, {}, tool, { requestRender() {} } as any, cwd); - expect(stripVTControlCharacters(row.render(40).join("\n"))).not.toContain("undefined"); - row.updateArgs({ instruction }); - row.setArgsComplete(); - row.updateResult({ content: [{ type: "text", text: "Receipt unconfirmed." }], isError: false }); - for (const expanded of [false, true]) { - row.setExpanded(expanded); - for (const width of [40, 100]) { - const output = stripVTControlCharacters(row.render(width).join("\n")); - for (const line of lines) expect(output).toContain(line); - expect(output).toContain("Receipt unconfirmed."); - } - } - } - const assistant = new AssistantMessageComponent(undefined, false); - for (const streaming of [true, false]) { - assistant.updateContent({ role: "assistant", content: [ - { type: "thinking", thinking: "The signs disagree. Inspect the outputs." }, - { type: "text", text: "Progress is mixed; the second check still fails." }, - { type: "toolCall", id: "call", name: "SteerWorker", arguments: { instruction } }, - ] } as any, streaming); - const output = stripVTControlCharacters(assistant.render(100).join("\n")); - expect(output).toContain("The signs disagree."); - expect(output).toContain("Progress is mixed;"); - } - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - - it("asks for judgment and useful recaps without inventing instructions", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-prompt-")); - try { - writeFileSync(join(cwd, "plan.md"), "# Outcome\nBeat random\n1. [ ] goal: repair\n - discriminator: beats random\n## Log\nold history"); - const runtime = setup(cwd, join(cwd, "plan.md")); - const first = await runtime.hooks.get("before_agent_start")({}, runtime.ctx); - const systemPrompt = `${first.systemPrompt}\n${first.message.content}`; - expect(systemPrompt).toContain("brief visible recap"); - expect(systemPrompt).toContain("discriminator: beats random"); - expect(systemPrompt).not.toContain("old history"); - expect(first.message.customType).toBe("pi-goals-supervisor-role"); - const activePlan = "# Outcome\nBeat random\n1. [ ] goal: repair\n - discriminator: beats random"; - expect(first.systemPrompt).toBe(`base\n\n${supervisorReviewContext(join(cwd, "plan.md"), activePlan)}`); - expect(first.message.content).toBe(supervisorOrientation(join(cwd, "plan.md"), activePlan)); - const review = async () => runtime.hooks.get("before_agent_start")({}, runtime.ctx); - const next = await review(); - expect(next.message).toBeUndefined(); - expect(next.systemPrompt).toContain("autonomously extending the user's agency"); - expect(next.systemPrompt).toContain("Inspect and diagnose directly. Delegate changes to the worker through SteerWorker; do not take over implementation or alter shared state."); - expect(first.message.content).toContain("instruction, not an enforced sandbox"); - await runtime.hooks.get("session_compact")({}, runtime.ctx); - expect((await review()).message.content).toContain("Protect the user's epistemic autonomy"); - writeFileSync(join(cwd, "plan.md"), "# Outcome\nBeat random\n1. [x] goal: repair\n - discriminator: beats random\n"); - expect((await review()).systemPrompt).toContain("[x] goal: repair"); - expect(systemPrompt).toContain("your judgment"); - expect(systemPrompt).toContain("justified confidence, not certainty at any cost"); - expect(systemPrompt).toContain('Treat "blocked", "waiting", "impossible", and "already done" as claims to investigate'); - expect(systemPrompt).toContain("whether the claimed dependency is real"); - expect(systemPrompt).not.toMatch(/Modal|pueue|worktree/); - expect(systemPrompt).toContain("what event will resume progress and how it will be observed"); - expect(systemPrompt).toContain("after checking what is already authorized"); - expect(systemPrompt).toContain("Check the actual deliverable against the user's goal"); - expect(systemPrompt).toContain("verbatim evidence with a source path or link"); - expect(systemPrompt).toContain("not independent evidence"); - expect(systemPrompt).toContain("checks that distinguish plausible explanations"); - expect(systemPrompt).toContain("Never repeat a steer that had no effect"); - expect(systemPrompt).toContain("Do not edit files or execute the worker's work"); - expect(systemPrompt).toContain("Keep independent work moving"); - expect(systemPrompt).toContain("do not invent work"); - expect(systemPrompt).toContain("give a short assessment and stop"); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - it("writes readiness without removing normal or custom extension tools", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-")); - try { - const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md")); - await runtime.hooks.get("session_start")({}, runtime.ctx); - await new Promise((resolve) => setImmediate(resolve)); - expect(runtime.ctx.compact).not.toHaveBeenCalled(); - expect(runtime.ready()).toBe(true); - expect(runtime.activeTools()).toEqual(["read", "grep", "bash", "write", "edit", "intercom", "custom_inspection", "custom_action"]); - expect(runtime.entries.at(-1)).toMatchObject({ customType: "pi-goals-visible-supervisor-v2" }); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - - it("waits for an inherited 60-second compaction without starting a competing compaction", async () => { - vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-reload-")); - try { - const runtime = setup(cwd, join(cwd, "plan.md"), null); - runtime.ctx.isIdle = () => false; - await runtime.start(); - await vi.advanceTimersByTimeAsync(60_000); - expect(runtime.ctx.compact).not.toHaveBeenCalled(); - expect(runtime.ready()).toBe(false); - runtime.entries.push({ type: "compaction", summary: "inherited compaction finished" }); - runtime.ctx.isIdle = () => true; - await vi.advanceTimersByTimeAsync(1000); - expect(runtime.ctx.compact).not.toHaveBeenCalled(); - expect(runtime.ready()).toBe(true); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - - it("compacts a large planning fork before writing readiness", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-")); - try { - let complete: (() => void) | undefined; - const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"), 100_001, (options) => { complete = options.onComplete; }); - await runtime.hooks.get("session_start")({}, runtime.ctx); - await new Promise((resolve) => setImmediate(resolve)); - expect(runtime.ctx.compact).toHaveBeenCalledOnce(); - expect(runtime.ctx.compact.mock.calls[0][0].customInstructions).toBe(supervisorCompaction(join(cwd, ".pi/plan/worker-v1.md"), true)); - expect(runtime.ready()).toBe(false); - complete!(); - expect(runtime.ready()).toBe(true); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - - it("does not become ready when initial compaction fails", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-")); - try { - const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"), null, (options) => options.onError(new Error("offline"))); - await runtime.hooks.get("session_start")({}, runtime.ctx); - await new Promise((resolve) => setImmediate(resolve)); - expect(runtime.ready()).toBe(false); - expect(runtime.ctx.ui.notify).toHaveBeenCalledWith("Supervisor startup compaction failed: offline", "error"); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - - it("writes a durable worker instruction", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-")); - try { - const runtime = setup(cwd, join(cwd, "plan.md")); - await runtime.start(); - expect(runtime.tools.get("SteerWorker").description).toBe(steerWorkerDescription); - expect(runtime.tools.get("SteerWorker").parameters.properties.instruction.description).toBe(steerWorkerInstructionDescription); - expect(runtime.tools.get("ApproveGoal").description).toBe(approveGoalDescription); - expect(runtime.tools.get("ApproveGoal").parameters.properties.goal.description).toBe(approveGoalParameters.goal); - const steered = await runtime.tools.get("SteerWorker").execute("id", { instruction: "Run the saved verification." }); - expect(steered.isError).toBe(false); - expect(runtime.transport.sent.filter(message => message.kind === "steer")).toMatchObject([{ text: "Run the saved verification." }]); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); - - it("records approval only from a stopped view with evidence and no active work", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-")); - try { - writeFileSync(join(cwd, ".gitignore"), ".pi/\n"); - writeFileSync(join(cwd, "verify.txt"), "PASS\n"); - execFileSync("git", ["init", "-q"], { cwd }); - execFileSync("git", ["add", ".gitignore", "verify.txt"], { cwd }); - execFileSync("git", ["-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-qm", "initial"], { cwd }); - const planPath = join(cwd, ".pi/plan/worker-v1.md"); - execFileSync("mkdir", ["-p", join(cwd, ".pi/plan")]); - writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [ ] goal: make the file\n - discriminator: output exists\n - evidence:\n - `result.txt`: contains ok\n\n## Log\n"); - const runtime = setup(cwd, planPath); - await runtime.start(); - const view = runtime.view("first", "The worker stopped.\n\ntool calls with no result: none"); - runtime.branch([{ type: "message", message: { role: "user", content: [{ type: "text", text: view.text }] } }]); - const originalPlan = readFileSync(planPath, "utf8"); - for (const evidence of [" - evidence: (empty until sign-off)", " - evidence:\n - (empty until sign-off)"]) { - writeFileSync(planPath, originalPlan.replace(" - evidence:\n - `result.txt`: contains ok", evidence)); - const rejected = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx); - expect(rejected.isError).toBe(true); - expect(rejected.content[0].text).toContain("nonblank evidence"); - expect(existsSync(approvalPath(cwd, "worker-session", "make the file"))).toBe(false); - } - writeFileSync(planPath, originalPlan); - const approved = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx); - expect(approved.isError).toBe(false); - expect(approved.content[0].text).toBe(goalApprovalRecorded("make the file")); - expect(existsSync(approvalPath(cwd, "worker-session", "make the file"))).toBe(true); - runtime.view("second", "The worker is still working.", "started"); - const stale = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx); - expect(stale.isError).toBe(true); - expect(stale.content[0].text).toContain("worker is starting or running"); - const unknown = runtime.view("third", "The worker stopped.\ntracked background work: unknown", "settled", false); - runtime.branch([{ type: "message", message: { role: "user", content: [{ type: "text", text: unknown.text }] } }]); - const blocked = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx); - expect(blocked.isError).toBe(true); - expect(blocked.content[0].text).toContain("background work is active or unknown"); - } finally { rmSync(cwd, { recursive: true, force: true }); } - }); -}); - -it("does not enforce a supervisor tool-call denylist or reset extension tool selections", async () => { - const cwd = mkdtempSync(join(tmpdir(), "goals-supervisor-actuators-")); - try { - const runtime = setup(cwd, join(cwd, "plan.md")); - await runtime.start(); - expect(runtime.activeTools()).toContain("intercom"); - const selection = ["intercom", "SteerWorker", "bash", "write", "edit", "custom_action"]; - runtime.pi.setActiveTools(selection); - await runtime.commands.get("goals").handler("supervise", runtime.ctx); - expect(runtime.activeTools()).toEqual(selection); - const restorations = runtime.pi.setModel.mock.calls.length; - await runtime.commands.get("goals").handler("work", runtime.ctx); - expect(runtime.pi.setModel).toHaveBeenCalledTimes(restorations); - expect(runtime.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("supervisor session"), "info"); - expect(runtime.hooks.has("tool_call")).toBe(false); - } finally { rmSync(cwd, { recursive: true, force: true }); } -}); - -it("keeps a supervisor unready after model restoration failure, then recovers explicitly without substituting a model", async () => { - const cwd = mkdtempSync(join(tmpdir(), "goals-supervisor-model-recovery-")); - try { - const runtime = setup(cwd, join(cwd, "plan.md")); - mkdirSync(join(cwd, ".pi/pi-goals/models"), { recursive: true }); - writeFileSync(join(cwd, ".pi/pi-goals/models/supervisor.json"), JSON.stringify({ provider: "gone", id: "expired" })); - runtime.ctx.modelRegistry.find = vi.fn().mockReturnValue(undefined); - await runtime.start(); - expect(runtime.ready()).toBe(false); - expect(runtime.pi.setModel).not.toHaveBeenCalled(); - expect((await runtime.tools.get("SteerWorker").execute("id", { instruction: "Do not deliver." })).isError).toBe(true); - runtime.ctx.modelRegistry.find = (provider, id) => ({ provider, id }); - await runtime.hooks.get("model_select")({ source: "set", model: { provider: "test", id: "chosen" } }, runtime.ctx); - await runtime.commands.get("goals").handler("reconnect", runtime.ctx); - await new Promise(resolve => setImmediate(resolve)); - expect(runtime.pi.setModel).toHaveBeenLastCalledWith({ provider: "test", id: "chosen" }); - expect(runtime.ready()).toBe(true); - } finally { rmSync(cwd, { recursive: true, force: true }); } -}); - -it("warns once on unavailable usage but stays quiet for Pi's post-compaction null token sample", async () => { - const cwd = mkdtempSync(join(tmpdir(), "pi-goals-usage-")); - try { - const runtime = setup(cwd, join(cwd, "plan.md")); - await runtime.start(); - runtime.ctx.ui.notify.mockClear(); - runtime.ctx.getContextUsage = () => ({ tokens: null }) as any; - await runtime.hooks.get("agent_settled")({}, runtime.ctx); - expect(runtime.ctx.ui.notify).not.toHaveBeenCalled(); - runtime.ctx.getContextUsage = () => undefined; - await runtime.hooks.get("agent_settled")({}, runtime.ctx); - await runtime.hooks.get("agent_settled")({}, runtime.ctx); - expect(runtime.ctx.ui.notify).toHaveBeenCalledExactlyOnceWith(expect.stringContaining("custom 100k compaction trigger cannot be checked"), "warning"); - expect(runtime.ctx.compact).not.toHaveBeenCalled(); - } finally { rmSync(cwd, { recursive: true, force: true }); } -}); diff --git a/test/tick-goal.test.ts b/test/tick-goal.test.ts deleted file mode 100644 index 66edc7c..0000000 --- a/test/tick-goal.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { tickGoal } from "../src/index.js"; - -const plan = `# Plan - -## Goals - -1. [/] goal: Implement the cache layer - - tasks: - 1. [x] wire client -2. [ ] goal: Ship the docs - -## Log -`; - -describe("tickGoal (sign-off ticks the goal; agent only ticks on wording drift)", () => { - it("ticks the exact-matching goal line, case-insensitive, leaving subtasks alone", () => { - const out = tickGoal(plan, "implement the CACHE layer"); - expect(out).toContain("1. [x] goal: Implement the cache layer"); - expect(out).toContain("1. [x] wire client"); // subtask untouched (was already x) - expect(out).toContain("2. [ ] goal: Ship the docs"); // other goal untouched - }); - - it("returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's)", () => { - expect(tickGoal(plan, "Implement caching")).toBeNull(); - }); - - it("returns null when the subject matches more than one goal line", () => { - const dup = plan.replace("## Log", "3. [ ] goal: Ship the docs\n\n## Log"); - expect(tickGoal(dup, "Ship the docs")).toBeNull(); - }); - - it("ignores a historical duplicate below the Log and leaves it unchanged", () => { - const historical = `${plan}3. [ ] goal: Ship the docs\n`; - const result = tickGoal(historical, "Ship the docs"); - expect(result).toContain("2. [x] goal: Ship the docs"); - expect(result).toContain("## Log\n3. [ ] goal: Ship the docs\n"); - }); -}); diff --git a/test/worker-view.test.ts b/test/worker-view.test.ts deleted file mode 100644 index 9e88728..0000000 --- a/test/worker-view.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { expect, it } from "vitest"; -import { type SupervisorReviewReason, supervisorPeriodicReview, supervisorPlanChangeReview, supervisorReadyReview, supervisorStartedReview, supervisorStoppedReview } from "../src/prompts.js"; -import { workerView } from "../src/worker-view.js"; - -const context = { sourceSession: "/sessions/worker.jsonl", latestDirection: "Modal uses a remote GPU.", model: "provider/worker", background: "processes: 0; subagents: 0" }; -const entry = (id: string, text: string) => ({ id, type: "message", message: { role: "assistant", content: text } }); - -it.each<[SupervisorReviewReason, boolean, string, string]>([ - ["ready", true, "The worker is ready to begin.", supervisorReadyReview], - ["started", false, "The worker is still working.", supervisorStartedReview], - ["turns", false, "The worker is still working.", supervisorPeriodicReview], - ["interval", false, "The worker is still working.", supervisorPeriodicReview], - ["settled", true, "The worker stopped.", supervisorStoppedReview], - ["interval", true, "The worker stopped.", supervisorStoppedReview], - ["settled", false, "The worker is still working.", supervisorPeriodicReview], - ["plan", true, "The worker stopped.", supervisorStoppedReview], - ["plan", false, "The worker is still working.", supervisorPeriodicReview], -])("wires %s (idle=%s) to its review task without changing status prefixes", (reason, idle, prefix, task) => { - const view = workerView([entry("claim", "The plot is complete but the result does not beat random.")], reason, idle, context); - expect(view.startsWith(`${prefix}\n\n`)).toBe(true); - expect(view).toContain(task); - expect(view).toContain(`review trigger: ${reason}`); - if (reason === "plan") expect(view).toContain(supervisorPlanChangeReview); - if (!idle) expect(view).not.toContain(supervisorStoppedReview); -}); - -it("keeps human direction and source location while sending only new messages", () => { - const view = workerView([entry("old", "old detail"), entry("new", "new result")], "interval", true, { ...context, since: "old" }); - expect(view).toContain(context.latestDirection); - expect(view).toContain(context.sourceSession); - expect(view).toContain("new result"); - expect(view).not.toContain("old detail"); -}); - -it("restarts after compaction and does not report historical tool calls as active", () => { - const entries = [ - { id: "old", type: "message", message: { role: "assistant", content: [{ type: "toolCall", id: "call", name: "edit" }] } }, - { id: "compact", type: "compaction", summary: "Saved worker account." }, - entry("new", "new result"), - ]; - const initial = workerView(entries, "interval", true, { ...context, since: "old" }); - expect(initial).toContain("Saved worker account."); - expect(initial).toContain("tool calls with no result: none"); - const next = workerView(entries, "interval", true, { ...context, since: "new" }); - expect(next).not.toContain("Saved worker account."); - expect(next).toContain("No new messages."); -}); - -it("bounds serialized Unicode and quoted logs while marking omissions", () => { - const view = workerView([ - { id: "compact", type: "compaction", summary: '"\\🧪'.repeat(20_000) }, - entry("new", '"\\🧪'.repeat(20_000)), - ], "interval", true, { ...context, latestDirection: "Remote only. ".repeat(3000), planReview: '"\\🧪'.repeat(20_000) }); - expect(Buffer.byteLength(JSON.stringify({ binding: "binding", role: "worker", kind: "view", id: "id", text: view }))).toBeLessThan(16_000); - expect(view).toContain("[truncated; inspect source session]"); - expect(view).toContain(context.sourceSession); -}); - -it("keeps two recent thinking tails beside their actions without mutating the branch", () => { - const entries = ["old", "middle", "new"].map(id => ({ id, type: "message", message: { role: "assistant", content: [ - { type: "thinking", thinking: `${id} discarded head ${"padding ".repeat(100)}${id} decisive tail` }, - { type: "toolCall", id, name: "bash", arguments: { command: `verify-${id}` } }, - ] } })); - const before = structuredClone(entries); - const view = workerView(entries, "turns", false, context); - expect(view).not.toContain("old decisive tail"); - expect(view).not.toContain("discarded head"); - expect(view).toContain("middle decisive tail"); - expect(view).toContain("new decisive tail"); - expect(view.indexOf("middle decisive tail")).toBeLessThan(view.indexOf("verify-middle")); - expect(view.indexOf("verify-middle")).toBeLessThan(view.indexOf("new decisive tail")); - expect(view.indexOf("new decisive tail")).toBeLessThan(view.indexOf("verify-new")); - expect(entries).toEqual(before); -}); - -it("extracts files and blockers, retaining tool arguments instead of verbose result bodies", () => { - const entries = [ - entry("claim", "Cannot finish because the fixture is broken."), - { id: "write", type: "message", message: { role: "assistant", content: [{ type: "toolCall", id: "call", name: "write", arguments: { path: "result.txt", content: "artifact" } }] } }, - { id: "result", type: "message", message: { role: "toolResult", toolName: "write", toolCallId: "call", content: "verbose-result-body".repeat(1000) } }, - ]; - const view = workerView(entries, "settled", true, { ...context, contextPercent: 42, planReview: "goal: [/] -> [x], manual claim" }); - expect(view).toContain("[Files And Changes]"); - expect(view).toContain("Modified: result.txt"); - expect(view).toContain("[Outstanding Context]"); - expect(view).toContain("fixture is broken"); - expect(view).toContain('write "result.txt"'); - expect(view).toContain("tool calls with no result: none"); - expect(view).toContain(context.background); - expect(view).toContain("context used: 42%"); - expect(view).toContain("goal: [/] -> [x], manual claim"); - expect(view).not.toContain("verbose-result-body"); - expect(view).toContain("tool-result bodies omitted; inspect source for evidence"); - expect(view).not.toContain("vcc_recall"); -}); - -it("preserves unanswered partial calls across the acknowledged boundary", () => { - const entries = [{ id: "call", type: "message", message: { role: "assistant", content: [{ type: "toolCall", id: "pending", name: "edit" }] } }]; - expect(workerView(entries, "turns", false, context)).toContain("tool calls with no result: edit"); - const view = workerView(entries, "turns", false, { ...context, since: "call" }); - expect(view).toContain("tool calls with no result: edit"); - expect(view).toContain("No new messages."); -}); - -it("restarts a rewound branch and keeps fresh headerless text after compaction", () => { - const view = workerView([ - { id: "compaction", type: "compaction", summary: "Prior worker account." }, entry("fresh", "Fresh decisive result."), - ], "settled", true, { ...context, since: "entry-on-discarded-branch", contextPercent: null }); - expect(view).toContain("initial or reset view"); - expect(view).toContain("Prior worker account."); - expect(view).toContain("Fresh decisive result."); - expect(view).not.toContain("context used:"); -}); - -it("protects VCC headers and newest actions when the compacted brief exceeds its budget", () => { - const entries = [ - { id: "write", type: "message", message: { role: "assistant", content: [{ type: "toolCall", id: "call", name: "write", arguments: { path: "important.txt", content: "artifact" } }] } }, - ...Array.from({ length: 150 }, (_, i) => entry(`entry-${i}`, `Action ${i}: ${"details ".repeat(80)}`)), - entry("last", "Newest decisive observation."), - ]; - const view = workerView(entries, "turns", false, context); - expect(view).toContain("[Files And Changes]"); - expect(view).toContain("important.txt"); - expect(view).toContain("Newest decisive observation."); - expect(view).toContain("[truncated; inspect source session]"); - expect(Buffer.byteLength(JSON.stringify({ text: view }))).toBeLessThan(16_000); -}); - -it("distinguishes omitted result-only updates from no messages and extracts paired commit evidence", () => { - const result = (content: string) => [{ id: "result", type: "message", message: { role: "toolResult", toolName: "bash", toolCallId: "done", content } }]; - const omitted = workerView(result("large diagnostic output"), "settled", true, context); - expect(omitted).toContain("No overview text retained from these messages."); - expect(omitted).not.toContain("No new messages."); - const commit = workerView([ - { id: "commit", type: "message", message: { role: "assistant", content: [{ type: "toolCall", id: "done", name: "bash", arguments: { command: 'git commit -m "Save verified artifact"' } }] } }, - ...result("[main abc1234] Save verified artifact"), - ], "settled", true, context); - expect(commit).toContain("[Commits]"); - expect(commit).toContain("abc1234"); - expect(commit).not.toContain("vcc_recall"); -}); diff --git a/tsconfig.json b/tsconfig.json index b6e00ad..3dabd2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,17 +3,15 @@ "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", - "paths": { - "pi-intercom": ["./src/intercom-package.d.ts"], - "@sting8k/pi-vcc/src/core/summarize": ["./src/vcc-package.d.ts"] - }, "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "jsx": "react-jsx", "outDir": "dist", "rootDir": "src", "declaration": true }, - "include": ["src/**/*.ts", "src/**/*.tsx"] + "include": [ + "src/**/*.ts", + "src/**/*.tsx" + ] }