@llui/agent 0.0.39 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +25 -0
- package/dist/client/factory.js.map +1 -1
- package/dist/client/rpc/describe-context.d.ts +34 -0
- package/dist/client/rpc/describe-context.d.ts.map +1 -1
- package/dist/client/rpc/describe-context.js +42 -3
- package/dist/client/rpc/describe-context.js.map +1 -1
- package/dist/client/rpc/describe-visible-content.d.ts +14 -0
- package/dist/client/rpc/describe-visible-content.d.ts.map +1 -1
- package/dist/client/rpc/describe-visible-content.js +17 -3
- package/dist/client/rpc/describe-visible-content.js.map +1 -1
- package/dist/client/rpc/list-actions.d.ts.map +1 -1
- package/dist/client/rpc/list-actions.js +94 -1
- package/dist/client/rpc/list-actions.js.map +1 -1
- package/dist/client/rpc/send-message.d.ts +8 -0
- package/dist/client/rpc/send-message.d.ts.map +1 -1
- package/dist/client/rpc/send-message.js +14 -7
- package/dist/client/rpc/send-message.js.map +1 -1
- package/dist/client/rpc/would-dispatch.d.ts +19 -0
- package/dist/client/rpc/would-dispatch.d.ts.map +1 -1
- package/dist/client/rpc/would-dispatch.js +14 -1
- package/dist/client/rpc/would-dispatch.js.map +1 -1
- package/dist/client/ws-client.d.ts +10 -1
- package/dist/client/ws-client.d.ts.map +1 -1
- package/dist/client/ws-client.js +68 -1
- package/dist/client/ws-client.js.map +1 -1
- package/dist/protocol.d.ts +23 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-actions.js","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"AAuFA,MAAM,UAAU,iBAAiB,CAAC,IAAqB;IACrD,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAA;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;IAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,qBAAqB,EAAE,IAAI,EAAE,CAAA;IACtD,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;IAElC,MAAM,GAAG,GAAiC,EAAE,CAAA;IAC5C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAE9B,sEAAsE;IACtE,oEAAoE;IACpE,oEAAoE;IACpE,2BAA2B;IAC3B,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,GAAG,EAAE,YAAY,KAAK,YAAY;YAAE,SAAQ;QAChD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACnB,MAAM,aAAa,GAAG,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACjD,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI;YAC3B,eAAe,EAAE,GAAG,EAAE,eAAe,IAAI,KAAK;YAC9C,YAAY,EAAE,GAAG,EAAE,YAAY,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ;YAC1E,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,IAAI;YACjB,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,IAAI;YAC7B,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,EAAE;YAC7B,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE;YACvB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE;SAClE,CAAC,CAAA;IACJ,CAAC;IAED,qEAAqE;IACrE,gEAAgE;IAChE,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,GAAG,EAAE,YAAY,KAAK,YAAY;YAAE,SAAQ;QAChD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAClB,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAA;QAC7B,MAAM,aAAa,GAAG,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC5C,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI;YAC3B,eAAe,EAAE,GAAG,EAAE,eAAe,IAAI,KAAK;YAC9C,YAAY,EAAE,GAAG,EAAE,YAAY,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ;YAC1E,MAAM,EAAE,mBAAmB;YAC3B,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;YACvD,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,IAAI;YAC7B,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,EAAE;YAC7B,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE;YACvB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE;SAClE,CAAC,CAAA;IACJ,CAAC;IAED,oEAAoE;IACpE,qEAAqE;IACrE,2DAA2D;IAC3D,gEAAgE;IAChE,qEAAqE;IACrE,oEAAoE;IACpE,kEAAkE;IAClE,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACzD,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAQ;QAC/B,IAAI,GAAG,CAAC,YAAY,KAAK,YAAY;YAAE,SAAQ;QAC/C,IAAI,CAAC,GAAG,CAAC,gBAAgB;YAAE,SAAQ;QACnC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACjB,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;QACxC,GAAG,CAAC,IAAI,CAAC;YACP,OAAO;YACP,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,YAAY,EAAE,GAAG,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ;YACzE,MAAM,EAAE,mBAAmB;YAC3B,YAAY,EAAE,IAAI;YAClB,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI;YAC/D,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;SACpD,CAAC,CAAA;IACJ,CAAC;IAED,qEAAqE;IACrE,mEAAmE;IACnE,mEAAmE;IACnE,gEAAgE;IAChE,EAAE;IACF,qEAAqE;IACrE,kEAAkE;IAClE,qEAAqE;IACrE,sEAAsE;IACtE,gDAAgD;IAChD,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAQ;YAC/B,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;YAChC,IAAI,GAAG,EAAE,YAAY,KAAK,YAAY;gBAAE,SAAQ;YAChD,GAAG,CAAC,IAAI,CAAC;gBACP,OAAO;gBACP,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,eAAe,EAAE,GAAG,CAAC,eAAe;gBACpC,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,QAAQ;gBAChB,YAAY,EAAE,IAAI;gBAClB,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC;gBAC/C,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAAC;aACtC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAA;AACzB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,iBAAiB,CAAC,OAAe,EAAE,MAAsC;IAChF,MAAM,GAAG,GAA4B,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IACtD,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;QACvC,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAA;QACrC,gEAAgE;QAChE,wBAAwB;QACxB,IAAI,QAAQ,IAAI,QAAQ,KAAK,QAAQ;YAAE,SAAQ;QAC/C,GAAG,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,iBAAiB,CACxB,MAAsC;IAEtC,MAAM,GAAG,GAA0C,EAAE,CAAA;IACrD,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAA;IACjC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,QAAQ,CACf,IAAY,EACZ,CAAiB,EACjB,GAA0C;IAE1C,gDAAgD;IAChD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QACvD,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;QAClC,CAAC;QACD,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC/B,OAAM;IACR,CAAC;IACD,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAA;AAC5B,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,CAAU,EAAE,GAA0C;IACxF,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAM;IAC/C,MAAM,GAAG,GAAG,CAA4B,CAAA;IACxC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACjF,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAuC,CAAC,EAAE,CAAC;YAC7F,QAAQ,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,CAAA;QAC9C,CAAC;QACD,OAAM;IACR,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzB,QAAQ,CAAC,GAAG,IAAI,IAAI,EAAE,GAAG,CAAC,OAAyB,EAAE,GAAG,CAAC,CAAA;IAC3D,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;QACvC,2DAA2D;QAC3D,kEAAkE;QAClE,gEAAgE;QAChE,6DAA6D;QAC7D,kDAAkD;QAClD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAA0D,CAAA;QAC/E,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAC7C,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACzC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI;gBACJ,IAAI,EAAE,+BAA+B,YAAY,iBAAiB,WAAW;qBAC1E,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;qBACpB,IAAI,CAAC,IAAI,CAAC,GAAG;aACjB,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3D,KAAK,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5D,QAAQ,CAAC,GAAG,IAAI,IAAI,YAAY,IAAI,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;YAChF,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,CAAiB;IACnC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAA;AACpE,CAAC;AAED,SAAS,QAAQ,CAAC,CAAiB;IACjC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;AACtE,CAAC;AAED,SAAS,YAAY,CAAC,CAAiB;IACrC,6DAA6D;IAC7D,MAAM,CAAC,GACL,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,CAAC;QAClC,CAAC,CAAC,CAAC,CAAC,IAAI;QACR,CAAC,CAAE,CAE+D,CAAA;IACtE,OAAO,cAAc,CAAC,CAAU,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,cAAc,CAAC,CAAU;IAChC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAA;QAC7B,IAAI,CAAC,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAA;QAC5B,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QACjC,OAAO,IAAI,CAAA,CAAC,kDAAkD;IAChE,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACpD,MAAM,GAAG,GAAG,CAA4B,CAAA;IACxC,IAAI,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,mEAAmE;QACnE,mEAAmE;QACnE,4BAA4B;QAC5B,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;IAC5B,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACjF,gEAAgE;QAChE,+DAA+D;QAC/D,oCAAoC;QACpC,MAAM,GAAG,GAA4B,EAAE,CAAA;QACvC,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAuC,CAAC,EAAE,CAAC;YAC7F,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;YACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAA;YAChC,IAAI,KAAK,IAAI,GAAG,KAAK,QAAQ;gBAAE,SAAQ;YACvC,GAAG,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;QACtC,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzB,iEAAiE;QACjE,+DAA+D;QAC/D,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;IACtC,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;QACvC,gEAAgE;QAChE,2DAA2D;QAC3D,iEAAiE;QACjE,6DAA6D;QAC7D,gEAAgE;QAChE,6DAA6D;QAC7D,2DAA2D;QAC3D,MAAM,QAAQ,GAAG,GAAG,CAAC,QAA0D,CAAA;QAC/E,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACjD,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAA;QAC5B,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG,UAAU,CAAA;QAC5C,MAAM,MAAM,GAA4B,EAAE,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,CAAA;QACtE,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7D,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;YACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAA;YAChC,IAAI,KAAK,IAAI,GAAG,KAAK,QAAQ;gBAAE,SAAQ;YACvC,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAAA;QACzC,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import type { MessageAnnotations } from '../../protocol.js'\nimport type { MsgSchemaShape, MsgSchemaField } from '../factory.js'\n\ntype Binding = { variant: string }\ntype Annotations = Record<string, MessageAnnotations>\n\nexport type ListActionsHost = {\n getState(): unknown\n getBindingDescriptors(): Binding[] | null\n getMsgAnnotations(): Annotations | null\n getMsgSchema(): MsgSchemaShape | null\n getAgentAffordances(): ((state: unknown) => Array<{ type: string; [k: string]: unknown }>) | null\n}\n\n/**\n * `dispatchMode` on each action is `'shared'` (human can also click via\n * a UI affordance) or `'agent-only'` (no UI binding — agent is the only\n * dispatcher). `'human-only'` variants are filtered out before this\n * point — they never reach the LLM.\n *\n * `source` distinguishes WHERE the affordance came from:\n * - `'binding'` — a tagged event handler is currently mounted in a\n * live scope (refcount > 0). Variants inside dead branches —\n * `show({when: false})`, unmounted `branch()` cases, removed `each`\n * items — auto-vanish from this set as their lifetimes dispose.\n * This is the framework's \"what can the user click right now\"\n * answer, and it's the default surface for the agent.\n * - `'always-affordable'` — either the app's `agentAffordances(state)`\n * hook listed the variant, or the variant carries the\n * `@alwaysAffordable` JSDoc tag. Both are the explicit \"agent can\n * reach this even when no live UI binding maps to it\" knob — bulk\n * seed ops (`Matrix/AddAlternatives`) and similar agent-driven\n * paths typically land here.\n * - `'schema'` — variant is annotated `@agentOnly` (the canonical\n * \"no UI button maps to this; the agent is the only dispatcher\")\n * and isn't already covered above. The payload is schema-synthesized\n * and the agent fills it in.\n *\n * `'shared'` variants WITHOUT a live binding, without\n * `agentAffordances` mention, and without `@alwaysAffordable` are\n * **deliberately hidden**. They're reachable through UI navigation —\n * the human user can't click them right now, and dispatching them\n * blindly would mutate state that drives `show()`/`branch()` gates,\n * popping hidden UI subtrees into view in places the user didn't\n * navigate to.\n */\nexport type ListActionsResult = {\n actions: Array<{\n variant: string\n /**\n * Human-readable phrase from `@intent(\"…\")`, or `null` when the\n * variant is unannotated. Mirror of LapActionsResponse.intent —\n * callers should treat `null` as a documentation gap and not as\n * \"missing label, fall back to variant name\".\n */\n intent: string | null\n requiresConfirm: boolean\n dispatchMode: 'shared' | 'agent-only'\n source: 'binding' | 'always-affordable' | 'schema'\n selectorHint: string | null\n payloadHint: object | null\n /** Cautionary text from `@warning` JSDoc, or null. */\n warning: string | null\n /** Concrete examples from `@example` JSDoc, in source order. */\n examples: string[]\n /**\n * Effect kinds this variant emits, from `@emits(\"k1\", \"k2\")`.\n * Empty when not annotated. Lets the agent know what side\n * effects fire — useful for batching (\"100 dispatches × cloud-\n * save = bad\") and for confirming destructive flows.\n */\n emits: string[]\n /**\n * Per-field guidance lifted from `@should(\"…\")` JSDoc on payload\n * fields. Path is dot/bracket notation rooted at the payload\n * (e.g. `\"cells\"` for a top-level field, `\"cells[].meta\"` for an\n * array element's nested field). Useful when the field is typed\n * as `unknown` or as a polymorphic shape — the hint says \"type\n * matches the criterion's kind: number for quantity, …\" so the\n * agent doesn't have to guess from the bare schema.\n *\n * Empty when no field on this variant carries an `@should` hint.\n */\n fieldHints: Array<{ path: string; hint: string }>\n }>\n}\n\nexport function handleListActions(host: ListActionsHost): ListActionsResult {\n const annotations = host.getMsgAnnotations() ?? {}\n const state = host.getState()\n const descriptors = host.getBindingDescriptors() ?? []\n const affordances = host.getAgentAffordances()?.(state) ?? []\n const schema = host.getMsgSchema()\n\n const out: ListActionsResult['actions'] = []\n const seen = new Set<string>()\n\n // From bindings — these have UI affordances by definition, so they're\n // either 'shared' (default) or, in the malformed case where someone\n // bound an `@agentOnly` Msg in a view, 'agent-only'. Either way the\n // agent can dispatch them.\n for (const d of descriptors) {\n const ann = annotations[d.variant]\n if (ann?.dispatchMode === 'human-only') continue\n seen.add(d.variant)\n const variantSchema = schema?.variants[d.variant]\n out.push({\n variant: d.variant,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'binding',\n selectorHint: null,\n payloadHint: null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `agentAffordances(state)` — the integrator's explicit list of\n // currently-reachable Msgs, with concrete payloads they author.\n for (const msg of affordances) {\n const ann = annotations[msg.type]\n if (ann?.dispatchMode === 'human-only') continue\n seen.add(msg.type)\n const { type, ...rest } = msg\n const variantSchema = schema?.variants[type]\n out.push({\n variant: type,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: Object.keys(rest).length > 0 ? rest : null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `@alwaysAffordable` annotations — the per-variant equivalent\n // of `agentAffordances`. Variants tagged this way surface regardless\n // of whether a live binding maps to them, so bulk seed ops\n // (`Matrix/AddAlternatives`) and similar agent-driven paths are\n // available even when their UI counterparts (if any) aren't mounted.\n // The payload is schema-synthesized — `agentAffordances` is the way\n // to ship a concrete pre-filled payload alongside the affordance.\n for (const [variant, ann] of Object.entries(annotations)) {\n if (seen.has(variant)) continue\n if (ann.dispatchMode === 'human-only') continue\n if (!ann.alwaysAffordable) continue\n seen.add(variant)\n const fields = schema?.variants[variant]\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: ann.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: fields ? synthesizePayload(variant, fields) : null,\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: fields ? collectFieldHints(fields) : [],\n })\n }\n\n // From schema — variants that aren't already surfaced above and that\n // the author marked `@agentOnly`. The canonical \"no UI button maps\n // to this; the agent is the only dispatcher.\" Bulk edits, imports,\n // admin operations that have no human-facing affordance at all.\n //\n // `'shared'` variants WITHOUT a live binding stay hidden here — they\n // are reachable through UI navigation, and dispatching them while\n // their UI subtree is unmounted would pop hidden state in places the\n // user didn't navigate to. The explicit knobs are `@alwaysAffordable`\n // (handled above) or `agentAffordances(state)`.\n if (schema) {\n for (const [variant, fields] of Object.entries(schema.variants)) {\n if (seen.has(variant)) continue\n const ann = annotations[variant]\n if (ann?.dispatchMode !== 'agent-only') continue\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: 'agent-only',\n source: 'schema',\n selectorHint: null,\n payloadHint: synthesizePayload(variant, fields),\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: collectFieldHints(fields),\n })\n }\n }\n\n return { actions: out }\n}\n\n/**\n * Build an example payload object the LLM can fill in. Required\n * fields always appear; optional fields appear only when annotated\n * `@should` (LLM is encouraged to fill them in). Fields without a\n * concrete primitive type (`'unknown'`) emit `null` placeholders the\n * LLM is expected to replace.\n *\n * The first key is `type` so the payload reads as a complete Msg\n * shape — copy-paste-ready into `send_message`.\n */\nfunction synthesizePayload(variant: string, fields: Record<string, MsgSchemaField>): object {\n const out: Record<string, unknown> = { type: variant }\n for (const [name, descriptor] of Object.entries(fields)) {\n const optional = isOptional(descriptor)\n const priority = isShould(descriptor)\n // Skip optional fields unless they're @should-flagged. Required\n // fields always appear.\n if (optional && priority !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n}\n\n/**\n * Walk a variant's field tree and collect every `@should` hint into a\n * flat list keyed by field path. Path conventions:\n * - top-level field: `\"cells\"`\n * - nested object property: `\"cells.value\"`\n * - array element: `\"cells[]\"` for the element itself; descendants\n * use `\"cells[].meta\"` and so on.\n *\n * Surfaces the same hints that show up nested inside\n * `description.messages.variants[X].field.hint` so callers don't have\n * to dig through the schema tree to find them.\n */\nfunction collectFieldHints(\n fields: Record<string, MsgSchemaField>,\n): Array<{ path: string; hint: string }> {\n const out: Array<{ path: string; hint: string }> = []\n for (const [name, descriptor] of Object.entries(fields)) {\n walkHint(name, descriptor, out)\n }\n return out\n}\n\nfunction walkHint(\n path: string,\n d: MsgSchemaField,\n out: Array<{ path: string; hint: string }>,\n): void {\n // Rich descriptor with a hint at this position.\n if (typeof d === 'object' && d !== null && 'type' in d) {\n if (typeof d.hint === 'string' && d.hint.length > 0) {\n out.push({ path, hint: d.hint })\n }\n walkHintBare(path, d.type, out)\n return\n }\n walkHintBare(path, d, out)\n}\n\nfunction walkHintBare(path: string, t: unknown, out: Array<{ path: string; hint: string }>): void {\n if (t === null || typeof t !== 'object') return\n const obj = t as Record<string, unknown>\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n walkHint(`${path}.${name}`, descriptor, out)\n }\n return\n }\n if (obj.kind === 'array') {\n walkHint(`${path}[]`, obj.element as MsgSchemaField, out)\n }\n if (obj.kind === 'discriminated-union') {\n // Synthetic hint at the union's path summarizing the legal\n // discriminant values. Lets the agent see \"format expects one of:\n // exact, range, compound\" without walking the full schema. Then\n // walk each branch's per-field hints with a path-suffix that\n // disambiguates which branch the hint applies to.\n const variants = obj.variants as Record<string, Record<string, MsgSchemaField>>\n const discriminant = String(obj.discriminant)\n const legalValues = Object.keys(variants)\n if (legalValues.length > 0) {\n out.push({\n path,\n hint: `Discriminated union — set \\`${discriminant}\\` to one of: ${legalValues\n .map((v) => `'${v}'`)\n .join(', ')}.`,\n })\n }\n for (const [discValue, fields] of Object.entries(variants)) {\n for (const [fieldName, fieldDesc] of Object.entries(fields)) {\n walkHint(`${path}(${discriminant}=${discValue}).${fieldName}`, fieldDesc, out)\n }\n }\n }\n}\n\nfunction isOptional(d: MsgSchemaField): boolean {\n return typeof d === 'object' && 'type' in d && d.optional === true\n}\n\nfunction isShould(d: MsgSchemaField): 'should' | undefined {\n return typeof d === 'object' && 'type' in d ? d.priority : undefined\n}\n\nfunction exampleValue(d: MsgSchemaField): unknown {\n // Unwrap rich descriptor to get the bare type for synthesis.\n const t =\n typeof d === 'object' && 'type' in d\n ? d.type\n : (d as\n | Exclude<MsgSchemaField, object>\n | Extract<MsgSchemaField, { kind?: string; enum?: string[] }>)\n return synthesizeBare(t as never)\n}\n\nfunction synthesizeBare(t: unknown): unknown {\n if (typeof t === 'string') {\n if (t === 'string') return ''\n if (t === 'number') return 0\n if (t === 'boolean') return false\n return null // 'unknown' or unrecognized keyword → placeholder\n }\n if (t === null || typeof t !== 'object') return null\n const obj = t as Record<string, unknown>\n if ('enum' in obj && Array.isArray(obj.enum)) {\n // First option doubles as the canonical example. Native value type\n // round-trips (string/number/boolean) since the compiler preserved\n // the literal kind on emit.\n return obj.enum[0] ?? null\n }\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n // Recurse into the nested shape. Same optional-skip rule as the\n // top-level synthesizer: required fields appear, optional ones\n // appear only when @should-flagged.\n const out: Record<string, unknown> = {}\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n const isOpt = isOptional(descriptor)\n const pri = isShould(descriptor)\n if (isOpt && pri !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n }\n if (obj.kind === 'array') {\n // Wrap the synthesized element in a one-item array. Lets the LLM\n // see the per-entry shape without us guessing at array length.\n return [synthesizeBare(obj.element)]\n }\n if (obj.kind === 'discriminated-union') {\n // Synthesize the FIRST branch as the concrete example. The full\n // shape (every legal branch + its payload) is preserved in\n // `description.messages.variants[X]` from `describe_app`, so the\n // agent that needs another branch reads the schema directly.\n // `collectFieldHints` adds a synthetic enumeration of the legal\n // `<discriminant>` values onto the hint surface so the agent\n // doesn't have to dig into the schema for the simple case.\n const variants = obj.variants as Record<string, Record<string, MsgSchemaField>>\n const discriminant = String(obj.discriminant)\n const firstEntry = Object.entries(variants).at(0)\n if (!firstEntry) return null\n const [firstValue, firstFields] = firstEntry\n const branch: Record<string, unknown> = { [discriminant]: firstValue }\n for (const [name, descriptor] of Object.entries(firstFields)) {\n const isOpt = isOptional(descriptor)\n const pri = isShould(descriptor)\n if (isOpt && pri !== 'should') continue\n branch[name] = exampleValue(descriptor)\n }\n return branch\n }\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"list-actions.js","sourceRoot":"","sources":["../../../src/client/rpc/list-actions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageAnnotations } from '../../protocol.js'\nimport type { MsgSchemaShape, MsgSchemaField } from '../factory.js'\n\ntype Binding = { variant: string }\ntype Annotations = Record<string, MessageAnnotations>\n\nexport type ListActionsHost = {\n getState(): unknown\n getBindingDescriptors(): Binding[] | null\n getMsgAnnotations(): Annotations | null\n getMsgSchema(): MsgSchemaShape | null\n getAgentAffordances(): ((state: unknown) => Array<{ type: string; [k: string]: unknown }>) | null\n}\n\n/**\n * `dispatchMode` on each action is `'shared'` (human can also click via\n * a UI affordance) or `'agent-only'` (no UI binding — agent is the only\n * dispatcher). `'human-only'` variants are filtered out before this\n * point — they never reach the LLM.\n *\n * `source` distinguishes WHERE the affordance came from:\n * - `'binding'` — a tagged event handler is currently mounted in a\n * live scope (refcount > 0). Variants inside dead branches —\n * `show({when: false})`, unmounted `branch()` cases, removed `each`\n * items — auto-vanish from this set as their lifetimes dispose.\n * This is the framework's \"what can the user click right now\"\n * answer, and it's the default surface for the agent.\n * - `'always-affordable'` — either the app's `agentAffordances(state)`\n * hook listed the variant, or the variant carries the\n * `@alwaysAffordable` JSDoc tag. Both are the explicit \"agent can\n * reach this even when no live UI binding maps to it\" knob — bulk\n * seed ops (`Matrix/AddAlternatives`) and similar agent-driven\n * paths typically land here.\n * - `'schema'` — variant is annotated `@agentOnly` (the canonical\n * \"no UI button maps to this; the agent is the only dispatcher\")\n * and isn't already covered above. The payload is schema-synthesized\n * and the agent fills it in.\n *\n * `'shared'` variants WITHOUT a live binding, without\n * `agentAffordances` mention, and without `@alwaysAffordable` are\n * **deliberately hidden**. They're reachable through UI navigation —\n * the human user can't click them right now, and dispatching them\n * blindly would mutate state that drives `show()`/`branch()` gates,\n * popping hidden UI subtrees into view in places the user didn't\n * navigate to.\n */\nexport type ListActionsResult = {\n actions: Array<{\n variant: string\n /**\n * Human-readable phrase from `@intent(\"…\")`, or `null` when the\n * variant is unannotated. Mirror of LapActionsResponse.intent —\n * callers should treat `null` as a documentation gap and not as\n * \"missing label, fall back to variant name\".\n */\n intent: string | null\n requiresConfirm: boolean\n dispatchMode: 'shared' | 'agent-only'\n source: 'binding' | 'always-affordable' | 'schema'\n selectorHint: string | null\n payloadHint: object | null\n /** Cautionary text from `@warning` JSDoc, or null. */\n warning: string | null\n /** Concrete examples from `@example` JSDoc, in source order. */\n examples: string[]\n /**\n * Effect kinds this variant emits, from `@emits(\"k1\", \"k2\")`.\n * Empty when not annotated. Lets the agent know what side\n * effects fire — useful for batching (\"100 dispatches × cloud-\n * save = bad\") and for confirming destructive flows.\n */\n emits: string[]\n /**\n * Per-field guidance lifted from `@should(\"…\")` JSDoc on payload\n * fields. Path is dot/bracket notation rooted at the payload\n * (e.g. `\"cells\"` for a top-level field, `\"cells[].meta\"` for an\n * array element's nested field). Useful when the field is typed\n * as `unknown` or as a polymorphic shape — the hint says \"type\n * matches the criterion's kind: number for quantity, …\" so the\n * agent doesn't have to guess from the bare schema.\n *\n * Empty when no field on this variant carries an `@should` hint.\n */\n fieldHints: Array<{ path: string; hint: string }>\n }>\n}\n\nexport function handleListActions(host: ListActionsHost): ListActionsResult {\n const annotations = host.getMsgAnnotations() ?? {}\n const state = host.getState()\n const descriptors = host.getBindingDescriptors() ?? []\n const affordancesFn = host.getAgentAffordances()\n const affordances = affordancesFn ? affordancesFn(state) : []\n // When the app provides `agentAffordances(state)`, it's opted into\n // explicit affordance control: only state-relevant Msgs are listed.\n // `@agentOnly` schema-source variants are then filtered to those the\n // hook returned — so a bulk-edit Msg like `Matrix/AddCriteria`\n // doesn't surface on the home page just because it's tagged\n // `@agentOnly`. Apps without `agentAffordances` keep the previous\n // permissive default (\"everything's available unless you say\n // otherwise\") since flipping that without explicit opt-in would\n // break consumers who rely on schema-source surfacing of\n // bulk Msgs.\n const explicitAffordances = affordancesFn !== null\n const affordanceVariants = new Set(affordances.map((m) => m.type))\n const schema = host.getMsgSchema()\n\n const out: ListActionsResult['actions'] = []\n const seen = new Set<string>()\n\n // From bindings — these have UI affordances by definition, so they're\n // either 'shared' (default) or, in the malformed case where someone\n // bound an `@agentOnly` Msg in a view, 'agent-only'. Either way the\n // agent can dispatch them.\n //\n // Filtered against the Msg schema: a binding whose variant isn't in\n // the user's Msg union is a library-internal Msg leaking through\n // `tagSend` translator wiring (the sortable component's `move`,\n // `drop`, `cancel`, etc. — they're routed into the user's update.ts\n // via a different shape but their lib names slip into the binding\n // registry). The agent has no use for those names — `would_dispatch`\n // / `send_message` would reject them as `unknown-variant` anyway —\n // so they pollute the affordance list. When a schema is available,\n // the schema's variant set is the source of truth.\n for (const d of descriptors) {\n if (schema && !(d.variant in schema.variants)) continue\n const ann = annotations[d.variant]\n if (ann?.dispatchMode === 'human-only') continue\n if (!passesRouteGate(ann, state)) continue\n seen.add(d.variant)\n const variantSchema = schema?.variants[d.variant]\n out.push({\n variant: d.variant,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'binding',\n selectorHint: null,\n payloadHint: null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `agentAffordances(state)` — the integrator's explicit list of\n // currently-reachable Msgs, with concrete payloads they author.\n for (const msg of affordances) {\n const ann = annotations[msg.type]\n if (ann?.dispatchMode === 'human-only') continue\n if (!passesRouteGate(ann, state)) continue\n seen.add(msg.type)\n const { type, ...rest } = msg\n const variantSchema = schema?.variants[type]\n out.push({\n variant: type,\n intent: ann?.intent ?? null,\n requiresConfirm: ann?.requiresConfirm ?? false,\n dispatchMode: ann?.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: Object.keys(rest).length > 0 ? rest : null,\n warning: ann?.warning ?? null,\n examples: ann?.examples ?? [],\n emits: ann?.emits ?? [],\n fieldHints: variantSchema ? collectFieldHints(variantSchema) : [],\n })\n }\n\n // From `@alwaysAffordable` annotations — the per-variant equivalent\n // of `agentAffordances`. Variants tagged this way surface regardless\n // of whether a live binding maps to them, so bulk seed ops\n // (`Matrix/AddAlternatives`) and similar agent-driven paths are\n // available even when their UI counterparts (if any) aren't mounted.\n // The payload is schema-synthesized — `agentAffordances` is the way\n // to ship a concrete pre-filled payload alongside the affordance.\n for (const [variant, ann] of Object.entries(annotations)) {\n if (seen.has(variant)) continue\n if (ann.dispatchMode === 'human-only') continue\n if (!ann.alwaysAffordable) continue\n if (!passesRouteGate(ann, state)) continue\n seen.add(variant)\n const fields = schema?.variants[variant]\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: ann.dispatchMode === 'agent-only' ? 'agent-only' : 'shared',\n source: 'always-affordable',\n selectorHint: null,\n payloadHint: fields ? synthesizePayload(variant, fields) : null,\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: fields ? collectFieldHints(fields) : [],\n })\n }\n\n // From schema — variants that aren't already surfaced above and that\n // the author marked `@agentOnly`. The canonical \"no UI button maps\n // to this; the agent is the only dispatcher.\" Bulk edits, imports,\n // admin operations that have no human-facing affordance at all.\n //\n // `'shared'` variants WITHOUT a live binding stay hidden here — they\n // are reachable through UI navigation, and dispatching them while\n // their UI subtree is unmounted would pop hidden state in places the\n // user didn't navigate to. The explicit knobs are `@alwaysAffordable`\n // (handled above) or `agentAffordances(state)`.\n //\n // When the app provides `agentAffordances`, this pass is filtered:\n // an `@agentOnly` variant only surfaces if the hook returned it.\n // That makes route-gated bulk Msgs (`Matrix/AddCriteria` available\n // only when a matrix is loaded) work as expected — they stop\n // appearing on the home page just because they're tagged\n // `@agentOnly`. Apps without `agentAffordances` keep the previous\n // permissive default.\n if (schema) {\n for (const [variant, fields] of Object.entries(schema.variants)) {\n if (seen.has(variant)) continue\n const ann = annotations[variant]\n if (ann?.dispatchMode !== 'agent-only') continue\n if (explicitAffordances && !affordanceVariants.has(variant)) continue\n if (!passesRouteGate(ann, state)) continue\n out.push({\n variant,\n intent: ann.intent,\n requiresConfirm: ann.requiresConfirm,\n dispatchMode: 'agent-only',\n source: 'schema',\n selectorHint: null,\n payloadHint: synthesizePayload(variant, fields),\n warning: ann.warning,\n examples: ann.examples,\n emits: ann.emits,\n fieldHints: collectFieldHints(fields),\n })\n }\n }\n\n return { actions: out }\n}\n\n/**\n * Build an example payload object the LLM can fill in. Required\n * fields always appear; optional fields appear only when annotated\n * `@should` (LLM is encouraged to fill them in). Fields without a\n * concrete primitive type (`'unknown'`) emit `null` placeholders the\n * LLM is expected to replace.\n *\n * The first key is `type` so the payload reads as a complete Msg\n * shape — copy-paste-ready into `send_message`.\n */\n/**\n * Evaluate `@routeGated(\"predicate\")` against the current state.\n * Returns true (variant passes) when:\n * - the variant has no `@routeGated` annotation, OR\n * - the predicate evaluates truthy with `state` bound.\n *\n * Predicate is compiled lazily via `new Function('state', 'return (' +\n * src + ')')` and cached in a module-level Map. Compile failures\n * (syntactically broken predicates) degrade to \"true\" so a single\n * malformed annotation doesn't paralyze the affordance pass — the\n * build-time linter is the right place to catch syntactic issues.\n * Evaluation throws fail-closed (return false) since a predicate that\n * crashes on the current state shouldn't surface the variant.\n */\nfunction passesRouteGate(ann: MessageAnnotations | undefined, state: unknown): boolean {\n const src = ann?.routeGate\n if (!src) return true\n const predicate = compileRouteGate(src)\n try {\n return Boolean(predicate(state))\n } catch {\n return false\n }\n}\n\nconst routeGateCache = new Map<string, (state: unknown) => boolean>()\nfunction compileRouteGate(src: string): (state: unknown) => boolean {\n let fn = routeGateCache.get(src)\n if (fn) return fn\n try {\n // eslint-disable-next-line @typescript-eslint/no-implied-eval\n fn = new Function('state', `return (${src})`) as (state: unknown) => boolean\n } catch {\n fn = () => true\n }\n routeGateCache.set(src, fn)\n return fn\n}\n\nfunction synthesizePayload(variant: string, fields: Record<string, MsgSchemaField>): object {\n const out: Record<string, unknown> = { type: variant }\n for (const [name, descriptor] of Object.entries(fields)) {\n const optional = isOptional(descriptor)\n const priority = isShould(descriptor)\n // Skip optional fields unless they're @should-flagged. Required\n // fields always appear.\n if (optional && priority !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n}\n\n/**\n * Walk a variant's field tree and collect every `@should` hint into a\n * flat list keyed by field path. Path conventions:\n * - top-level field: `\"cells\"`\n * - nested object property: `\"cells.value\"`\n * - array element: `\"cells[]\"` for the element itself; descendants\n * use `\"cells[].meta\"` and so on.\n *\n * Surfaces the same hints that show up nested inside\n * `description.messages.variants[X].field.hint` so callers don't have\n * to dig through the schema tree to find them.\n */\nfunction collectFieldHints(\n fields: Record<string, MsgSchemaField>,\n): Array<{ path: string; hint: string }> {\n const out: Array<{ path: string; hint: string }> = []\n for (const [name, descriptor] of Object.entries(fields)) {\n walkHint(name, descriptor, out)\n }\n return out\n}\n\nfunction walkHint(\n path: string,\n d: MsgSchemaField,\n out: Array<{ path: string; hint: string }>,\n): void {\n // Rich descriptor with a hint at this position.\n if (typeof d === 'object' && d !== null && 'type' in d) {\n if (typeof d.hint === 'string' && d.hint.length > 0) {\n out.push({ path, hint: d.hint })\n }\n // `@validates(...)` predicates surface alongside `@should` hints\n // so the agent sees the constraint at affordance time rather than\n // only as a post-dispatch rejection. The verbatim predicate text\n // is what the runtime evaluates; agents trained on JS read it\n // directly. Prefix `validates: ` to disambiguate from freeform\n // `@should` text.\n if (typeof d.validates === 'string' && d.validates.length > 0) {\n out.push({ path, hint: `validates: ${d.validates}` })\n }\n walkHintBare(path, d.type, out)\n return\n }\n walkHintBare(path, d, out)\n}\n\nfunction walkHintBare(path: string, t: unknown, out: Array<{ path: string; hint: string }>): void {\n if (t === null || typeof t !== 'object') return\n const obj = t as Record<string, unknown>\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n walkHint(`${path}.${name}`, descriptor, out)\n }\n return\n }\n if (obj.kind === 'array') {\n walkHint(`${path}[]`, obj.element as MsgSchemaField, out)\n }\n if (obj.kind === 'discriminated-union') {\n // Synthetic hint at the union's path summarizing the legal\n // discriminant values. Lets the agent see \"format expects one of:\n // exact, range, compound\" without walking the full schema. Then\n // walk each branch's per-field hints with a path-suffix that\n // disambiguates which branch the hint applies to.\n const variants = obj.variants as Record<string, Record<string, MsgSchemaField>>\n const discriminant = String(obj.discriminant)\n const legalValues = Object.keys(variants)\n if (legalValues.length > 0) {\n out.push({\n path,\n hint: `Discriminated union — set \\`${discriminant}\\` to one of: ${legalValues\n .map((v) => `'${v}'`)\n .join(', ')}.`,\n })\n }\n for (const [discValue, fields] of Object.entries(variants)) {\n for (const [fieldName, fieldDesc] of Object.entries(fields)) {\n walkHint(`${path}(${discriminant}=${discValue}).${fieldName}`, fieldDesc, out)\n }\n }\n }\n}\n\nfunction isOptional(d: MsgSchemaField): boolean {\n return typeof d === 'object' && 'type' in d && d.optional === true\n}\n\nfunction isShould(d: MsgSchemaField): 'should' | undefined {\n return typeof d === 'object' && 'type' in d ? d.priority : undefined\n}\n\nfunction exampleValue(d: MsgSchemaField): unknown {\n // Unwrap rich descriptor to get the bare type for synthesis.\n const t =\n typeof d === 'object' && 'type' in d\n ? d.type\n : (d as\n | Exclude<MsgSchemaField, object>\n | Extract<MsgSchemaField, { kind?: string; enum?: string[] }>)\n return synthesizeBare(t as never)\n}\n\nfunction synthesizeBare(t: unknown): unknown {\n if (typeof t === 'string') {\n if (t === 'string') return ''\n if (t === 'number') return 0\n if (t === 'boolean') return false\n return null // 'unknown' or unrecognized keyword → placeholder\n }\n if (t === null || typeof t !== 'object') return null\n const obj = t as Record<string, unknown>\n if ('enum' in obj && Array.isArray(obj.enum)) {\n // First option doubles as the canonical example. Native value type\n // round-trips (string/number/boolean) since the compiler preserved\n // the literal kind on emit.\n return obj.enum[0] ?? null\n }\n if (obj.kind === 'object' && obj.shape !== null && typeof obj.shape === 'object') {\n // Recurse into the nested shape. Same optional-skip rule as the\n // top-level synthesizer: required fields appear, optional ones\n // appear only when @should-flagged.\n const out: Record<string, unknown> = {}\n for (const [name, descriptor] of Object.entries(obj.shape as Record<string, MsgSchemaField>)) {\n const isOpt = isOptional(descriptor)\n const pri = isShould(descriptor)\n if (isOpt && pri !== 'should') continue\n out[name] = exampleValue(descriptor)\n }\n return out\n }\n if (obj.kind === 'array') {\n // Wrap the synthesized element in a one-item array. Lets the LLM\n // see the per-entry shape without us guessing at array length.\n return [synthesizeBare(obj.element)]\n }\n if (obj.kind === 'discriminated-union') {\n // Synthesize the FIRST branch as the concrete example. The full\n // shape (every legal branch + its payload) is preserved in\n // `description.messages.variants[X]` from `describe_app`, so the\n // agent that needs another branch reads the schema directly.\n // `collectFieldHints` adds a synthetic enumeration of the legal\n // `<discriminant>` values onto the hint surface so the agent\n // doesn't have to dig into the schema for the simple case.\n const variants = obj.variants as Record<string, Record<string, MsgSchemaField>>\n const discriminant = String(obj.discriminant)\n const firstEntry = Object.entries(variants).at(0)\n if (!firstEntry) return null\n const [firstValue, firstFields] = firstEntry\n const branch: Record<string, unknown> = { [discriminant]: firstValue }\n for (const [name, descriptor] of Object.entries(firstFields)) {\n const isOpt = isOptional(descriptor)\n const pri = isShould(descriptor)\n if (isOpt && pri !== 'should') continue\n branch[name] = exampleValue(descriptor)\n }\n return branch\n }\n return null\n}\n"]}
|
|
@@ -40,6 +40,14 @@ export type SendMessageHost = ListActionsHost & {
|
|
|
40
40
|
* without `window`), the drain envelope records an empty array.
|
|
41
41
|
*/
|
|
42
42
|
getAndClearDrainErrors?: () => LapDrainMeta['errors'];
|
|
43
|
+
/**
|
|
44
|
+
* Optional dispatch-policy accessor — when defined, returns the
|
|
45
|
+
* server's configured `'strict' | 'lenient'` policy for payload
|
|
46
|
+
* validation. Strict mode rejects fields not in the schema and
|
|
47
|
+
* emits warnings for `'unknown'`-typed fields the agent provided
|
|
48
|
+
* values for. Default is lenient (omit / undefined).
|
|
49
|
+
*/
|
|
50
|
+
getDispatchPolicy?: () => 'strict' | 'lenient';
|
|
43
51
|
/** Called when @requiresConfirm; caller stores a ConfirmEntry in state. */
|
|
44
52
|
proposeConfirm(entry: {
|
|
45
53
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-message.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAG3E,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;IACrC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,QAAQ,IAAI,OAAO,CAAA;IACnB,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;IAC9D;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAA;IACrD,2EAA2E;IAC3E,cAAc,CAAC,KAAK,EAAE;QACpB,EAAE,EAAE,MAAM,CAAA;QACV,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,UAAU,EAAE,MAAM,CAAA;QAClB,MAAM,EAAE,SAAS,CAAA;KAClB,GAAG,IAAI,CAAA;CACT,CAAA;AAKD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"send-message.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAG3E,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;IACrC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,QAAQ,IAAI,OAAO,CAAA;IACnB,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;IAC9D;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAA;IACrD;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAA;IAC9C,2EAA2E;IAC3E,cAAc,CAAC,KAAK,EAAE;QACpB,EAAE,EAAE,MAAM,CAAA;QACV,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,UAAU,EAAE,MAAM,CAAA;QAClB,MAAM,EAAE,SAAS,CAAA;KAClB,GAAG,IAAI,CAAA;CACT,CAAA;AAKD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,kBAAkB,CAAC,CAiK7B;AA2GD,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,CAAA;AACtF,YAAY,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -28,7 +28,8 @@ export async function handleSendMessage(host, args) {
|
|
|
28
28
|
// correct from in one round trip. Reducers stay the last line of
|
|
29
29
|
// defense; this is the first.
|
|
30
30
|
const schema = host.getMsgSchema?.() ?? null;
|
|
31
|
-
const
|
|
31
|
+
const policy = host.getDispatchPolicy?.() ?? 'lenient';
|
|
32
|
+
const validation = validatePayload(args.msg, schema, { policy });
|
|
32
33
|
if (!validation.ok) {
|
|
33
34
|
return {
|
|
34
35
|
status: 'rejected',
|
|
@@ -36,6 +37,11 @@ export async function handleSendMessage(host, args) {
|
|
|
36
37
|
detail: validation.errors.map((e) => `${e.path}: ${e.message}`).join('; '),
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
// Warnings from the validator (strict-mode `untyped-field` flags etc.)
|
|
41
|
+
// ride along to `drain.warnings` so the agent sees them on the
|
|
42
|
+
// dispatched envelope. Lenient mode never emits warnings; this is
|
|
43
|
+
// a no-op array for the default path.
|
|
44
|
+
const validationWarnings = validation.warnings ?? [];
|
|
39
45
|
if (ann?.requiresConfirm) {
|
|
40
46
|
const id = randomUUID();
|
|
41
47
|
const { type: _type, ...payload } = args.msg;
|
|
@@ -62,13 +68,13 @@ export async function handleSendMessage(host, args) {
|
|
|
62
68
|
const includeState = args.includeState === true;
|
|
63
69
|
if (waitFor === 'none') {
|
|
64
70
|
safeSend(host, args.msg, []);
|
|
65
|
-
return dispatched(host, emptyDrain(), prevState, includeState);
|
|
71
|
+
return dispatched(host, emptyDrain(), prevState, includeState, validationWarnings);
|
|
66
72
|
}
|
|
67
73
|
if (waitFor === 'idle') {
|
|
68
74
|
const dispatchErrors = [];
|
|
69
75
|
safeSendAndFlush(host, args.msg, dispatchErrors);
|
|
70
76
|
await Promise.resolve();
|
|
71
|
-
return dispatched(host, { effectsObserved: 1, durationMs: 0, timedOut: false, errors: dispatchErrors }, prevState, includeState);
|
|
77
|
+
return dispatched(host, { effectsObserved: 1, durationMs: 0, timedOut: false, errors: dispatchErrors }, prevState, includeState, validationWarnings);
|
|
72
78
|
}
|
|
73
79
|
// waitFor === 'drained' — message-queue quiescence detection.
|
|
74
80
|
// Clear any errors buffered before this call so `drain.errors`
|
|
@@ -98,7 +104,7 @@ export async function handleSendMessage(host, args) {
|
|
|
98
104
|
durationMs: elapsed,
|
|
99
105
|
timedOut: true,
|
|
100
106
|
errors: mergeDrainErrors(dispatchErrors, host.getAndClearDrainErrors?.()),
|
|
101
|
-
}, prevState, includeState);
|
|
107
|
+
}, prevState, includeState, validationWarnings);
|
|
102
108
|
}
|
|
103
109
|
const budget = Math.min(quietMs, capMs - elapsed);
|
|
104
110
|
// When the cap is within `quietMs` of `elapsed`, the quiet
|
|
@@ -116,7 +122,7 @@ export async function handleSendMessage(host, args) {
|
|
|
116
122
|
durationMs: now() - t0,
|
|
117
123
|
timedOut: !fullQuiet,
|
|
118
124
|
errors: mergeDrainErrors(dispatchErrors, host.getAndClearDrainErrors?.()),
|
|
119
|
-
}, prevState, includeState);
|
|
125
|
+
}, prevState, includeState, validationWarnings);
|
|
120
126
|
}
|
|
121
127
|
// A commit fired during the wait — flush any queued follow-ups so
|
|
122
128
|
// effects dispatched by that cycle run before we re-check.
|
|
@@ -181,13 +187,14 @@ function mergeDrainErrors(fromDispatch, fromHost) {
|
|
|
181
187
|
return fromHost;
|
|
182
188
|
return [...fromDispatch, ...fromHost];
|
|
183
189
|
}
|
|
184
|
-
function dispatched(host, drain, prevState, includeState) {
|
|
190
|
+
function dispatched(host, drain, prevState, includeState, validationWarnings = []) {
|
|
185
191
|
const stateAfter = host.getState();
|
|
192
|
+
const drainWithWarnings = validationWarnings.length === 0 ? drain : { ...drain, warnings: validationWarnings };
|
|
186
193
|
const base = {
|
|
187
194
|
status: 'dispatched',
|
|
188
195
|
stateDiff: computeStateDiff(prevState, stateAfter),
|
|
189
196
|
actions: handleListActions(host).actions,
|
|
190
|
-
drain,
|
|
197
|
+
drain: drainWithWarnings,
|
|
191
198
|
};
|
|
192
199
|
return includeState ? { ...base, stateAfter } : base;
|
|
193
200
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-message.js","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from '../uuid.js'\nimport { handleListActions, type ListActionsHost } from './list-actions.js'\nimport { computeStateDiff } from '../../state-diff.js'\nimport { validatePayload } from './validate-payload.js'\nimport type {\n LapActionsResponse,\n LapDrainMeta,\n LapMessageResponse,\n MessageAnnotations,\n} from '../../protocol.js'\n\nexport type SendMessageArgs = {\n msg: { type: string; [k: string]: unknown }\n reason?: string\n /** See LapMessageRequest['waitFor']. Default: 'drained'. */\n waitFor?: 'drained' | 'idle' | 'none'\n /** See LapMessageRequest['drainQuietMs']. Default: 100ms. */\n drainQuietMs?: number\n /** See LapMessageRequest['timeoutMs']. Default: 5000ms. */\n timeoutMs?: number\n /** See LapMessageRequest['includeState']. Default: false. */\n includeState?: boolean\n}\n\nexport type SendMessageHost = ListActionsHost & {\n getState(): unknown\n send(msg: unknown): void\n flush(): void\n /**\n * Register a listener called after every update cycle commits —\n * backed by `AppHandle.subscribe`. Returns an unsubscribe function.\n * The drain loop uses this to detect message-queue quiescence: each\n * listener fire resets the quiet-window timer; no fires for\n * `drainQuietMs` means the loop has gone idle and async effects (if\n * any) have either completed or are persistent\n * (websocket/interval/storageWatch).\n */\n subscribe(listener: () => void): () => void\n getMsgAnnotations(): Record<string, MessageAnnotations> | null\n /**\n * Snapshot and clear the drain-error buffer. The agent factory\n * installs persistent `window.error` / `unhandledrejection`\n * listeners that accumulate into this buffer; calling this at the\n * start of a drain discards stale errors from prior windows, and\n * calling it at the end yields just the errors that fired during\n * this drain. Optional — when omitted (e.g., Node test harness\n * without `window`), the drain envelope records an empty array.\n */\n getAndClearDrainErrors?: () => LapDrainMeta['errors']\n /** Called when @requiresConfirm; caller stores a ConfirmEntry in state. */\n proposeConfirm(entry: {\n id: string\n variant: string\n payload: unknown\n intent: string\n reason: string | null\n proposedAt: number\n status: 'pending'\n }): void\n}\n\nconst DEFAULT_QUIET_MS = 100\nconst DEFAULT_TIMEOUT_MS = 5_000\n\nexport async function handleSendMessage(\n host: SendMessageHost,\n args: SendMessageArgs,\n): Promise<LapMessageResponse> {\n if (!args.msg || typeof args.msg.type !== 'string') {\n return { status: 'rejected', reason: 'invalid' }\n }\n const annotations = host.getMsgAnnotations() ?? {}\n const ann = annotations[args.msg.type]\n\n // If annotations map is non-empty and this variant isn't in it, it's an\n // unknown msg type that the app never declared — reject early so the\n // browser never dispatches an unrecognised variant into update().\n const hasAnnotations = Object.keys(annotations).length > 0\n if (hasAnnotations && !ann) {\n return { status: 'rejected', reason: 'invalid', detail: `unknown variant: ${args.msg.type}` }\n }\n\n if (ann?.dispatchMode === 'human-only') {\n return { status: 'rejected', reason: 'human-only' }\n }\n\n // Schema validation: when the compiler emitted a `__msgSchema`,\n // check the payload against this variant's field shape before\n // dispatch. Catches the everyday agent bug — missing required\n // field, wrong enum value, missing discriminant on a tagged union,\n // typo in a key name — early, with structured errors the LLM can\n // correct from in one round trip. Reducers stay the last line of\n // defense; this is the first.\n const schema = host.getMsgSchema?.() ?? null\n const validation = validatePayload(args.msg, schema)\n if (!validation.ok) {\n return {\n status: 'rejected',\n reason: 'invalid',\n detail: validation.errors.map((e) => `${e.path}: ${e.message}`).join('; '),\n }\n }\n\n if (ann?.requiresConfirm) {\n const id = randomUUID()\n const { type: _type, ...payload } = args.msg\n host.proposeConfirm({\n id,\n variant: args.msg.type,\n payload,\n intent: ann?.intent ?? args.msg.type,\n reason: args.reason ?? null,\n proposedAt: Date.now(),\n status: 'pending',\n })\n return { status: 'pending-confirmation', confirmId: id }\n }\n\n const waitFor = args.waitFor ?? 'drained'\n const quietMs = Math.max(0, args.drainQuietMs ?? DEFAULT_QUIET_MS)\n const capMs = Math.max(0, args.timeoutMs ?? DEFAULT_TIMEOUT_MS)\n\n // Snapshot pre-dispatch state for diffing. The host's `getState`\n // returns a reference; capturing it here keeps a pre-mutation\n // pointer even after `host.send` triggers reducer-driven state\n // replacement (state itself is immutable per LLui's TEA contract,\n // so the reference stays valid).\n const prevState = host.getState()\n\n const includeState = args.includeState === true\n\n if (waitFor === 'none') {\n safeSend(host, args.msg, [])\n return dispatched(host, emptyDrain(), prevState, includeState)\n }\n\n if (waitFor === 'idle') {\n const dispatchErrors: LapDrainMeta['errors'] = []\n safeSendAndFlush(host, args.msg, dispatchErrors)\n await Promise.resolve()\n return dispatched(\n host,\n { effectsObserved: 1, durationMs: 0, timedOut: false, errors: dispatchErrors },\n prevState,\n includeState,\n )\n }\n\n // waitFor === 'drained' — message-queue quiescence detection.\n // Clear any errors buffered before this call so `drain.errors`\n // attributes only to this window.\n host.getAndClearDrainErrors?.()\n\n const t0 = now()\n let observed = 0\n let wake: ((reason: 'msg' | 'timeout') => void) | null = null\n const unsub = host.subscribe(() => {\n observed++\n const w = wake\n wake = null\n w?.('msg')\n })\n // Synchronous throws during send/flush — captured here and folded\n // into drain.errors. Async post-flush errors come in via\n // `getAndClearDrainErrors` (effect handler crashes, async rejections\n // observed by the runtime) and are merged at response time.\n const dispatchErrors: LapDrainMeta['errors'] = []\n try {\n safeSendAndFlush(host, args.msg, dispatchErrors)\n\n while (true) {\n const elapsed = now() - t0\n if (elapsed >= capMs) {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: elapsed,\n timedOut: true,\n errors: mergeDrainErrors(dispatchErrors, host.getAndClearDrainErrors?.()),\n },\n prevState,\n includeState,\n )\n }\n const budget = Math.min(quietMs, capMs - elapsed)\n // When the cap is within `quietMs` of `elapsed`, the quiet\n // window is truncated. In that case a timeout resolution does\n // NOT mean we detected quiescence — it means the cap cut the\n // window short. Only a full-length quiet window that elapses\n // without a new commit counts as real idle.\n const fullQuiet = budget >= quietMs\n const reason = await awaitQuietOrMsg(budget, (resolve) => {\n wake = resolve\n })\n if (reason === 'timeout') {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: now() - t0,\n timedOut: !fullQuiet,\n errors: mergeDrainErrors(dispatchErrors, host.getAndClearDrainErrors?.()),\n },\n prevState,\n includeState,\n )\n }\n // A commit fired during the wait — flush any queued follow-ups so\n // effects dispatched by that cycle run before we re-check.\n try {\n host.flush()\n } catch (e) {\n dispatchErrors.push(toDrainError(e))\n }\n }\n } finally {\n unsub()\n }\n}\n\n/**\n * Send a Msg and capture any synchronous throw into `errors` rather\n * than letting it propagate to the WS RPC layer. By the time `send`\n * has thrown, the reducer may have partially run (state can advance),\n * but bindings or downstream effects on the same commit may have\n * crashed mid-flight. From the agent's POV: the dispatch IS dispatched,\n * the state diff reflects what actually changed, and `drain.errors`\n * reports the in-flight crash. That's strictly more useful than HTTP\n * 500, which the agent reads as \"the dispatch never happened.\"\n */\nfunction safeSend(\n host: SendMessageHost,\n msg: { type: string; [k: string]: unknown },\n errors: LapDrainMeta['errors'],\n): void {\n try {\n host.send(msg)\n } catch (e) {\n errors.push(toDrainError(e))\n }\n}\n\nfunction safeSendAndFlush(\n host: SendMessageHost,\n msg: { type: string; [k: string]: unknown },\n errors: LapDrainMeta['errors'],\n): void {\n try {\n host.send(msg)\n } catch (e) {\n errors.push(toDrainError(e))\n return // can't flush something we never sent\n }\n try {\n host.flush()\n } catch (e) {\n errors.push(toDrainError(e))\n }\n}\n\nfunction toDrainError(e: unknown): LapDrainMeta['errors'][number] {\n if (e instanceof Error) {\n const stack = e.stack ? e.stack.split('\\n').slice(0, 8).join('\\n') : undefined\n return stack !== undefined\n ? { kind: 'error', message: `${e.name}: ${e.message}`, stack }\n : { kind: 'error', message: `${e.name}: ${e.message}` }\n }\n return { kind: 'error', message: String(e) }\n}\n\nfunction mergeDrainErrors(\n fromDispatch: LapDrainMeta['errors'],\n fromHost: LapDrainMeta['errors'] | undefined,\n): LapDrainMeta['errors'] {\n if (!fromHost || fromHost.length === 0) return fromDispatch\n if (fromDispatch.length === 0) return fromHost\n return [...fromDispatch, ...fromHost]\n}\n\nfunction dispatched(\n host: SendMessageHost,\n drain: LapDrainMeta,\n prevState: unknown,\n includeState: boolean,\n): LapMessageResponse {\n const stateAfter = host.getState()\n const base = {\n status: 'dispatched' as const,\n stateDiff: computeStateDiff(prevState, stateAfter),\n actions: handleListActions(host).actions,\n drain,\n }\n return includeState ? { ...base, stateAfter } : base\n}\n\nfunction emptyDrain(): LapDrainMeta {\n return { effectsObserved: 0, durationMs: 0, timedOut: false, errors: [] }\n}\n\nfunction awaitQuietOrMsg(\n budgetMs: number,\n registerWake: (resolve: (r: 'msg' | 'timeout') => void) => void,\n): Promise<'msg' | 'timeout'> {\n return new Promise<'msg' | 'timeout'>((resolve) => {\n let settled = false\n const guarded = (r: 'msg' | 'timeout') => {\n if (settled) return\n settled = true\n resolve(r)\n }\n registerWake(guarded)\n setTimeout(() => guarded('timeout'), budgetMs)\n })\n}\n\nfunction now(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now()\n}\n\n// Helper types for external callers that want the dispatched envelope.\nexport type DispatchedEnvelope = Extract<LapMessageResponse, { status: 'dispatched' }>\nexport type { LapActionsResponse }\n"]}
|
|
1
|
+
{"version":3,"file":"send-message.js","sourceRoot":"","sources":["../../../src/client/rpc/send-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from '../uuid.js'\nimport { handleListActions, type ListActionsHost } from './list-actions.js'\nimport { computeStateDiff } from '../../state-diff.js'\nimport { validatePayload } from './validate-payload.js'\nimport type {\n LapActionsResponse,\n LapDrainMeta,\n LapMessageResponse,\n MessageAnnotations,\n} from '../../protocol.js'\n\nexport type SendMessageArgs = {\n msg: { type: string; [k: string]: unknown }\n reason?: string\n /** See LapMessageRequest['waitFor']. Default: 'drained'. */\n waitFor?: 'drained' | 'idle' | 'none'\n /** See LapMessageRequest['drainQuietMs']. Default: 100ms. */\n drainQuietMs?: number\n /** See LapMessageRequest['timeoutMs']. Default: 5000ms. */\n timeoutMs?: number\n /** See LapMessageRequest['includeState']. Default: false. */\n includeState?: boolean\n}\n\nexport type SendMessageHost = ListActionsHost & {\n getState(): unknown\n send(msg: unknown): void\n flush(): void\n /**\n * Register a listener called after every update cycle commits —\n * backed by `AppHandle.subscribe`. Returns an unsubscribe function.\n * The drain loop uses this to detect message-queue quiescence: each\n * listener fire resets the quiet-window timer; no fires for\n * `drainQuietMs` means the loop has gone idle and async effects (if\n * any) have either completed or are persistent\n * (websocket/interval/storageWatch).\n */\n subscribe(listener: () => void): () => void\n getMsgAnnotations(): Record<string, MessageAnnotations> | null\n /**\n * Snapshot and clear the drain-error buffer. The agent factory\n * installs persistent `window.error` / `unhandledrejection`\n * listeners that accumulate into this buffer; calling this at the\n * start of a drain discards stale errors from prior windows, and\n * calling it at the end yields just the errors that fired during\n * this drain. Optional — when omitted (e.g., Node test harness\n * without `window`), the drain envelope records an empty array.\n */\n getAndClearDrainErrors?: () => LapDrainMeta['errors']\n /**\n * Optional dispatch-policy accessor — when defined, returns the\n * server's configured `'strict' | 'lenient'` policy for payload\n * validation. Strict mode rejects fields not in the schema and\n * emits warnings for `'unknown'`-typed fields the agent provided\n * values for. Default is lenient (omit / undefined).\n */\n getDispatchPolicy?: () => 'strict' | 'lenient'\n /** Called when @requiresConfirm; caller stores a ConfirmEntry in state. */\n proposeConfirm(entry: {\n id: string\n variant: string\n payload: unknown\n intent: string\n reason: string | null\n proposedAt: number\n status: 'pending'\n }): void\n}\n\nconst DEFAULT_QUIET_MS = 100\nconst DEFAULT_TIMEOUT_MS = 5_000\n\nexport async function handleSendMessage(\n host: SendMessageHost,\n args: SendMessageArgs,\n): Promise<LapMessageResponse> {\n if (!args.msg || typeof args.msg.type !== 'string') {\n return { status: 'rejected', reason: 'invalid' }\n }\n const annotations = host.getMsgAnnotations() ?? {}\n const ann = annotations[args.msg.type]\n\n // If annotations map is non-empty and this variant isn't in it, it's an\n // unknown msg type that the app never declared — reject early so the\n // browser never dispatches an unrecognised variant into update().\n const hasAnnotations = Object.keys(annotations).length > 0\n if (hasAnnotations && !ann) {\n return { status: 'rejected', reason: 'invalid', detail: `unknown variant: ${args.msg.type}` }\n }\n\n if (ann?.dispatchMode === 'human-only') {\n return { status: 'rejected', reason: 'human-only' }\n }\n\n // Schema validation: when the compiler emitted a `__msgSchema`,\n // check the payload against this variant's field shape before\n // dispatch. Catches the everyday agent bug — missing required\n // field, wrong enum value, missing discriminant on a tagged union,\n // typo in a key name — early, with structured errors the LLM can\n // correct from in one round trip. Reducers stay the last line of\n // defense; this is the first.\n const schema = host.getMsgSchema?.() ?? null\n const policy = host.getDispatchPolicy?.() ?? 'lenient'\n const validation = validatePayload(args.msg, schema, { policy })\n if (!validation.ok) {\n return {\n status: 'rejected',\n reason: 'invalid',\n detail: validation.errors.map((e) => `${e.path}: ${e.message}`).join('; '),\n }\n }\n // Warnings from the validator (strict-mode `untyped-field` flags etc.)\n // ride along to `drain.warnings` so the agent sees them on the\n // dispatched envelope. Lenient mode never emits warnings; this is\n // a no-op array for the default path.\n const validationWarnings = validation.warnings ?? []\n\n if (ann?.requiresConfirm) {\n const id = randomUUID()\n const { type: _type, ...payload } = args.msg\n host.proposeConfirm({\n id,\n variant: args.msg.type,\n payload,\n intent: ann?.intent ?? args.msg.type,\n reason: args.reason ?? null,\n proposedAt: Date.now(),\n status: 'pending',\n })\n return { status: 'pending-confirmation', confirmId: id }\n }\n\n const waitFor = args.waitFor ?? 'drained'\n const quietMs = Math.max(0, args.drainQuietMs ?? DEFAULT_QUIET_MS)\n const capMs = Math.max(0, args.timeoutMs ?? DEFAULT_TIMEOUT_MS)\n\n // Snapshot pre-dispatch state for diffing. The host's `getState`\n // returns a reference; capturing it here keeps a pre-mutation\n // pointer even after `host.send` triggers reducer-driven state\n // replacement (state itself is immutable per LLui's TEA contract,\n // so the reference stays valid).\n const prevState = host.getState()\n\n const includeState = args.includeState === true\n\n if (waitFor === 'none') {\n safeSend(host, args.msg, [])\n return dispatched(host, emptyDrain(), prevState, includeState, validationWarnings)\n }\n\n if (waitFor === 'idle') {\n const dispatchErrors: LapDrainMeta['errors'] = []\n safeSendAndFlush(host, args.msg, dispatchErrors)\n await Promise.resolve()\n return dispatched(\n host,\n { effectsObserved: 1, durationMs: 0, timedOut: false, errors: dispatchErrors },\n prevState,\n includeState,\n validationWarnings,\n )\n }\n\n // waitFor === 'drained' — message-queue quiescence detection.\n // Clear any errors buffered before this call so `drain.errors`\n // attributes only to this window.\n host.getAndClearDrainErrors?.()\n\n const t0 = now()\n let observed = 0\n let wake: ((reason: 'msg' | 'timeout') => void) | null = null\n const unsub = host.subscribe(() => {\n observed++\n const w = wake\n wake = null\n w?.('msg')\n })\n // Synchronous throws during send/flush — captured here and folded\n // into drain.errors. Async post-flush errors come in via\n // `getAndClearDrainErrors` (effect handler crashes, async rejections\n // observed by the runtime) and are merged at response time.\n const dispatchErrors: LapDrainMeta['errors'] = []\n try {\n safeSendAndFlush(host, args.msg, dispatchErrors)\n\n while (true) {\n const elapsed = now() - t0\n if (elapsed >= capMs) {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: elapsed,\n timedOut: true,\n errors: mergeDrainErrors(dispatchErrors, host.getAndClearDrainErrors?.()),\n },\n prevState,\n includeState,\n validationWarnings,\n )\n }\n const budget = Math.min(quietMs, capMs - elapsed)\n // When the cap is within `quietMs` of `elapsed`, the quiet\n // window is truncated. In that case a timeout resolution does\n // NOT mean we detected quiescence — it means the cap cut the\n // window short. Only a full-length quiet window that elapses\n // without a new commit counts as real idle.\n const fullQuiet = budget >= quietMs\n const reason = await awaitQuietOrMsg(budget, (resolve) => {\n wake = resolve\n })\n if (reason === 'timeout') {\n return dispatched(\n host,\n {\n effectsObserved: observed,\n durationMs: now() - t0,\n timedOut: !fullQuiet,\n errors: mergeDrainErrors(dispatchErrors, host.getAndClearDrainErrors?.()),\n },\n prevState,\n includeState,\n validationWarnings,\n )\n }\n // A commit fired during the wait — flush any queued follow-ups so\n // effects dispatched by that cycle run before we re-check.\n try {\n host.flush()\n } catch (e) {\n dispatchErrors.push(toDrainError(e))\n }\n }\n } finally {\n unsub()\n }\n}\n\n/**\n * Send a Msg and capture any synchronous throw into `errors` rather\n * than letting it propagate to the WS RPC layer. By the time `send`\n * has thrown, the reducer may have partially run (state can advance),\n * but bindings or downstream effects on the same commit may have\n * crashed mid-flight. From the agent's POV: the dispatch IS dispatched,\n * the state diff reflects what actually changed, and `drain.errors`\n * reports the in-flight crash. That's strictly more useful than HTTP\n * 500, which the agent reads as \"the dispatch never happened.\"\n */\nfunction safeSend(\n host: SendMessageHost,\n msg: { type: string; [k: string]: unknown },\n errors: LapDrainMeta['errors'],\n): void {\n try {\n host.send(msg)\n } catch (e) {\n errors.push(toDrainError(e))\n }\n}\n\nfunction safeSendAndFlush(\n host: SendMessageHost,\n msg: { type: string; [k: string]: unknown },\n errors: LapDrainMeta['errors'],\n): void {\n try {\n host.send(msg)\n } catch (e) {\n errors.push(toDrainError(e))\n return // can't flush something we never sent\n }\n try {\n host.flush()\n } catch (e) {\n errors.push(toDrainError(e))\n }\n}\n\nfunction toDrainError(e: unknown): LapDrainMeta['errors'][number] {\n if (e instanceof Error) {\n const stack = e.stack ? e.stack.split('\\n').slice(0, 8).join('\\n') : undefined\n return stack !== undefined\n ? { kind: 'error', message: `${e.name}: ${e.message}`, stack }\n : { kind: 'error', message: `${e.name}: ${e.message}` }\n }\n return { kind: 'error', message: String(e) }\n}\n\nfunction mergeDrainErrors(\n fromDispatch: LapDrainMeta['errors'],\n fromHost: LapDrainMeta['errors'] | undefined,\n): LapDrainMeta['errors'] {\n if (!fromHost || fromHost.length === 0) return fromDispatch\n if (fromDispatch.length === 0) return fromHost\n return [...fromDispatch, ...fromHost]\n}\n\nfunction dispatched(\n host: SendMessageHost,\n drain: LapDrainMeta,\n prevState: unknown,\n includeState: boolean,\n validationWarnings: NonNullable<LapDrainMeta['warnings']> = [],\n): LapMessageResponse {\n const stateAfter = host.getState()\n const drainWithWarnings: LapDrainMeta =\n validationWarnings.length === 0 ? drain : { ...drain, warnings: validationWarnings }\n const base = {\n status: 'dispatched' as const,\n stateDiff: computeStateDiff(prevState, stateAfter),\n actions: handleListActions(host).actions,\n drain: drainWithWarnings,\n }\n return includeState ? { ...base, stateAfter } : base\n}\n\nfunction emptyDrain(): LapDrainMeta {\n return { effectsObserved: 0, durationMs: 0, timedOut: false, errors: [] }\n}\n\nfunction awaitQuietOrMsg(\n budgetMs: number,\n registerWake: (resolve: (r: 'msg' | 'timeout') => void) => void,\n): Promise<'msg' | 'timeout'> {\n return new Promise<'msg' | 'timeout'>((resolve) => {\n let settled = false\n const guarded = (r: 'msg' | 'timeout') => {\n if (settled) return\n settled = true\n resolve(r)\n }\n registerWake(guarded)\n setTimeout(() => guarded('timeout'), budgetMs)\n })\n}\n\nfunction now(): number {\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now()\n}\n\n// Helper types for external callers that want the dispatched envelope.\nexport type DispatchedEnvelope = Extract<LapMessageResponse, { status: 'dispatched' }>\nexport type { LapActionsResponse }\n"]}
|
|
@@ -84,6 +84,25 @@ export type WouldDispatchResult = {
|
|
|
84
84
|
status: 'rejected';
|
|
85
85
|
reason: 'schema-mismatch';
|
|
86
86
|
errors: ValidationError[];
|
|
87
|
+
} | {
|
|
88
|
+
/**
|
|
89
|
+
* The reducer threw while running against the candidate Msg.
|
|
90
|
+
* State is poisoned (or the reducer has a latent bug); a real
|
|
91
|
+
* `send_message` would land state change + an error in
|
|
92
|
+
* `drain.errors`. `would_dispatch` mirrors that contract: the
|
|
93
|
+
* "diff" is empty (we couldn't compute it), and the throw text
|
|
94
|
+
* is surfaced so the agent knows to back off rather than
|
|
95
|
+
* retrying the same payload.
|
|
96
|
+
*
|
|
97
|
+
* Distinct from `'rejected'` because the agent learned something
|
|
98
|
+
* different: the reducer DOES accept this Msg shape but errors
|
|
99
|
+
* downstream. Often that means earlier state needs fixing first
|
|
100
|
+
* (a previously-dispatched bad Msg poisoned a derived path),
|
|
101
|
+
* not that this candidate is malformed.
|
|
102
|
+
*/
|
|
103
|
+
status: 'reducer-threw';
|
|
104
|
+
message: string;
|
|
105
|
+
stack?: string;
|
|
87
106
|
};
|
|
88
107
|
export declare function handleWouldDispatch(host: WouldDispatchHost, args: WouldDispatchArgs): WouldDispatchResult;
|
|
89
108
|
//# sourceMappingURL=would-dispatch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"would-dispatch.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,IAAI,OAAO,CAAA;IACnB;;;;;;;OAOG;IACH,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,EAAE,MAAM,CAAA;QACZ,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KACrB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;IACjD;;;;;;;;OAQG;IACH,YAAY,CAAC,IAAI,cAAc,GAAG,IAAI,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC3B;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,mEAAmE;IACnE,SAAS,EAAE,SAAS,CAAA;IACpB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB,GACD;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1E;IACE;;;;;;OAMG;IACH,MAAM,EAAE,UAAU,CAAA;IAClB,MAAM,EAAE,iBAAiB,CAAA;IACzB,MAAM,EAAE,eAAe,EAAE,CAAA;CAC1B,CAAA;AAEL,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,iBAAiB,GACtB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"would-dispatch.d.ts","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,IAAI,OAAO,CAAA;IACnB;;;;;;;OAOG;IACH,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,EAAE,MAAM,CAAA;QACZ,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KACrB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;IACjD;;;;;;;;OAQG;IACH,YAAY,CAAC,IAAI,cAAc,GAAG,IAAI,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC3B;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,mEAAmE;IACnE,SAAS,EAAE,SAAS,CAAA;IACpB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB,GACD;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1E;IACE;;;;;;OAMG;IACH,MAAM,EAAE,UAAU,CAAA;IAClB,MAAM,EAAE,iBAAiB,CAAA;IACzB,MAAM,EAAE,eAAe,EAAE,CAAA;CAC1B,GACD;IACE;;;;;;;;;;;;;;OAcG;IACH,MAAM,EAAE,eAAe,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAEL,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,iBAAiB,GACtB,mBAAmB,CAyCrB"}
|
|
@@ -13,7 +13,20 @@ export function handleWouldDispatch(host, args) {
|
|
|
13
13
|
if (!validation.ok) {
|
|
14
14
|
return { status: 'rejected', reason: 'schema-mismatch', errors: validation.errors };
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
let result;
|
|
17
|
+
try {
|
|
18
|
+
result = host.runReducer(args.msg);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
// The reducer threw — same Phase-5 contract as `send_message`:
|
|
22
|
+
// surface the throw as structured data instead of letting it
|
|
23
|
+
// become an HTTP 500 the agent reads as "transport failure."
|
|
24
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
25
|
+
const stack = err.stack ? err.stack.split('\n').slice(0, 8).join('\n') : undefined;
|
|
26
|
+
return stack !== undefined
|
|
27
|
+
? { status: 'reducer-threw', message: `${err.name}: ${err.message}`, stack }
|
|
28
|
+
: { status: 'reducer-threw', message: `${err.name}: ${err.message}` };
|
|
29
|
+
}
|
|
17
30
|
if (result === null) {
|
|
18
31
|
return {
|
|
19
32
|
status: 'rejected',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"would-dispatch.js","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,EAAE,eAAe,EAAwB,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"would-dispatch.js","sourceRoot":"","sources":["../../../src/client/rpc/would-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,EAAE,eAAe,EAAwB,MAAM,uBAAuB,CAAA;AAuG7E,MAAM,UAAU,mBAAmB,CACjC,IAAuB,EACvB,IAAuB;IAEvB,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACnD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAA;IACvF,CAAC;IAED,oEAAoE;IACpE,mEAAmE;IACnE,+DAA+D;IAC/D,cAAc;IACd,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,IAAI,CAAA;IAC5C,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACpD,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAA;IACrF,CAAC;IAED,IAAI,MAAqD,CAAA;IACzD,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,+DAA+D;QAC/D,6DAA6D;QAC7D,6DAA6D;QAC7D,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;QACzD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAClF,OAAO,KAAK,KAAK,SAAS;YACxB,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE;YAC5E,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,CAAA;IACzE,CAAC;IACD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO;YACL,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,6DAA6D;SACtE,CAAA;IACH,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjC,OAAO;QACL,MAAM,EAAE,WAAW;QACnB,SAAS,EAAE,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC;QACpD,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAA;AACH,CAAC","sourcesContent":["import { computeStateDiff } from '../../state-diff.js'\nimport type { StateDiff } from '../../state-diff.js'\nimport { validatePayload, type ValidationError } from './validate-payload.js'\nimport type { MsgSchemaShape } from '../factory.js'\n\n/**\n * Predict the result of dispatching `msg` without actually applying\n * it. Runs the reducer in isolation against the current state,\n * returns the would-be diff and the would-fire effects, but doesn't\n * commit or run anything. Lets the agent reason about a candidate\n * action before pulling the trigger:\n *\n * - \"If I dispatch X, what will change?\" — read `stateDiff`.\n * - \"Will it fire effects? Which ones?\" — read `effects`.\n * - \"Should I batch?\" — predict each, see whether the diffs\n * compose without conflict.\n *\n * The contract is bounded by TEA's purity assumption: the reducer\n * must be a pure function `(state, msg) → [newState, effects]`. LLui\n * reducers are pure by convention (the runtime never re-runs them\n * speculatively for any other reason, so impurity would already be\n * a latent bug). Apps whose reducers branch on `Date.now()` or read\n * `localStorage` will see prediction drift from real dispatch by\n * exactly that amount of impurity — usually negligible, sometimes\n * surprising; document at the call site.\n *\n * **No effects fire.** The returned `effects` array is the literal\n * effect descriptors the reducer produced — what `onEffect` would\n * have received. The agent reads them; the runtime ignores them.\n * This is the entire reason the tool exists separately from\n * `send_message`: a real dispatch hits the cloud / analytics /\n * persistence; a predicted one doesn't.\n */\nexport type WouldDispatchHost = {\n getState(): unknown\n /**\n * Run the reducer in isolation. `[newState, effects]` shape.\n * Implemented by the AppHandle as a thin wrapper around\n * `inst.def.update(state, msg)` — no flush, no subscribe, no\n * commit. Implementations that can't run the reducer (e.g.\n * test harnesses with no live instance) return null and the\n * tool reports unsupported.\n */\n runReducer(msg: {\n type: string\n [k: string]: unknown\n }): { state: unknown; effects: unknown[] } | null\n /**\n * The compiler-emitted Msg schema, when available. Used to\n * validate the candidate payload structurally before running the\n * reducer — wrong enum values, missing discriminants, primitive\n * type mismatches surface as structured errors the agent can fix\n * in one round trip instead of probing field by field. Hosts\n * without schema metadata return null and the validator skips,\n * keeping the tool permissive (the reducer still runs).\n */\n getMsgSchema?(): MsgSchemaShape | null\n}\n\nexport type WouldDispatchArgs = {\n msg: { type: string; [k: string]: unknown }\n}\n\nexport type WouldDispatchResult =\n | {\n status: 'predicted'\n /** Diff from current state to the predicted post-reducer state. */\n stateDiff: StateDiff\n /** Effects the reducer would emit. Order matches the reducer's return. */\n effects: unknown[]\n }\n | { status: 'rejected'; reason: 'invalid' | 'unsupported'; detail?: string }\n | {\n /**\n * The candidate Msg failed schema validation BEFORE the reducer\n * ran. `errors` lists every structural mismatch with a path-\n * keyed description. The agent reads this as \"fix these fields\n * and retry\" — no reducer side-effects to roll back, no state\n * change to predict around.\n */\n status: 'rejected'\n reason: 'schema-mismatch'\n errors: ValidationError[]\n }\n | {\n /**\n * The reducer threw while running against the candidate Msg.\n * State is poisoned (or the reducer has a latent bug); a real\n * `send_message` would land state change + an error in\n * `drain.errors`. `would_dispatch` mirrors that contract: the\n * \"diff\" is empty (we couldn't compute it), and the throw text\n * is surfaced so the agent knows to back off rather than\n * retrying the same payload.\n *\n * Distinct from `'rejected'` because the agent learned something\n * different: the reducer DOES accept this Msg shape but errors\n * downstream. Often that means earlier state needs fixing first\n * (a previously-dispatched bad Msg poisoned a derived path),\n * not that this candidate is malformed.\n */\n status: 'reducer-threw'\n message: string\n stack?: string\n }\n\nexport function handleWouldDispatch(\n host: WouldDispatchHost,\n args: WouldDispatchArgs,\n): WouldDispatchResult {\n if (!args.msg || typeof args.msg.type !== 'string') {\n return { status: 'rejected', reason: 'invalid', detail: 'msg.type must be a string' }\n }\n\n // Schema-driven preflight. Permissive when no schema is available —\n // the reducer still validates semantically. With a schema, surface\n // structured errors so the agent doesn't iterate one field per\n // round trip.\n const schema = host.getMsgSchema?.() ?? null\n const validation = validatePayload(args.msg, schema)\n if (!validation.ok) {\n return { status: 'rejected', reason: 'schema-mismatch', errors: validation.errors }\n }\n\n let result: { state: unknown; effects: unknown[] } | null\n try {\n result = host.runReducer(args.msg)\n } catch (e) {\n // The reducer threw — same Phase-5 contract as `send_message`:\n // surface the throw as structured data instead of letting it\n // become an HTTP 500 the agent reads as \"transport failure.\"\n const err = e instanceof Error ? e : new Error(String(e))\n const stack = err.stack ? err.stack.split('\\n').slice(0, 8).join('\\n') : undefined\n return stack !== undefined\n ? { status: 'reducer-threw', message: `${err.name}: ${err.message}`, stack }\n : { status: 'reducer-threw', message: `${err.name}: ${err.message}` }\n }\n if (result === null) {\n return {\n status: 'rejected',\n reason: 'unsupported',\n detail: 'host does not expose a reducer (no live component instance)',\n }\n }\n const prevState = host.getState()\n return {\n status: 'predicted',\n stateDiff: computeStateDiff(prevState, result.state),\n effects: result.effects,\n }\n}\n"]}
|
|
@@ -6,7 +6,7 @@ import { type SendMessageHost } from './rpc/send-message.js';
|
|
|
6
6
|
import { type ListActionsHost } from './rpc/list-actions.js';
|
|
7
7
|
import { type QueryDomHost } from './rpc/query-dom.js';
|
|
8
8
|
import { type DescribeVisibleHost } from './rpc/describe-visible-content.js';
|
|
9
|
-
import { type DescribeContextHost } from './rpc/describe-context.js';
|
|
9
|
+
import { type DescribeContextHost, type LastDispatchOutcome } from './rpc/describe-context.js';
|
|
10
10
|
import { type ObserveHost } from './rpc/observe.js';
|
|
11
11
|
export interface WsLike {
|
|
12
12
|
send(data: string): void;
|
|
@@ -39,6 +39,15 @@ export type WsClientOpts = {
|
|
|
39
39
|
* server regardless.
|
|
40
40
|
*/
|
|
41
41
|
onLogEntry?: (entry: LogEntry) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Called with the outcome of every `send_message` rpc — `dispatched`
|
|
44
|
+
* (with optional errors / warnings), `rejected` (with errors), or
|
|
45
|
+
* `reducer-threw`. The factory uses this to maintain a "last
|
|
46
|
+
* outcome" snapshot that `describe_context` injects as a synthetic
|
|
47
|
+
* hint, so apps don't have to maintain their own
|
|
48
|
+
* `lastDispatchError` state field.
|
|
49
|
+
*/
|
|
50
|
+
onDispatchOutcome?: (outcome: LastDispatchOutcome | null) => void;
|
|
42
51
|
};
|
|
43
52
|
/**
|
|
44
53
|
* Wires up a WebSocket to serve rpc requests from the server. See spec §9.4.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-client.d.ts","sourceRoot":"","sources":["../../src/client/ws-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,UAAU,EACV,QAAQ,EAGT,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC5E,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,
|
|
1
|
+
{"version":3,"file":"ws-client.d.ts","sourceRoot":"","sources":["../../src/client/ws-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,UAAU,EACV,QAAQ,EAGT,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC5E,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;IACb,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAA;IACxF,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC/D;AAED,MAAM,MAAM,QAAQ,GAAG,YAAY,GACjC,cAAc,GACd,eAAe,GACf,eAAe,GACf,YAAY,GACZ,mBAAmB,GACnB,mBAAmB,GACnB,WAAW,GACX,iBAAiB,CAAA;AAEnB,MAAM,MAAM,YAAY,GAAG,MAAM,UAAU,CAAA;AAE3C,MAAM,MAAM,QAAQ,GAAG;IACrB,kFAAkF;IAClF,cAAc,CACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,GAAG,gBAAgB,EACvC,UAAU,CAAC,EAAE,OAAO,GACnB,IAAI,CAAA;IACP,kFAAkF;IAClF,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAAA;IACxD,kGAAkG;IAClG,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;IACpC,gCAAgC;IAChC,KAAK,IAAI,IAAI,CAAA;CACd,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;IACtC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,KAAK,IAAI,CAAA;CAClE,CAAA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,EACnB,IAAI,GAAE,YAAiB,GACtB,QAAQ,CAqGV"}
|
package/dist/client/ws-client.js
CHANGED
|
@@ -5,7 +5,7 @@ import { handleSendMessage } from './rpc/send-message.js';
|
|
|
5
5
|
import { handleListActions } from './rpc/list-actions.js';
|
|
6
6
|
import { handleQueryDom } from './rpc/query-dom.js';
|
|
7
7
|
import { handleDescribeVisibleContent, } from './rpc/describe-visible-content.js';
|
|
8
|
-
import { handleDescribeContext } from './rpc/describe-context.js';
|
|
8
|
+
import { handleDescribeContext, } from './rpc/describe-context.js';
|
|
9
9
|
import { handleObserve } from './rpc/observe.js';
|
|
10
10
|
/**
|
|
11
11
|
* Wires up a WebSocket to serve rpc requests from the server. See spec §9.4.
|
|
@@ -80,6 +80,14 @@ export function attachWsClient(ws, rpc, hello, opts = {}) {
|
|
|
80
80
|
if (frame.tool === 'send_message' && rpcErr === null && isDispatchedResult(result)) {
|
|
81
81
|
logEntry.stateDiff = result.stateDiff;
|
|
82
82
|
}
|
|
83
|
+
if (frame.tool === 'send_message') {
|
|
84
|
+
// Capture the outcome so `describe_context` can surface it as a
|
|
85
|
+
// synthetic "Last dispatch …" hint. Apps used to roll their
|
|
86
|
+
// own `lastDispatchError` field; the framework owns it now.
|
|
87
|
+
const outcome = extractDispatchOutcome(frame.args, result, rpcErr);
|
|
88
|
+
if (outcome !== null)
|
|
89
|
+
opts.onDispatchOutcome?.(outcome);
|
|
90
|
+
}
|
|
83
91
|
opts.onLogEntry?.(logEntry);
|
|
84
92
|
ws.send(JSON.stringify({ t: 'log-append', entry: logEntry }));
|
|
85
93
|
});
|
|
@@ -169,6 +177,65 @@ function isDispatchedResult(result) {
|
|
|
169
177
|
result.status === 'dispatched' &&
|
|
170
178
|
Array.isArray(result.stateDiff));
|
|
171
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Build a `LastDispatchOutcome` snapshot from the send_message rpc's
|
|
182
|
+
* result/error pair. Returns null when the args don't carry a variant
|
|
183
|
+
* name (malformed dispatch — would never have been logged anyway).
|
|
184
|
+
*
|
|
185
|
+
* Status mapping:
|
|
186
|
+
* - { status: 'dispatched' } → 'dispatched' (with errors/warnings)
|
|
187
|
+
* - { status: 'rejected' } → 'rejected'
|
|
188
|
+
* - rpcErr (rpc handler threw, including reducer-throw on the predict path) → 'reducer-threw'
|
|
189
|
+
*
|
|
190
|
+
* The outcome is consumed by `describe_context` to prepend a synthetic
|
|
191
|
+
* "Last dispatch …" hint. Clean dispatched outcomes (no errors, no
|
|
192
|
+
* warnings) still get tracked — `formatLastOutcomeHint` decides whether
|
|
193
|
+
* to surface them.
|
|
194
|
+
*/
|
|
195
|
+
function extractDispatchOutcome(args, result, rpcErr) {
|
|
196
|
+
const variant = extractVariant('send_message', args);
|
|
197
|
+
if (variant === undefined)
|
|
198
|
+
return null;
|
|
199
|
+
const at = Date.now();
|
|
200
|
+
if (rpcErr !== null) {
|
|
201
|
+
// The rpc handler itself threw — most often this is a reducer
|
|
202
|
+
// throw that bubbled past the catch in send-message.ts (e.g.
|
|
203
|
+
// would_dispatch's reducer threw in a non-send-message tool that
|
|
204
|
+
// shares the path). Treat as `reducer-threw` so the agent reads
|
|
205
|
+
// "state may be partially advanced; observe before retrying."
|
|
206
|
+
return {
|
|
207
|
+
variant,
|
|
208
|
+
status: 'reducer-threw',
|
|
209
|
+
errors: [{ message: rpcErr.detail ?? rpcErr.code ?? 'rpc handler threw' }],
|
|
210
|
+
at,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (result === null || typeof result !== 'object')
|
|
214
|
+
return null;
|
|
215
|
+
const r = result;
|
|
216
|
+
if (r.status === 'dispatched') {
|
|
217
|
+
const errors = r.drain?.errors ?? [];
|
|
218
|
+
const warnings = r.drain?.warnings ?? [];
|
|
219
|
+
const out = { variant, status: 'dispatched', at };
|
|
220
|
+
if (errors.length > 0)
|
|
221
|
+
out.errors = errors;
|
|
222
|
+
if (warnings.length > 0)
|
|
223
|
+
out.warnings = warnings;
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
if (r.status === 'rejected') {
|
|
227
|
+
const detail = r.detail ?? 'rejected';
|
|
228
|
+
return {
|
|
229
|
+
variant,
|
|
230
|
+
status: 'rejected',
|
|
231
|
+
errors: [{ message: detail }],
|
|
232
|
+
at,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
// Other statuses (`pending-confirmation`, etc.) — don't update the
|
|
236
|
+
// last-outcome cache. The dispatch hasn't really concluded yet.
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
172
239
|
function extractVariant(tool, args) {
|
|
173
240
|
if (tool === 'send_message') {
|
|
174
241
|
const a = args;
|