From f49be72a307bbc4d22fd3468d090255e72389bc9 Mon Sep 17 00:00:00 2001 From: wassname <1103714+wassname@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:54:48 +0800 Subject: [PATCH] Fix completed recovery and steer payload validation --- .../review-round-3-disposition.md | 10 ++++++ .../review-round-3-validation.txt | 23 +++++++++++++ .../round-3-native/supervisor-events.jsonl | 19 +++++++++++ .../round-3-native/supervisor-stderr.txt | 0 .../round-3-native/worker-events.jsonl | 32 +++++++++++++++++++ .../round-3-native/worker-stderr.txt | 0 src/index.ts | 10 +++++- src/intercom.ts | 11 +++++-- test/goals-flow.test.ts | 22 +++++++++++++ test/intercom.test.ts | 16 ++++++++++ 10 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 slop/reviews/solo-recovery/review-round-3-disposition.md create mode 100644 slop/reviews/solo-recovery/review-round-3-validation.txt create mode 100644 slop/reviews/solo-recovery/round-3-native/supervisor-events.jsonl create mode 100644 slop/reviews/solo-recovery/round-3-native/supervisor-stderr.txt create mode 100644 slop/reviews/solo-recovery/round-3-native/worker-events.jsonl create mode 100644 slop/reviews/solo-recovery/round-3-native/worker-stderr.txt diff --git a/slop/reviews/solo-recovery/review-round-3-disposition.md b/slop/reviews/solo-recovery/review-round-3-disposition.md new file mode 100644 index 0000000..740700c --- /dev/null +++ b/slop/reviews/solo-recovery/review-round-3-disposition.md @@ -0,0 +1,10 @@ +# Round-3 review disposition + +Disposition by the implementation worker against the independent review report at `/home/code/.pi/agent/sessions/--home-code-.pi-agent-git-github.com-wassname-pi-goals--/subagent-artifacts/outputs/52a907b5-4861-4ea5-88fb-90c4a32df3f0/review-round-3.md`, reviewing `9350d9f`. + +Both findings are accepted. + +1. **F1 — completed manual recovery:** agreed. The manual `/goals reconnect` and `/goals restart` failure catch bypassed the existing `planIsComplete` protection used by automatic recovery. It could demote a completed, signed-off pairing to solo and send the solo continuation follow-up. The catch now keeps a complete plan supervised, preserves recorded sign-offs, and reports the failed recovery without a solo transition. Regression covers both reconnect and restart timeouts. +2. **F2 — oversize steer retention:** agreed. `steer()` recorded a disconnected instruction before `publish()` applied the 16,000-byte wire limit; connected steers also recorded pending state before a publish failure. Steer payload validation now precedes all persistence and pending-state changes, using the same shared 16,000-byte limit as `publish()`. Regression checks both connected and disconnected calls leave entries and sent messages unchanged. + +The changes preserve solo fallback for unfinished valid working plans. Full validation and its isolated evidence environment are recorded in `review-round-3-validation.txt` and `round-3-native/`. diff --git a/slop/reviews/solo-recovery/review-round-3-validation.txt b/slop/reviews/solo-recovery/review-round-3-validation.txt new file mode 100644 index 0000000..abc53ea --- /dev/null +++ b/slop/reviews/solo-recovery/review-round-3-validation.txt @@ -0,0 +1,23 @@ +Round-3 review fixes validation + +Reviewed base: 9350d9f + +Environment isolation: every command unsets `PI_SUBAGENT_CHILD` and `PI_GOALS_ROLE`; native RPC evidence is redirected to the fresh, committed `slop/reviews/solo-recovery/round-3-native/` directory. No inherited review-fixes-native evidence directory was used. + +$ npx vitest run test/intercom.test.ts test/goals-flow.test.ts +2 files passed; 71 tests passed. The F1 matrix covers both manual reconnect and restart timeouts; F2 covers connected and disconnected oversize steers. + +$ env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE PI_GOALS_EVIDENCE_DIR="$PWD/slop/reviews/solo-recovery/round-3-native" npm test +22 files passed; 164 tests passed in 9.66s. The `fatal: not a git repository` stderr is the expected negative preflight regression. + +$ env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE PI_GOALS_EVIDENCE_DIR="$PWD/slop/reviews/solo-recovery/round-3-native" npm run typecheck +Passed (`tsc --noEmit`). + +$ env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE PI_GOALS_EVIDENCE_DIR="$PWD/slop/reviews/solo-recovery/round-3-native" npm run lint +Passed (Biome checked 42 files; no fixes applied). + +$ env -u PI_SUBAGENT_CHILD -u PI_GOALS_ROLE PI_GOALS_EVIDENCE_DIR="$PWD/slop/reviews/solo-recovery/round-3-native" npm run build +Passed (`tsc`). + +$ git diff --check +Passed. diff --git a/slop/reviews/solo-recovery/round-3-native/supervisor-events.jsonl b/slop/reviews/solo-recovery/round-3-native/supervisor-events.jsonl new file mode 100644 index 0000000..a410778 --- /dev/null +++ b/slop/reviews/solo-recovery/round-3-native/supervisor-events.jsonl @@ -0,0 +1,19 @@ +{"type":"extension_ui_request","id":"6d65e875-6ca0-4319-8ec0-03edb1cf52a1","method":"setStatus","statusKey":"pi-goals","statusText":"supervisor · paused"} +{"type":"extension_ui_request","id":"b2fe231f-0369-4f2f-9f25-ea68a8b3a14d","method":"notify","message":"supervisor model: offline/test","notifyType":"info"} +{"type":"extension_ui_request","id":"bd3e6702-3d2d-4701-baf7-1f671d7a963b","method":"setStatus","statusKey":"pi-goals","statusText":"supervisor · starting/reconnecting"} +{"type":"extension_ui_request","id":"c974abd7-2a06-4db4-b4da-bcda8412e78d","method":"setStatus","statusKey":"pi-goals","statusText":"supervisor · starting/reconnecting"} +{"id":"resumed-review","type":"response","command":"prompt","success":true} +{"type":"agent_start"} +{"type":"turn_start"} +{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"Confirm resumed identity without sending another instruction."}],"timestamp":1788965639214}} +{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"Confirm resumed identity without sending another instruction."}],"timestamp":1788965639214}} +{"type":"message_start","message":{"role":"custom","customType":"pi-goals-supervisor-role","content":"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 /tmp/goals-native-pair-LFF9w2/plan.md, 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.\n\nAt 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.\n\nUnderstand 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.\n\nProtect 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.\n\nYou are the visible pi-goals supervisor for /tmp/goals-native-pair-LFF9w2/plan.md. 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 /tmp/goals-native-pair-LFF9w2/plan.md is the source of truth; read it directly after every compaction.\n\nSupervise 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.\n\nThe 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.\n\nKeep 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.\n\nAt 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.\n\nGround 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.\n\nCheck 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.\n\nOnly 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\n\nFull active plan:\n1. [ ] goal: inspect actual outputs\n - discriminator: raw output inspected","display":true,"timestamp":1788965639214}} +{"type":"message_end","message":{"role":"custom","customType":"pi-goals-supervisor-role","content":"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 /tmp/goals-native-pair-LFF9w2/plan.md, 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.\n\nAt 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.\n\nUnderstand 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.\n\nProtect 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.\n\nYou are the visible pi-goals supervisor for /tmp/goals-native-pair-LFF9w2/plan.md. 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 /tmp/goals-native-pair-LFF9w2/plan.md is the source of truth; read it directly after every compaction.\n\nSupervise 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.\n\nThe 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.\n\nKeep 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.\n\nAt 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.\n\nGround 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.\n\nCheck 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.\n\nOnly 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\n\nFull active plan:\n1. [ ] goal: inspect actual outputs\n - discriminator: raw output inspected","display":true,"timestamp":1788965639214}} +{"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":1788965639224}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"assistantMessageEvent":{"type":"text_start","contentIndex":0}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Test context retained. Actual outputs still need inspection."}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"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":1788965639224,"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":1788965639224,"rawStopReason":"stop"},"toolResults":[]} +{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"Confirm resumed identity without sending another instruction."}],"timestamp":1788965639214},{"role":"custom","customType":"pi-goals-supervisor-role","content":"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 /tmp/goals-native-pair-LFF9w2/plan.md, 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.\n\nAt 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.\n\nUnderstand 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.\n\nProtect 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.\n\nYou are the visible pi-goals supervisor for /tmp/goals-native-pair-LFF9w2/plan.md. 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 /tmp/goals-native-pair-LFF9w2/plan.md is the source of truth; read it directly after every compaction.\n\nSupervise 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.\n\nThe 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.\n\nKeep 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.\n\nAt 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.\n\nGround 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.\n\nCheck 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.\n\nOnly 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\n\nFull active plan:\n1. [ ] goal: inspect actual outputs\n - discriminator: raw output inspected","display":true,"timestamp":1788965639214},{"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":1788965639224,"rawStopReason":"stop"}],"willRetry":false} +{"type":"agent_settled"} \ No newline at end of file diff --git a/slop/reviews/solo-recovery/round-3-native/supervisor-stderr.txt b/slop/reviews/solo-recovery/round-3-native/supervisor-stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/slop/reviews/solo-recovery/round-3-native/worker-events.jsonl b/slop/reviews/solo-recovery/round-3-native/worker-events.jsonl new file mode 100644 index 0000000..26dab6b --- /dev/null +++ b/slop/reviews/solo-recovery/round-3-native/worker-events.jsonl @@ -0,0 +1,32 @@ +{"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":1788965637965}} +{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788965637965}} +{"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":1788965637979}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"assistantMessageEvent":{"type":"text_start","contentIndex":0}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Test context retained. Actual outputs still need inspection."}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"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":1788965637979,"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":1788965637979,"rawStopReason":"stop"},"toolResults":[]} +{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"Retain this planning context for the supervisor fork."}],"timestamp":1788965637965},{"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":1788965637979,"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:33921"},"thinkingLevel":"off","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/tmp/goals-native-pair-LFF9w2/agent/sessions/--tmp-goals-native-pair-LFF9w2--/2026-09-09T14-53-57-375Z_01a086a9-4cfe-76b0-998e-3a106c78b226.jsonl","sessionId":"01a086a9-4cfe-76b0-998e-3a106c78b226","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":"55e3995b-2ae7-49ab-b38f-4406fd48a4be","text":"The worker stopped.\n\nInspect 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.\n\nreview trigger: settled\nsource session: /tmp/goals-native-pair-LFF9w2/agent/sessions/--tmp-goals-native-pair-LFF9w2--/2026-09-09T14-53-57-375Z_01a086a9-4cfe-76b0-998e-3a106c78b226.jsonl\nworker model: offline/test\nlatest human direction:\nInspect actual outputs.\ntool calls with no result: none\ntracked background work: No tracked work in this fixture.\n\nnew worker overview (initial or reset view) (VCC algorithmic compression; local # refs index new messages; tool-result bodies omitted; inspect source for evidence):\n[Session Goal]\n- Retain this planning context for the supervisor fork.\n\n[user]\nRetain this planning context for the supervisor fork.\n\n[assistant]\nTest context retained. Actual outputs still need inspection. (#1)\n\nworker view id: 55e3995b-2ae7-49ab-b38f-4406fd48a4be","reason":"settled","backgroundQuiet":true}},"id":"4544a17d","parentId":"2378d250","timestamp":"2026-09-09T14:53:58.674Z"}} +{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"supervisor","kind":"received","id":"55e3995b-2ae7-49ab-b38f-4406fd48a4be"}},"id":"9a591425","parentId":"4544a17d","timestamp":"2026-09-09T14:53:58.680Z"}} +{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"ack","message":{"binding":"native-pair-test","role":"supervisor","kind":"received","id":"55e3995b-2ae7-49ab-b38f-4406fd48a4be"}},"id":"70b9472b","parentId":"9a591425","timestamp":"2026-09-09T14:53:58.705Z"}} +{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"queued","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"f49295bc-d941-4684-b9f4-20ef94e0bbb1","text":"Read the real outputs before declaring completion."}},"id":"bd79ee34","parentId":"70b9472b","timestamp":"2026-09-09T14:53:58.717Z"}} +{"type":"agent_start"} +{"type":"turn_start"} +{"type":"entry_appended","entry":{"type":"custom","customType":"pi-goals-intercom","data":{"direction":"in","message":{"binding":"native-pair-test","role":"supervisor","kind":"steer","id":"f49295bc-d941-4684-b9f4-20ef94e0bbb1","text":"Read the real outputs before declaring completion."}},"id":"dd0f19bb","parentId":"bd79ee34","timestamp":"2026-09-09T14:53:58.718Z"}} +{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788965638718}} +{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788965638718}} +{"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":1788965638719}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"assistantMessageEvent":{"type":"text_start","contentIndex":0}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Test context retained. Actual outputs still need inspection."}} +{"type":"message_update","usage":{"input":10,"output":10,"cacheRead":0,"cacheWrite":0,"reasoning":0,"totalTokens":20,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"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":1788965638719,"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":1788965638719,"rawStopReason":"stop"},"toolResults":[]} +{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"[supervisor] Read the real outputs before declaring completion."}],"timestamp":1788965638718},{"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":1788965638719,"rawStopReason":"stop"}],"willRetry":false} +{"type":"agent_settled"} +{"type":"extension_ui_request","id":"141c01fc-c3ed-441f-98b7-78220c0aa126","method":"notify","message":"Goal supervision peer disconnected; reconnecting the existing session.","notifyType":"warning"} \ No newline at end of file diff --git a/slop/reviews/solo-recovery/round-3-native/worker-stderr.txt b/slop/reviews/solo-recovery/round-3-native/worker-stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/index.ts b/src/index.ts index 4aa7d2b..314c0b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -575,7 +575,15 @@ export function registerWorker(pi: ExtensionAPI): void { 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 (!current()) return; - if (error instanceof SupervisorFailure && state.phase === "working" && !modelError) { enterSolo(ctx, `Supervisor recovery failed: ${error.message}`); return; } + if (error instanceof SupervisorFailure && state.phase === "working" && !modelError) { + if (planIsComplete(ctx)) { + ctx.ui.notify(`Supervisor recovery failed: ${error.message} The completed plan remains supervised and its recorded sign-offs are unchanged.`, "warning"); + updateWidget(ctx); + return; + } + enterSolo(ctx, `Supervisor recovery failed: ${error.message}`); + return; + } ctx.ui.notify(`Goal recovery failed: ${String(error)} Use /goals reconnect to retry, or /goals restart to explicitly replace the tracked pane.`, "warning"); } finally { if (recoveryCommand === command) recoveryCommand = undefined; } updateWidget(ctx); diff --git a/src/intercom.ts b/src/intercom.ts index 8d52d50..510c51e 100644 --- a/src/intercom.ts +++ b/src/intercom.ts @@ -6,6 +6,7 @@ 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"; +const PAYLOAD_LIMIT_BYTES = 16_000; export class GoalIntercom { private channel?: IntercomExtensionChannel; @@ -183,13 +184,14 @@ export class GoalIntercom { steer(text: string): { id: string; queued: boolean } { if (!this.bound) throw new Error("Worker pairing is not active; no instruction was retained."); if (this.failure || this.peerFailure) throw new Error(this.failure ?? this.peerFailure); + const message: Message = { binding: this.binding, role: this.role, kind: "steer", id: randomUUID(), text }; + this.validatePayload(message, "; no instruction was retained."); if (!this.connected) { - const retained = [...this.pending.values()].find(message => message.text === text); + const retained = [...this.pending.values()].find(pending => pending.text === text); if (retained) return { id: retained.id, queued: true }; for (const pending of this.pending.values()) this.record("superseded", pending); this.pending.clear(); } - const message: Message = { binding: this.binding, role: this.role, kind: "steer", id: randomUUID(), text }; this.record("out", message); this.pending.set(message.id, message); if (this.connected) this.publish(message); @@ -229,9 +231,12 @@ export class GoalIntercom { } private record(direction: string, message: Message): void { this.pi.appendEntry(STATE, { direction, message }); } + private validatePayload(message: Message, suffix = ""): void { + if (Buffer.byteLength(JSON.stringify(message)) > PAYLOAD_LIMIT_BYTES) throw new Error(`Intercom message exceeds the 16 KB payload limit${suffix}`); + } 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."); + this.validatePayload(message); if (!this.channel?.snapshot().supported) throw new Error("pi-intercom broker does not support extension channels."); this.channel.publish(message, { audience: "capable" }); } diff --git a/test/goals-flow.test.ts b/test/goals-flow.test.ts index 0d19e71..a55d91b 100644 --- a/test/goals-flow.test.ts +++ b/test/goals-flow.test.ts @@ -780,6 +780,28 @@ it.each(["startup", "resume", "disconnect"])("allows the existing five-minute re } finally { rmSync(flow.cwd, { recursive: true, force: true }); } }); +it.each(["reconnect", "restart"])("keeps a fully signed-off completed plan supervised when manual %s recovery times out", async action => { + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] }); + const flow = setup([]); + try { + const path = restoredPlan(flow); + writeFileSync(path, readFileSync(path, "utf8").replace("[ ] goal:", "[x] goal:")); + Object.assign(flow.entries.at(-1)!.data as any, { mode: "supervised", soloReason: null, signedOffGoals: ["make the file"] }); + flow.transport.replyToHello(false); + await flow.hooks.get("session_start")({}, flow.ctx); + const recovering = flow.commands.get("goals").handler(action, flow.ctx); + await new Promise(resolve => setImmediate(resolve)); + await vi.advanceTimersByTimeAsync(300_000); + await recovering; + const state = flow.entries.at(-1)?.data as any; + expect(state).toMatchObject({ phase: "working", mode: "supervised", signedOffGoals: ["make the file"] }); + expect(state.approvalId).toBeTruthy(); + expect(state.soloReason).toBeNull(); + expect(flow.messages.some(message => message.content.includes("Continue useful implementation"))).toBe(false); + expect(flow.notifications.at(-1)).toContain("completed plan remains supervised"); + } finally { rmSync(flow.cwd, { recursive: true, force: true }); } +}); + it("falls back with the exact reported terminal peer failure without confusing it with worker model failure", async () => { const flow = setup([]); try { diff --git a/test/intercom.test.ts b/test/intercom.test.ts index a76bc56..37146ee 100644 --- a/test/intercom.test.ts +++ b/test/intercom.test.ts @@ -106,6 +106,22 @@ it("retries an unanswered active-binding hello twice after delayed channel regis vi.useRealTimers(); }); +it("rejects an oversize steer before retaining it whether connected or disconnected", async () => { + const runtime = setup("supervisor"); + runtime.link.markReady(); + await runtime.link.waitReady(); + const oversized = "x".repeat(16_000); + for (const connected of [true, false]) { + if (!connected) runtime.fixture.connect(false); + const entriesBefore = runtime.entries.length; + const sentBefore = runtime.fixture.sent.length; + expect(() => runtime.link.steer(oversized)).toThrow("16 KB payload limit; no instruction was retained"); + expect(runtime.entries).toHaveLength(entriesBefore); + expect(runtime.fixture.sent).toHaveLength(sentBefore); + } + await runtime.hooks.get("session_shutdown")(); +}); + it("retains only the newest disconnected steer across a supervisor reload", async () => { const first = setup("supervisor"); first.link.markReady();