fix(session): never let arg recovery rewrite a route into a new child - #1962
Closed
wqymi wants to merge 1 commit into
Closed
fix(session): never let arg recovery rewrite a route into a new child#1962wqymi wants to merge 1 commit into
wqymi wants to merge 1 commit into
Conversation
recoverSessionArgs' bare-{task} fallback synthesized {action:"create"} from ANY
payload carrying a `task` string. mimo-v2.5 repeatedly emits a FLATTENED shape
({"operation":"send","sessionID":"ses_…","task":"…"}) which strictObject rejects,
so had that recovery path been reached a correct route-to-existing-child `send`
would have been silently converted into a NEW child `create` — the route-first
violation #1741 exists to prevent, and invisible: no error, just a duplicate
child. (Today the strict-parse error fires first, so this is latent.)
Recovery now:
- reconstructs the flattened shape properly — the top-level discriminator
(`operation` verb string or `action`) plus its sibling operands are re-nested
and validated against the real union, so the model's actual intent runs. This
shape is a real, repeatedly-observed model output, and validating here is what
makes it safe: shell-wrap hands a recovered value to def.execute WITHOUT
re-validating against `parameters`.
- refuses to synthesize a `create` when the payload carries routing evidence
(sessionID / session_id / sessionIDs / question / target), and returns
undefined so the call errors loudly and the model self-corrects.
Collaborator
Author
|
Folded into #1741 as 1dabcc3 (cherry-picked cleanly, no conflicts; both sides' tests pass: 71 pass / 1 skip / 0 fail on test/tool/session-tool.test.ts + test/session/orchestrator-active-sessions.test.ts, typecheck exit 0). Reason: this is the same defect as #1741's, not a neighbouring one. Branch retained. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
recoverSessionArgs(src/tool/session.ts:338atorigin/main) is the shell-mode fallback for a malformedsessiontool call. Its last branch synthesizes acreatefrom any payload that carries ataskstring:mimo-v2.5repeatedly emits a FLATTENED shape instead of the nested one:{"operation":"send","sessionID":"ses_0582701b9ffenkU394MLPdIi6d","task":"…"}strictObjectrejects that with "Invalid arguments" today, so the recovery path is not reached in practice (the call errors loudly first and the model retries correctly — verified in live runs). But if it ever were, a correct route-to-existing-childsendwould be silently converted into a NEW childcreate— the exact route-first violation #1741 exists to prevent, and invisible: no error surfaces, you just get a duplicate child session doing the work the standing child should have picked up.The same hazard applies to
{"action":"send","sessionID":…,"task":…}, and to any{task, sessionID}pair with no verb at all.The fix, and why this shape
Two changes, both in
recoverSessionArgs:Reconstruct the flattened shape properly rather than merely erroring. The top-level discriminator — a bare
operationverb string that survived theJSON.parseattempt, oraction— is re-nested with its sibling operands and validated against the realparametersunion. On success the model's actual intent runs (sendstays asend); on failure we returnundefined.Justification for recovering rather than only erroring: this is a real, repeatedly observed model output, the intent is unambiguous (the verb is stated explicitly), and the reconstruction is checked — so it cannot misroute. Validation is load-bearing here, not decoration:
shell-wrap.ts:44-47hands a recovered value straight todef.executewithout re-validating it againstparameters(the comment there claiming otherwise is wrong), so an unvalidated re-nest could have pushed a half-built op into the send branch. A loud error remains the outcome for anything that does not validate.Never synthesize a
createwhen the payload carries routing evidence. If any ofsessionID/session_id/sessionIDs/question/targetis present, the model named an existing session (or an ask/grant target) and cannot have meant "spawn a new child" — recovery declines and returnsundefined, so the call errors loudly and the model self-corrects. A loud error that makes the model retry is acceptable; silent misrouting is not.A bare
{task}with no routing evidence still recovers into acreate, unchanged.Tests (
test/tool/session-tool.test.ts)NEVER yields a create when the payload carries a routing field— a battery of malformed payloads (sessionID,session_id,sessionIDs,question,target, and one with create-ish decorations alongside asessionID) each assertaction !== "create"and that recovery declines.reconstructs the FLATTENED send that mimo-v2.5 actually emits— both theoperation:"send"andaction:"send"variants become{operation:{action:"send",sessionID,task}}.reconstructs other flattened operations—status,cancel,ask(with its snake_casesession_id),list, and a flattenedcreate.a flattened operation that does not validate fails loudly instead of degrading to a create—sendmissingtask,sendmissingsessionID, and unknown verbs all returnundefinedrather than falling through to the create branch.a bare {task} with no routing evidence still creates (unchanged)plus all seven pre-existingrecoverSessionArgstests, untouched and passing.Revert-probed: with the function body restored to its
origin/mainform, exactly these 4 new tests fail, the central one verbatim asbun typecheckexits 0;test/tool/session-tool.test.tsis 56 pass / 1 skip / 0 fail.