@parlor/agent-host 0.2.0 → 0.3.0
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/acp.d.ts +21 -0
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +138 -12
- package/dist/acp.js.map +1 -1
- package/dist/agent-options.d.ts +17 -0
- package/dist/agent-options.d.ts.map +1 -0
- package/dist/agent-options.js +73 -0
- package/dist/agent-options.js.map +1 -0
- package/dist/agent-tools.d.ts +30 -0
- package/dist/agent-tools.d.ts.map +1 -0
- package/dist/agent-tools.js +71 -0
- package/dist/agent-tools.js.map +1 -0
- package/dist/control.d.ts +15 -0
- package/dist/control.d.ts.map +1 -1
- package/dist/control.js.map +1 -1
- package/dist/daemon.d.ts +10 -2
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +149 -14
- package/dist/daemon.js.map +1 -1
- package/dist/fold.d.ts.map +1 -1
- package/dist/fold.js +2 -0
- package/dist/fold.js.map +1 -1
- package/dist/grants.d.ts +9 -0
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js.map +1 -1
- package/dist/learned.d.ts +23 -0
- package/dist/learned.d.ts.map +1 -0
- package/dist/learned.js +36 -0
- package/dist/learned.js.map +1 -0
- package/dist/main.js +105 -9
- package/dist/main.js.map +1 -1
- package/dist/mcp-proxy.d.ts +26 -3
- package/dist/mcp-proxy.d.ts.map +1 -1
- package/dist/mcp-proxy.js +180 -9
- package/dist/mcp-proxy.js.map +1 -1
- package/dist/pairing.d.ts +2 -0
- package/dist/pairing.d.ts.map +1 -1
- package/dist/pairing.js +25 -3
- package/dist/pairing.js.map +1 -1
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +1 -0
- package/dist/paths.js.map +1 -1
- package/dist/policy.d.ts +12 -2
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +13 -3
- package/dist/policy.js.map +1 -1
- package/dist/prompter.d.ts +5 -0
- package/dist/prompter.d.ts.map +1 -1
- package/dist/prompter.js +15 -0
- package/dist/prompter.js.map +1 -1
- package/dist/spec-kit.d.ts +2 -1
- package/dist/spec-kit.d.ts.map +1 -1
- package/dist/spec-kit.js +6 -1
- package/dist/spec-kit.js.map +1 -1
- package/dist/testing/fake-agent.d.ts +12 -0
- package/dist/testing/fake-agent.d.ts.map +1 -1
- package/dist/testing/fake-agent.js +11 -1
- package/dist/testing/fake-agent.js.map +1 -1
- package/dist/turn.d.ts +7 -1
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +16 -3
- package/dist/turn.js.map +1 -1
- package/package.json +9 -9
package/dist/mcp-proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-proxy.js","sourceRoot":"","sources":["../src/mcp-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,YAAY,EAA4B,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AA0BpC,MAAM,CAAC,MAAM,eAAe,GAC1B,4FAA4F,CAAC;AAE/F,MAAM,CAAC,MAAM,oBAAoB,GAC/B,sFAAsF,CAAC;AAEzF,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAC9C,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,YAAY,GAAG,CAAC,KAAK,CAAC;AAC5B,MAAM,YAAY,GAAG,CAAC,KAAK,CAAC;AAqB5B,SAAS,KAAK,CAAC,IAAY;IACzB,IAAI,CAAC;QACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACzE,CAAC,CAAE,KAAiB;YACpB,CAAC,CAAC,SAAS,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,EAAa,EAAE,KAAc,EAAE,EAAE,CAC/C,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAExD,MAAM,OAAO,GAAG,CAAC,EAAa,EAAE,IAAY,EAAE,OAAe,EAAE,EAAE,CAC/D,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;AAEnE,8FAA8F;AAC9F,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAC9B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAEhE,SAAS,YAAY,CAAC,IAAU,EAAE,OAAgB;IAChD,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACrF,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC;IACvB,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACxB,KAAK,YAAY;YACf,OAAO,IAAI,CAAC,WAAW,KAAK,SAAS;gBACnC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,eAAe,CAAC;gBAC5C,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACnC,KAAK,YAAY;YACf,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS;gBAC7B,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,eAAe,CAAC;gBAC5C,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,KAAK,YAAY;YACf,OAAO,MAAM,CAAC,EAAE,EAAE;gBAChB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;gBAClD,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;QACL;YACE,OAAO,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,cAAc,CAAC,IAAgC;IAC7D,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,IAAI,GAAa,EAAE,CAAC;IACxB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/B,OAAO,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACrD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;gBAChB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;oBAAE,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3C,IAAI,GAAG,EAAE,CAAC;YACZ,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3D,CAAC;YACD,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,OAAO,CAAC,MAAc;IAC7B,OAAO,MAAM,KAAK,GAAG;QACnB,CAAC,CAAC,+FAA+F;QACjG,CAAC,CAAC,yBAAyB,MAAM,GAAG,CAAC;AACzC,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,IAAU,EACV,KAAa,EACb,IAAY,EACZ,OAAgB,EAChB,KAA6B;IAE7B,MAAM,MAAM,GAAG,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;IAChF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC9B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,cAAc,EAAE;YAC5D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,MAAM,EAAE,qCAAqC;gBAC7C,cAAc,EAAE,kBAAkB;gBAClC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAClF;YACD,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QACzB,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC/E,IAAI,QAAQ,CAAC,IAAI;gBACf,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9E,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,MAAM,EAAE,OAAO,KAAK,KAAK;gBAAE,SAAS;YACxC,IAAI,MAAM,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,EAAE,CAAC;gBAC7B,QAAQ,GAAG,IAAI,CAAC;gBAChB,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;oBAC3D,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;oBACjC,MAAM,OAAO,GAAI,MAAM,CAAC,MAAiD;yBACtE,eAAe,CAAC;oBACnB,IAAI,OAAO,OAAO,KAAK,QAAQ;wBAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;gBAClE,CAAC;gBACD,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;oBACxD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;YAC/B,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC7B,KAAK,CACH,UAAU,CAAC,MAAM,CAAC,OAAO;gBACvB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,eAAe,CAAC;gBACpD,CAAC,CAAC,OAAO,CACL,OAAO,CAAC,EAAE,EACV,YAAY,EACZ,iCAAiC,MAAM,CAAC,KAAK,CAAC,EAAE,CACjD,CACN,CAAC;QACJ,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACjC,QAAQ,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtF,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,IAAU,EAAE,IAAY,EAAE,KAA6B;IACrE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,IAAI,MAAM;YAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,OAAO;IACT,CAAC;IACD,IACE,OAAO,CAAC,MAAM,KAAK,YAAY;QAC/B,IAAI,CAAC,KAAK,KAAK,SAAS;QACxB,OAAO,CAAC,EAAE,KAAK,SAAS,EACxB,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IACD,KAAK,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAgB,CAAC;IACzC,IAAI,MAA0B,CAAC;IAC/B,IAAI,UAA8B,CAAC;IAEnC,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE;QAChC,IAAI,IAAsB,CAAC;QAC3B,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE;YAC7B,IAAI,CAAC,MAAM,CAAC,SAAS;gBAAE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QACnD,CAAC,CAAC;QACF,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBAC1B,OAAO;YACT,CAAC;YACD,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YACjC,IAAI,IAAI;gBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;;gBAC9B,MAAM,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,IAAI;YACf,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7D,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChC,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAChC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1C,CAAC,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACzB,MAAM,GAAG,SAAS,CAAC;YACnB,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,SAAS;YACvB,IAAI,UAAU,KAAK,SAAS;gBAC1B,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACzD,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YACpD,MAAM,IAAI,GAAS;gBACjB,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxC,KAAK,EAAE,SAAS;gBAChB,eAAe,EAAE,SAAS;gBAC1B,WAAW,EAAE,SAAS;gBACtB,KAAK,EAAE,SAAS;gBAChB,QAAQ,EAAE,IAAI,GAAG,EAAE;gBACnB,OAAO,EAAE,IAAI,GAAG,EAAE;aACnB,CAAC;YACF,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC1B,MAAM,GAAG,GAAG,GAAG,EAAE;gBACf,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;gBACvB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,QAAQ;oBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;YAC7D,CAAC,CAAC;YACF,OAAO;gBACL,KAAK;gBACL,MAAM,EAAE;oBACN,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,UAAU;oBACnB,IAAI,EAAE,CAAC,WAAW,CAAC;oBACnB,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE;iBACtD;gBACD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;oBACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;gBAC3B,CAAC;gBACD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;oBACf,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;wBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;gBACzD,CAAC;gBACD,GAAG;gBACH,OAAO,EAAE,GAAG,EAAE;oBACZ,GAAG,EAAE,CAAC;oBACN,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACvB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO;wBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;gBACtD,CAAC;aACF,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,aAAa;YACjB,MAAM,OAAO,GAAG,MAAM,CAAC;YACvB,MAAM,GAAG,SAAS,CAAC;YACnB,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,UAAU;gBAAE,MAAM,EAAE,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,KAAK,CAAC,KAAK;YACT,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACrC,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,QAAQ;oBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;gBAC3D,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO;oBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACtD,CAAC;YACD,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,MAAM,CAAC;YACvB,MAAM,GAAG,SAAS,CAAC;YACnB,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACrE,IAAI,UAAU;oBAAE,MAAM,EAAE,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAsB;IAC7D,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,QAAQ,SAAS,eAAe,CAAC,CAAC;IACpF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAa,CAAC;IACxC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,CAAC,EAAa,EAAE,EAAE,CAC/B,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAC3C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;QACxB,KAAK,MAAM,EAAE,IAAI,UAAU;YAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACxC,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CACH,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;IAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACvB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,OAAO,EAAE,EAAE,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9D,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;QAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,EAAE,GAAG,OAAO,OAAO,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,IAAI,EAAE,KAAK,SAAS;gBAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAC5B,CAAC;aAAM,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,CAAC,EAAE,CAAC,CAAC;QACb,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,EAAE,CAAC;IACb,MAAM,MAAM,CAAC;AACf,CAAC","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { chmod, mkdir, rm } from \"node:fs/promises\";\nimport { connect, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname } from \"node:path\";\n\nimport type { Delegation } from \"@parlor/server\";\n\nimport type { StdioMcpServer } from \"./acp.js\";\nimport { onLines } from \"./lines.js\";\nimport { logEvent } from \"./log.js\";\n\n/**\n * The agent's MCP server for an app is this host: a stdio server (`parlor-host\n * mcp-proxy`) that connects back to the daemon's 0600 socket and names its session by a\n * nonce. The daemon forwards each message to `<app>/_parlor/mcp` with the current turn's\n * token, so no app credential is ever in the agent's config, logs or environment.\n */\nexport interface McpProxy {\n listen(socketPath: string): Promise<void>;\n open(appServer: string, appOrigin: string): ProxySession;\n stopListening(): Promise<void>;\n close(): Promise<void>;\n}\n\nexport interface ProxySession {\n readonly nonce: string;\n /** The entry for `session/new`. */\n readonly server: StdioMcpServer;\n /** Forward with this token until `end`; a `tools/call` outside a turn gets NO_TURN_MESSAGE. */\n begin(token: Delegation): void;\n renew(token: Delegation): void;\n end(): void;\n dispose(): void;\n}\n\nexport const NO_TURN_MESSAGE =\n \"No turn is active: the user has not asked anything right now. Wait for their next message.\";\n\nexport const DISCONNECTED_MESSAGE =\n \"This computer no longer serves the app: it was disconnected, or Parlor Host stopped.\";\n\nconst SOCKET_ENV = \"PARLOR_HOST_PROXY_SOCKET\";\nconst NONCE_ENV = \"PARLOR_HOST_PROXY_NONCE\";\nconst NO_TURN_CODE = -32000;\nconst REFUSED_CODE = -32001;\n\ntype JsonRpcId = string | number | null;\n\ninterface Message {\n readonly jsonrpc?: unknown;\n readonly id?: JsonRpcId;\n readonly method?: unknown;\n readonly result?: unknown;\n}\n\ninterface Live {\n readonly appOrigin: string;\n token: string | undefined;\n protocolVersion: string | undefined;\n initialized: unknown;\n tools: unknown;\n readonly inflight: Set<AbortController>;\n readonly sockets: Set<Socket>;\n}\n\nfunction parse(text: string): Message | undefined {\n try {\n const value: unknown = JSON.parse(text);\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Message)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst result = (id: JsonRpcId, value: unknown) =>\n JSON.stringify({ jsonrpc: \"2.0\", id, result: value });\n\nconst failure = (id: JsonRpcId, code: number, message: string) =>\n JSON.stringify({ jsonrpc: \"2.0\", id, error: { code, message } });\n\n/** One line per message on the agent's stdio: only a body with a raw newline is compacted. */\nconst asLine = (text: string) =>\n text.includes(\"\\n\") ? JSON.stringify(JSON.parse(text)) : text;\n\nfunction betweenTurns(live: Live, message: Message): string | undefined {\n if (message.id === undefined || typeof message.method !== \"string\") return undefined;\n const { id } = message;\n switch (message.method) {\n case \"ping\":\n return result(id, {});\n case \"initialize\":\n return live.initialized === undefined\n ? failure(id, NO_TURN_CODE, NO_TURN_MESSAGE)\n : result(id, live.initialized);\n case \"tools/list\":\n return live.tools === undefined\n ? failure(id, NO_TURN_CODE, NO_TURN_MESSAGE)\n : result(id, live.tools);\n case \"tools/call\":\n return result(id, {\n content: [{ type: \"text\", text: NO_TURN_MESSAGE }],\n isError: true,\n });\n default:\n return failure(id, NO_TURN_CODE, NO_TURN_MESSAGE);\n }\n}\n\nasync function* serverSentData(body: ReadableStream<Uint8Array>): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n let buffer = \"\";\n let data: string[] = [];\n for await (const chunk of body) {\n buffer += decoder.decode(chunk, { stream: true });\n let end = buffer.indexOf(\"\\n\");\n while (end !== -1) {\n const line = buffer.slice(0, end).replace(/\\r$/, \"\");\n buffer = buffer.slice(end + 1);\n if (line === \"\") {\n if (data.length > 0) yield data.join(\"\\n\");\n data = [];\n } else if (line.startsWith(\"data:\")) {\n data.push(line.slice(line.startsWith(\"data: \") ? 6 : 5));\n }\n end = buffer.indexOf(\"\\n\");\n }\n }\n if (data.length > 0) yield data.join(\"\\n\");\n}\n\nfunction refusal(status: number): string {\n return status === 401\n ? \"This computer can no longer act for the app: the turn ended or the computer was disconnected.\"\n : `The app answered HTTP ${status}.`;\n}\n\nasync function forward(\n live: Live,\n token: string,\n line: string,\n message: Message,\n reply: (line: string) => void,\n): Promise<void> {\n const method = typeof message.method === \"string\" ? message.method : \"response\";\n const controller = new AbortController();\n live.inflight.add(controller);\n let status = 0;\n try {\n const response = await fetch(`${live.appOrigin}/_parlor/mcp`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${token}`,\n accept: \"application/json, text/event-stream\",\n \"content-type\": \"application/json\",\n ...(live.protocolVersion ? { \"mcp-protocol-version\": live.protocolVersion } : {}),\n },\n body: line,\n signal: controller.signal,\n });\n status = response.status;\n const bodies: string[] = [];\n if ((response.headers.get(\"content-type\") ?? \"\").includes(\"text/event-stream\")) {\n if (response.body)\n for await (const data of serverSentData(response.body)) bodies.push(data);\n } else {\n const text = await response.text();\n if (text.trim() !== \"\") bodies.push(text);\n }\n let answered = false;\n for (const text of bodies) {\n const answer = parse(text);\n if (answer?.jsonrpc !== \"2.0\") continue;\n if (answer.id === message.id) {\n answered = true;\n if (method === \"initialize\" && answer.result !== undefined) {\n live.initialized = answer.result;\n const version = (answer.result as { readonly protocolVersion?: unknown })\n .protocolVersion;\n if (typeof version === \"string\") live.protocolVersion = version;\n }\n if (method === \"tools/list\" && answer.result !== undefined)\n live.tools = answer.result;\n }\n reply(asLine(text));\n }\n if (message.id !== undefined && !answered) {\n reply(failure(message.id, REFUSED_CODE, refusal(status)));\n }\n } catch (error) {\n if (message.id !== undefined) {\n reply(\n controller.signal.aborted\n ? failure(message.id, NO_TURN_CODE, NO_TURN_MESSAGE)\n : failure(\n message.id,\n REFUSED_CODE,\n `The app could not be reached: ${String(error)}`,\n ),\n );\n }\n } finally {\n live.inflight.delete(controller);\n logEvent(\"proxy_forward\", { appOrigin: live.appOrigin, rpcMethod: method, status });\n }\n}\n\nfunction handle(live: Live, line: string, reply: (line: string) => void): void {\n const message = parse(line);\n if (!message) return;\n const token = live.token;\n if (token === undefined) {\n const answer = betweenTurns(live, message);\n if (answer) reply(answer);\n return;\n }\n if (\n message.method === \"tools/list\" &&\n live.tools !== undefined &&\n message.id !== undefined\n ) {\n reply(result(message.id, live.tools));\n return;\n }\n void forward(live, token, line, message, reply);\n}\n\nexport function createMcpProxy(hostBinary: string): McpProxy {\n const sessions = new Map<string, Live>();\n let server: Server | undefined;\n let socketPath: string | undefined;\n\n const accept = (socket: Socket) => {\n let live: Live | undefined;\n const reply = (line: string) => {\n if (!socket.destroyed) socket.write(`${line}\\n`);\n };\n onLines(socket, (line) => {\n if (live !== undefined) {\n handle(live, line, reply);\n return;\n }\n live = sessions.get(line.trim());\n if (live) live.sockets.add(socket);\n else socket.destroy();\n });\n socket.on(\"close\", () => live?.sockets.delete(socket));\n socket.on(\"error\", () => socket.destroy());\n };\n\n return {\n async listen(path) {\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await rm(path, { force: true });\n const listening = createServer(accept);\n await new Promise<void>((resolve, reject) => {\n listening.once(\"error\", reject);\n listening.listen(path, () => resolve());\n });\n await chmod(path, 0o600);\n server = listening;\n socketPath = path;\n },\n open(appServer, appOrigin) {\n if (socketPath === undefined)\n throw new Error(\"The MCP proxy is not listening yet.\");\n const nonce = randomBytes(24).toString(\"base64url\");\n const live: Live = {\n appOrigin: appOrigin.replace(/\\/+$/, \"\"),\n token: undefined,\n protocolVersion: undefined,\n initialized: undefined,\n tools: undefined,\n inflight: new Set(),\n sockets: new Set(),\n };\n sessions.set(nonce, live);\n const end = () => {\n live.token = undefined;\n for (const controller of live.inflight) controller.abort();\n };\n return {\n nonce,\n server: {\n name: appServer,\n command: hostBinary,\n args: [\"mcp-proxy\"],\n env: { [SOCKET_ENV]: socketPath, [NONCE_ENV]: nonce },\n },\n begin: (token) => {\n live.token = token.token;\n },\n renew: (token) => {\n if (live.token !== undefined) live.token = token.token;\n },\n end,\n dispose: () => {\n end();\n sessions.delete(nonce);\n for (const socket of live.sockets) socket.destroy();\n },\n };\n },\n async stopListening() {\n const closing = server;\n server = undefined;\n if (!closing) return;\n closing.close();\n if (socketPath) await rm(socketPath, { force: true });\n },\n async close() {\n for (const live of sessions.values()) {\n for (const controller of live.inflight) controller.abort();\n for (const socket of live.sockets) socket.destroy();\n }\n sessions.clear();\n const closing = server;\n server = undefined;\n if (closing) {\n await new Promise<void>((resolve) => closing.close(() => resolve()));\n if (socketPath) await rm(socketPath, { force: true });\n }\n },\n };\n}\n\n/**\n * `parlor-host mcp-proxy`: stdio to the daemon socket and back. Once the daemon hangs up,\n * every request still unanswered, and every later one, is answered with an error here.\n */\nexport async function runMcpProxyProcess(env: NodeJS.ProcessEnv): Promise<void> {\n const path = env[SOCKET_ENV];\n const nonce = env[NONCE_ENV];\n if (!path || !nonce) throw new Error(`${SOCKET_ENV} and ${NONCE_ENV} must be set.`);\n const unanswered = new Set<JsonRpcId>();\n const toAgent = (line: string) => void process.stdout.write(`${line}\\n`);\n const refuse = (id: JsonRpcId) =>\n toAgent(failure(id, REFUSED_CODE, DISCONNECTED_MESSAGE));\n process.stdout.on(\"error\", () => undefined);\n\n const daemon = connect(path);\n daemon.on(\"error\", () => daemon.destroy());\n const hungUp = new Promise<void>((resolve) =>\n daemon.once(\"close\", () => {\n for (const id of unanswered) refuse(id);\n unanswered.clear();\n resolve();\n }),\n );\n daemon.write(`${nonce}\\n`);\n onLines(daemon, (line) => {\n const message = parse(line);\n if (message?.id !== undefined && message.method === undefined) {\n unanswered.delete(message.id);\n }\n toAgent(line);\n });\n\n const agent = onLines(process.stdin, (line) => {\n const message = parse(line);\n const id = typeof message?.method === \"string\" ? message.id : undefined;\n if (!daemon.destroyed) {\n if (id !== undefined) unanswered.add(id);\n daemon.write(`${line}\\n`);\n } else if (id !== undefined) {\n refuse(id);\n }\n });\n await once(agent, \"close\");\n daemon.end();\n await hungUp;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mcp-proxy.js","sourceRoot":"","sources":["../src/mcp-proxy.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { chmod, mkdir, rm } from \"node:fs/promises\";\nimport { connect, createServer, type Server, type Socket } from \"node:net\";\nimport { dirname } from \"node:path\";\n\nimport type { Delegation } from \"@parlor/server\";\n\nimport type { StdioMcpServer } from \"./acp.js\";\nimport { onLines } from \"./lines.js\";\nimport { logEvent } from \"./log.js\";\n\n/**\n * The agent's MCP server for an app is this host: a stdio server (`parlor-host\n * mcp-proxy`) that connects back to the daemon's 0600 socket and names its session by a\n * nonce. The daemon forwards each message to `<app>/_parlor/mcp` with the current turn's\n * token, so no app credential is ever in the agent's config, logs or environment.\n */\nexport interface McpProxy {\n listen(socketPath: string): Promise<void>;\n open(appServer: string, appOrigin: string): ProxySession;\n /**\n * Serves `parlor-host mcp-proxy --app <origin>`: a coding agent on this computer outside\n * any turn, such as Claude Code in a terminal. `undefined` refuses the app.\n */\n serveOutside(resolve: (appOrigin: string) => OutsideApp | undefined): void;\n stopListening(): Promise<void>;\n close(): Promise<void>;\n}\n\nexport interface ProxySession {\n readonly nonce: string;\n /** The entry for `session/new`. */\n readonly server: StdioMcpServer;\n /** Forward with this token until `end`; a `tools/call` outside a turn gets NO_TURN_MESSAGE. */\n begin(token: Delegation): void;\n renew(token: Delegation): void;\n end(): void;\n dispose(): void;\n}\n\n/** An app a coding agent on this computer may use outside its sidebar. */\nexport interface OutsideApp {\n readonly appOrigin: string;\n readonly appName: string;\n /** The name the user asks the agent with, its MCP server name. */\n readonly name: string;\n /** Whose account the agent acts as: its email, or its name. */\n readonly account: string;\n /** A token for `agent`'s calls, fresh enough to use now. */\n token(agent: string): Promise<string>;\n /** Asks on this computer, for an agent that cannot ask the user itself. */\n askHere(question: string): Promise<boolean>;\n}\n\nexport const NO_TURN_MESSAGE =\n \"No turn is active: the user has not asked anything right now. Wait for their next message.\";\n\nexport const DISCONNECTED_MESSAGE =\n \"This computer no longer serves the app: it was disconnected, or Parlor Host stopped.\";\n\nconst OUTSIDE_PREFIX = \"app \";\nconst AGENT_NAMES: Readonly<Record<string, string>> = {\n \"claude-code\": \"Claude Code\",\n \"codex-mcp-client\": \"Codex\",\n codex: \"Codex\",\n};\n\nconst SOCKET_ENV = \"PARLOR_HOST_PROXY_SOCKET\";\nconst NONCE_ENV = \"PARLOR_HOST_PROXY_NONCE\";\nconst NO_TURN_CODE = -32000;\nconst REFUSED_CODE = -32001;\n\ntype JsonRpcId = string | number | null;\n\ninterface Message {\n readonly jsonrpc?: unknown;\n readonly id?: JsonRpcId;\n readonly method?: unknown;\n readonly result?: unknown;\n}\n\ninterface Live {\n readonly appOrigin: string;\n token: string | undefined;\n protocolVersion: string | undefined;\n initialized: unknown;\n tools: unknown;\n readonly inflight: Set<AbortController>;\n readonly sockets: Set<Socket>;\n}\n\nfunction parse(text: string): Message | undefined {\n try {\n const value: unknown = JSON.parse(text);\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Message)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst result = (id: JsonRpcId, value: unknown) =>\n JSON.stringify({ jsonrpc: \"2.0\", id, result: value });\n\nconst failure = (id: JsonRpcId, code: number, message: string) =>\n JSON.stringify({ jsonrpc: \"2.0\", id, error: { code, message } });\n\n/** One line per message on the agent's stdio: only a body with a raw newline is compacted. */\nconst asLine = (text: string) =>\n text.includes(\"\\n\") ? JSON.stringify(JSON.parse(text)) : text;\n\nfunction betweenTurns(live: Live, message: Message): string | undefined {\n if (message.id === undefined || typeof message.method !== \"string\") return undefined;\n const { id } = message;\n switch (message.method) {\n case \"ping\":\n return result(id, {});\n case \"initialize\":\n return live.initialized === undefined\n ? failure(id, NO_TURN_CODE, NO_TURN_MESSAGE)\n : result(id, live.initialized);\n case \"tools/list\":\n return live.tools === undefined\n ? failure(id, NO_TURN_CODE, NO_TURN_MESSAGE)\n : result(id, live.tools);\n case \"tools/call\":\n return result(id, {\n content: [{ type: \"text\", text: NO_TURN_MESSAGE }],\n isError: true,\n });\n default:\n return failure(id, NO_TURN_CODE, NO_TURN_MESSAGE);\n }\n}\n\nasync function* serverSentData(body: ReadableStream<Uint8Array>): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n let buffer = \"\";\n let data: string[] = [];\n for await (const chunk of body) {\n buffer += decoder.decode(chunk, { stream: true });\n let end = buffer.indexOf(\"\\n\");\n while (end !== -1) {\n const line = buffer.slice(0, end).replace(/\\r$/, \"\");\n buffer = buffer.slice(end + 1);\n if (line === \"\") {\n if (data.length > 0) yield data.join(\"\\n\");\n data = [];\n } else if (line.startsWith(\"data:\")) {\n data.push(line.slice(line.startsWith(\"data: \") ? 6 : 5));\n }\n end = buffer.indexOf(\"\\n\");\n }\n }\n if (data.length > 0) yield data.join(\"\\n\");\n}\n\nfunction refusal(status: number): string {\n return status === 401\n ? \"This computer can no longer act for the app: the turn ended or the computer was disconnected.\"\n : `The app answered HTTP ${status}.`;\n}\n\nasync function forward(\n live: Live,\n token: string,\n line: string,\n message: Message,\n reply: (line: string) => void,\n): Promise<void> {\n const method = typeof message.method === \"string\" ? message.method : \"response\";\n const controller = new AbortController();\n live.inflight.add(controller);\n let status = 0;\n try {\n const response = await fetch(`${live.appOrigin}/_parlor/mcp`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${token}`,\n accept: \"application/json, text/event-stream\",\n \"content-type\": \"application/json\",\n ...(live.protocolVersion ? { \"mcp-protocol-version\": live.protocolVersion } : {}),\n },\n body: line,\n signal: controller.signal,\n });\n status = response.status;\n const bodies: string[] = [];\n if ((response.headers.get(\"content-type\") ?? \"\").includes(\"text/event-stream\")) {\n if (response.body)\n for await (const data of serverSentData(response.body)) bodies.push(data);\n } else {\n const text = await response.text();\n if (text.trim() !== \"\") bodies.push(text);\n }\n let answered = false;\n for (const text of bodies) {\n const answer = parse(text);\n if (answer?.jsonrpc !== \"2.0\") continue;\n if (answer.id === message.id) {\n answered = true;\n if (method === \"initialize\" && answer.result !== undefined) {\n live.initialized = answer.result;\n const version = (answer.result as { readonly protocolVersion?: unknown })\n .protocolVersion;\n if (typeof version === \"string\") live.protocolVersion = version;\n }\n if (method === \"tools/list\" && answer.result !== undefined)\n live.tools = answer.result;\n }\n reply(asLine(text));\n }\n if (message.id !== undefined && !answered) {\n reply(failure(message.id, REFUSED_CODE, refusal(status)));\n }\n } catch (error) {\n if (message.id !== undefined) {\n reply(\n controller.signal.aborted\n ? failure(message.id, NO_TURN_CODE, NO_TURN_MESSAGE)\n : failure(\n message.id,\n REFUSED_CODE,\n `The app could not be reached: ${String(error)}`,\n ),\n );\n }\n } finally {\n live.inflight.delete(controller);\n logEvent(\"proxy_forward\", { appOrigin: live.appOrigin, rpcMethod: method, status });\n }\n}\n\nfunction handle(live: Live, line: string, reply: (line: string) => void): void {\n const message = parse(line);\n if (!message) return;\n const token = live.token;\n if (token === undefined) {\n const answer = betweenTurns(live, message);\n if (answer) reply(answer);\n return;\n }\n if (\n message.method === \"tools/list\" &&\n live.tools !== undefined &&\n message.id !== undefined\n ) {\n reply(result(message.id, live.tools));\n return;\n }\n void forward(live, token, line, message, reply);\n}\n\n/**\n * One agent outside a turn, on one socket. Reads run as asked; the first change in the\n * conversation waits for the user, through the agent's own elicitation where it has it.\n */\nfunction outsideSession(\n app: OutsideApp,\n socket: Socket,\n reply: (line: string) => void,\n): (line: string) => void {\n const live: Live = {\n appOrigin: app.appOrigin.replace(/\\/+$/, \"\"),\n token: undefined,\n protocolVersion: undefined,\n initialized: undefined,\n tools: undefined,\n inflight: new Set(),\n sockets: new Set([socket]),\n };\n let agent = \"A coding agent\";\n let elicits = false;\n let changesAllowed: Promise<boolean> | undefined;\n let asked = 0;\n const answers = new Map<string, (allowed: boolean) => void>();\n\n const send = async (line: string, message: Message, onReply = reply) => {\n let token: string;\n try {\n token = await app.token(agent);\n } catch (error) {\n if (message.id !== undefined) {\n onReply(\n failure(message.id, REFUSED_CODE, `${app.appName} refused: ${String(error)}`),\n );\n }\n return;\n }\n await forward(live, token, line, message, onReply);\n };\n\n const readOnly = async (tool: string): Promise<boolean> => {\n if (live.tools === undefined) {\n const id = `parlor-tools-${++asked}`;\n const list = { jsonrpc: \"2.0\", id, method: \"tools/list\", params: {} };\n await send(JSON.stringify(list), list, () => undefined);\n }\n const tools = (live.tools as { readonly tools?: readonly ToolEntry[] } | undefined)\n ?.tools;\n return (\n tools?.find((entry) => entry.name === tool)?.annotations?.readOnlyHint === true\n );\n };\n\n const askToChange = (): Promise<boolean> => {\n const question = `${agent} wants to change ${app.appName} (${app.appOrigin}) as ${app.account}. Allow changes from this conversation?`;\n if (!elicits) return app.askHere(question);\n const id = `parlor-allow-${++asked}`;\n const answered = new Promise<boolean>((resolve) => answers.set(id, resolve));\n reply(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n method: \"elicitation/create\",\n params: {\n message: question,\n requestedSchema: { type: \"object\", properties: {} },\n },\n }),\n );\n return answered;\n };\n\n return (line) => {\n const message = parse(line);\n if (!message) return;\n if (message.method === undefined && typeof message.id === \"string\") {\n const answer = answers.get(message.id);\n if (answer) {\n answers.delete(message.id);\n const action = (message.result as { readonly action?: unknown } | undefined)\n ?.action;\n answer(action === \"accept\");\n return;\n }\n }\n if (message.method === \"initialize\") {\n const params = (message as { readonly params?: InitializeParams }).params;\n const client = params?.clientInfo?.name;\n if (typeof client === \"string\" && client !== \"\")\n agent = AGENT_NAMES[client] ?? client;\n elicits = params?.capabilities?.elicitation !== undefined;\n logEvent(\"outside_agent\", { appOrigin: app.appOrigin, agent, elicits });\n void send(line, message, (answer) => reply(withGuidance(answer, app, agent)));\n return;\n }\n if (message.method === \"tools/call\" && message.id !== undefined) {\n const tool = (message as { readonly params?: { readonly name?: unknown } }).params\n ?.name;\n void (async () => {\n if (typeof tool === \"string\" && !(await readOnly(tool))) {\n changesAllowed ??= askToChange().then((allowed) => {\n if (!allowed) changesAllowed = undefined;\n return allowed;\n });\n if (!(await changesAllowed)) {\n reply(\n result(message.id!, {\n content: [\n {\n type: \"text\",\n text: `Nothing was changed: the user did not allow changes to ${app.appName} from this conversation, or could not be asked here. Tell them so. If they ask for the change again, they are asked again first; nothing in ${app.appName} itself turns this on.`,\n },\n ],\n isError: true,\n }),\n );\n return;\n }\n }\n await send(line, message);\n })();\n return;\n }\n void send(line, message);\n };\n}\n\ninterface ToolEntry {\n readonly name?: string;\n readonly annotations?: { readonly readOnlyHint?: boolean };\n}\n\ninterface InitializeParams {\n readonly clientInfo?: { readonly name?: unknown };\n readonly capabilities?: { readonly elicitation?: unknown };\n}\n\n/** The app's own instructions, led by when to use it at all: only when the user names it. */\nfunction withGuidance(line: string, app: OutsideApp, agent: string): string {\n const answer = parse(line);\n const initialized = answer?.result as { readonly instructions?: unknown } | undefined;\n if (!initialized || typeof initialized !== \"object\") return line;\n const guidance = `Use these tools only when the user names ${app.appName} or \"${app.name}\" in their request: they act on the live app at ${app.appOrigin} as ${app.account}, and ${agent} runs outside the app here. Reading is free; before the first change in this conversation the user is asked to allow changes.`;\n const own =\n typeof initialized.instructions === \"string\" ? initialized.instructions : \"\";\n return JSON.stringify({\n ...answer,\n result: { ...initialized, instructions: own ? `${guidance}\\n\\n${own}` : guidance },\n });\n}\n\nexport function createMcpProxy(hostBinary: string): McpProxy {\n const sessions = new Map<string, Live>();\n let server: Server | undefined;\n let socketPath: string | undefined;\n let outside: (appOrigin: string) => OutsideApp | undefined = () => undefined;\n\n const accept = (socket: Socket) => {\n let live: Live | undefined;\n let outsideLine: ((line: string) => void) | undefined;\n const reply = (line: string) => {\n if (!socket.destroyed) socket.write(`${line}\\n`);\n };\n onLines(socket, (line) => {\n if (outsideLine !== undefined) {\n outsideLine(line);\n return;\n }\n if (live !== undefined) {\n handle(live, line, reply);\n return;\n }\n const first = line.trim();\n if (first.startsWith(OUTSIDE_PREFIX)) {\n const appOrigin = first.slice(OUTSIDE_PREFIX.length);\n const app = outside(appOrigin);\n if (!app) {\n reply(JSON.stringify({ refused: notServedMessage(appOrigin) }));\n socket.end();\n return;\n }\n outsideLine = outsideSession(app, socket, reply);\n return;\n }\n live = sessions.get(first);\n if (live) live.sockets.add(socket);\n else socket.destroy();\n });\n socket.on(\"close\", () => live?.sockets.delete(socket));\n socket.on(\"error\", () => socket.destroy());\n };\n\n return {\n async listen(path) {\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await rm(path, { force: true });\n const listening = createServer(accept);\n await new Promise<void>((resolve, reject) => {\n listening.once(\"error\", reject);\n listening.listen(path, () => resolve());\n });\n await chmod(path, 0o600);\n server = listening;\n socketPath = path;\n },\n serveOutside(resolve) {\n outside = resolve;\n },\n open(appServer, appOrigin) {\n if (socketPath === undefined)\n throw new Error(\"The MCP proxy is not listening yet.\");\n const nonce = randomBytes(24).toString(\"base64url\");\n const live: Live = {\n appOrigin: appOrigin.replace(/\\/+$/, \"\"),\n token: undefined,\n protocolVersion: undefined,\n initialized: undefined,\n tools: undefined,\n inflight: new Set(),\n sockets: new Set(),\n };\n sessions.set(nonce, live);\n const end = () => {\n live.token = undefined;\n for (const controller of live.inflight) controller.abort();\n };\n return {\n nonce,\n server: {\n name: appServer,\n command: hostBinary,\n args: [\"mcp-proxy\"],\n env: { [SOCKET_ENV]: socketPath, [NONCE_ENV]: nonce },\n },\n begin: (token) => {\n live.token = token.token;\n },\n renew: (token) => {\n if (live.token !== undefined) live.token = token.token;\n },\n end,\n dispose: () => {\n end();\n sessions.delete(nonce);\n for (const socket of live.sockets) socket.destroy();\n },\n };\n },\n async stopListening() {\n const closing = server;\n server = undefined;\n if (!closing) return;\n closing.close();\n if (socketPath) await rm(socketPath, { force: true });\n },\n async close() {\n for (const live of sessions.values()) {\n for (const controller of live.inflight) controller.abort();\n for (const socket of live.sockets) socket.destroy();\n }\n sessions.clear();\n const closing = server;\n server = undefined;\n if (closing) {\n await new Promise<void>((resolve) => closing.close(() => resolve()));\n if (socketPath) await rm(socketPath, { force: true });\n }\n },\n };\n}\n\n/** Why an agent outside a turn cannot use `appOrigin` here. */\nfunction notServedMessage(appOrigin: string): string {\n return `This computer does not let coding agents use ${appOrigin}: it is not connected, or that was turned off. Connect it again with \\`npx @parlor/agent-host connect ${appOrigin}\\`.`;\n}\n\nconst notRunningMessage = (appOrigin: string) =>\n `Parlor Host is not running on this computer, so ${appOrigin} cannot be reached. Open the app's agent sidebar, or run \\`npx @parlor/agent-host connect ${appOrigin}\\`.`;\n\n/**\n * `parlor-host mcp-proxy`: stdio to the daemon socket and back. Inside a turn the\n * environment names the session; `--app` serves an agent outside any turn. Once the daemon\n * hangs up, every request still unanswered, and every later one, is answered with an error\n * here.\n */\nexport async function runMcpProxyProcess(\n env: NodeJS.ProcessEnv,\n outside?: { readonly appOrigin: string; readonly socketPath: string },\n): Promise<void> {\n const path = outside?.socketPath ?? env[SOCKET_ENV];\n const nonce = env[NONCE_ENV];\n if (!path || (!outside && !nonce)) {\n throw new Error(`${SOCKET_ENV} and ${NONCE_ENV} must be set, or --app.`);\n }\n let why = outside ? notRunningMessage(outside.appOrigin) : DISCONNECTED_MESSAGE;\n const unanswered = new Set<JsonRpcId>();\n const toAgent = (line: string) => void process.stdout.write(`${line}\\n`);\n const refuse = (id: JsonRpcId) => toAgent(failure(id, REFUSED_CODE, why));\n process.stdout.on(\"error\", () => undefined);\n\n const daemon = connect(path);\n daemon.on(\"error\", () => daemon.destroy());\n daemon.once(\"connect\", () => {\n if (outside) why = DISCONNECTED_MESSAGE;\n });\n const hungUp = new Promise<void>((resolve) =>\n daemon.once(\"close\", () => {\n for (const id of unanswered) refuse(id);\n unanswered.clear();\n resolve();\n }),\n );\n daemon.write(outside ? `${OUTSIDE_PREFIX}${outside.appOrigin}\\n` : `${nonce}\\n`);\n onLines(daemon, (line) => {\n const refused = (parse(line) as { readonly refused?: unknown } | undefined)?.refused;\n if (typeof refused === \"string\") {\n why = refused;\n return;\n }\n const message = parse(line);\n if (message?.id !== undefined && message.method === undefined) {\n unanswered.delete(message.id);\n }\n toAgent(line);\n });\n\n const agent = onLines(process.stdin, (line) => {\n const message = parse(line);\n const id = typeof message?.method === \"string\" ? message.id : undefined;\n if (!daemon.destroyed) {\n if (id !== undefined) unanswered.add(id);\n daemon.write(`${line}\\n`);\n } else if (id !== undefined) {\n refuse(id);\n }\n });\n await once(agent, \"close\");\n daemon.end();\n await hungUp;\n}\n"]}
|
package/dist/pairing.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface PairInput {
|
|
|
29
29
|
readonly channel: PairingChannel;
|
|
30
30
|
readonly keys: HostKeys;
|
|
31
31
|
readonly workspaceRoot: string;
|
|
32
|
+
/** Offers, on the consent page, to let the coding agents here use the app outside its sidebar. */
|
|
33
|
+
readonly offerAgentTools?: boolean | undefined;
|
|
32
34
|
}
|
|
33
35
|
export type PairOutcome = {
|
|
34
36
|
readonly paired: true;
|
package/dist/pairing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIvE,OAAO,KAAK,EAAE,QAAQ,EAAe,QAAQ,EAAE,MAAM,aAAa,CAAC;AAKnE,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAC9D;IACE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClD,CAAC;AAEN,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,kGAAkG;IAClG,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChD;AAED,MAAM,MAAM,WAAW,GACnB;IACE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;CACtD,GACD;IACE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,kBAAkB,GAAG,iBAAiB,GAAG,SAAS,CAAC;CACrE,CAAC;AAEN,qBAAa,cAAe,SAAQ,KAAK;IAGrC,QAAQ,CAAC,KAAK,EAAE,QAAQ;IAF1B,SAAkB,IAAI,oBAAoB;IAC1C,YACW,KAAK,EAAE,QAAQ,EACxB,KAAK,EAAE,KAAK,EAGb;CACF;AA0ID;;;GAGG;AACH,wBAAsB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CA+GjE;AAmBD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClD,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACjD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,YAAY,CA+CjF;AAgCD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAmBzE"}
|
package/dist/pairing.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomInt } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { HOST_PAIRING_FRAGMENT, } from "@parlor/auth";
|
|
4
|
+
import { agentToolsName } from "./agent-tools.js";
|
|
4
5
|
import { hostAssertion } from "./assertion.js";
|
|
5
6
|
import { workspaceOf } from "./naming.js";
|
|
6
7
|
import { printable } from "./prompter.js";
|
|
@@ -52,7 +53,10 @@ async function loopback(state) {
|
|
|
52
53
|
}
|
|
53
54
|
waiting = response;
|
|
54
55
|
const code = url.searchParams.get("code");
|
|
55
|
-
|
|
56
|
+
const agentTools = url.searchParams.get("agentTools");
|
|
57
|
+
deliver(code
|
|
58
|
+
? { code, ...(agentTools === null ? {} : { agentTools: agentTools === "on" }) }
|
|
59
|
+
: { declined: true });
|
|
56
60
|
});
|
|
57
61
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
58
62
|
const { port } = server.address();
|
|
@@ -102,6 +106,11 @@ function timeout(ms) {
|
|
|
102
106
|
});
|
|
103
107
|
return { expired, clear: () => clearTimeout(timer) };
|
|
104
108
|
}
|
|
109
|
+
/** The pairing secret goes nowhere but the app's own origin; an older app has no landing page. */
|
|
110
|
+
function landingOf(origin, landing) {
|
|
111
|
+
const url = landing === undefined ? undefined : URL.parse(landing, origin);
|
|
112
|
+
return url?.origin === origin ? url : new URL("/", origin);
|
|
113
|
+
}
|
|
105
114
|
/**
|
|
106
115
|
* Only the host starts a pairing: it makes the PKCE verifier, `state`, the matching code
|
|
107
116
|
* and the key pair, so nothing from the web side can complete one.
|
|
@@ -109,6 +118,7 @@ function timeout(ms) {
|
|
|
109
118
|
export async function pair(input) {
|
|
110
119
|
const origin = new URL(input.appOrigin).origin;
|
|
111
120
|
const appName = await appNameOf(origin);
|
|
121
|
+
const toolsName = input.offerAgentTools ? agentToolsName(appName, origin) : undefined;
|
|
112
122
|
const key = await input.keys.create(origin);
|
|
113
123
|
const codeVerifier = base64url(randomBytes(32));
|
|
114
124
|
const pairing = {
|
|
@@ -136,6 +146,7 @@ export async function pair(input) {
|
|
|
136
146
|
const query = new URLSearchParams({
|
|
137
147
|
redirectUri: listener.redirectUri,
|
|
138
148
|
...pairing,
|
|
149
|
+
...(toolsName ? { agentTools: "offered" } : {}),
|
|
139
150
|
});
|
|
140
151
|
await input.channel.open(`${origin}/_parlor/auth/hosts/authorize?${query}`);
|
|
141
152
|
const callback = await Promise.race([listener.callback, deadline.expired]);
|
|
@@ -155,10 +166,21 @@ export async function pair(input) {
|
|
|
155
166
|
}
|
|
156
167
|
await key.keep(granted.grant.id);
|
|
157
168
|
const pairingSecret = base64url(randomBytes(32));
|
|
158
|
-
|
|
169
|
+
const landing = landingOf(origin, granted.landing);
|
|
170
|
+
const agentTools = toolsName && "agentTools" in callback ? callback.agentTools : undefined;
|
|
171
|
+
if (agentTools && toolsName)
|
|
172
|
+
landing.searchParams.set("agentTools", toolsName);
|
|
173
|
+
landing.hash = new URLSearchParams({
|
|
174
|
+
[HOST_PAIRING_FRAGMENT]: pairingSecret,
|
|
175
|
+
}).toString();
|
|
176
|
+
listener.redirect(landing.href);
|
|
159
177
|
return {
|
|
160
178
|
paired: true,
|
|
161
|
-
grant: {
|
|
179
|
+
grant: {
|
|
180
|
+
...grantOf(granted),
|
|
181
|
+
pairingSecret,
|
|
182
|
+
...(agentTools === undefined ? {} : { agentTools: { on: agentTools } }),
|
|
183
|
+
},
|
|
162
184
|
approvedBy: granted.approvedBy,
|
|
163
185
|
};
|
|
164
186
|
}
|
package/dist/pairing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing.js","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes, randomInt } from \"node:crypto\";\nimport { createServer, type Server, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nimport {\n type AccountSummary,\n HOST_PAIRING_FRAGMENT,\n type HostGrantResponse,\n} from \"@parlor/auth\";\nimport type { HostPairing, PairingDevice } from \"@parlor/capabilities\";\n\nimport { hostAssertion } from \"./assertion.js\";\nimport type { AppGrant, HostKeyPair, HostKeys } from \"./grants.js\";\nimport { workspaceOf } from \"./naming.js\";\nimport { printable } from \"./prompter.js\";\nimport { revokeGrant } from \"./revoke.js\";\n\nexport const PAIRING_TIMEOUT_MS = 10 * 60_000;\n\nexport interface PairingReview {\n readonly appName: string;\n readonly appOrigin: string;\n readonly account: AccountSummary;\n readonly matchingCode: string;\n}\n\n/**\n * `browser`: a one-shot 127.0.0.1 listener (RFC 8252) accepting exactly this attempt's\n * `state`; the consent page is the one decision. `ipc`: the desktop shell's signed-in\n * webview gets the code from the backend and hands it over Tauri IPC, with no consent page,\n * so the shell's native dialog confirms the account the host got back.\n */\nexport type PairingChannel =\n | { readonly kind: \"browser\"; open(url: string): Promise<void> }\n | {\n readonly kind: \"ipc\";\n authorize(pairing: HostPairing): Promise<string>;\n confirm(review: PairingReview): Promise<boolean>;\n };\n\nexport interface PairInput {\n readonly appOrigin: string;\n readonly hostName: string;\n readonly platform: string;\n readonly channel: PairingChannel;\n readonly keys: HostKeys;\n readonly workspaceRoot: string;\n}\n\nexport type PairOutcome =\n | {\n readonly paired: true;\n readonly grant: AppGrant;\n readonly approvedBy: HostGrantResponse[\"approvedBy\"];\n }\n | {\n readonly paired: false;\n readonly reason: \"declined_locally\" | \"declined_in_app\" | \"timeout\";\n };\n\nexport class UnrevokedGrant extends Error {\n override readonly name = \"UnrevokedGrant\";\n constructor(\n readonly grant: AppGrant,\n cause: Error,\n ) {\n super(cause.message, { cause });\n }\n}\n\nconst MATCHING_ALPHABET = \"ABCDEFGHJKLMNPQRSTUVWXYZ23456789\";\n\nconst base64url = (bytes: Buffer) => bytes.toString(\"base64url\");\n\nfunction matchingCode(): string {\n const part = () =>\n Array.from(\n { length: 3 },\n () => MATCHING_ALPHABET[randomInt(MATCHING_ALPHABET.length)],\n ).join(\"\");\n return `${part()}-${part()}`;\n}\n\nasync function appNameOf(origin: string): Promise<string> {\n const response = await fetch(`${origin}/_parlor/manifest`);\n if (!response.ok) {\n throw new Error(`${origin} answered HTTP ${response.status}; is it a Parlor app?`);\n }\n const manifest = (await response.json()) as {\n readonly app?: { readonly name?: unknown };\n };\n return typeof manifest.app?.name === \"string\"\n ? manifest.app.name\n : new URL(origin).host;\n}\n\ntype Callback = { readonly code: string } | { readonly declined: true };\n\nconst escapeHtml = (text: string) =>\n printable(text).replace(/[&<>\"']/g, (char) => `&#${char.charCodeAt(0)};`);\n\nconst RESULT_STYLE = `:root{color-scheme:light dark;--bg:#f6f6f4;--card:#fff;--text:#1d1d1b;--muted:#5f5f5a;--line:#e2e2dc}@media (prefers-color-scheme:dark){:root{--bg:#151514;--card:#1f1f1d;--text:#ededea;--muted:#a3a39d;--line:#34342f}}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:16px;background:var(--bg);color:var(--text);font:15px/1.5 system-ui,-apple-system,\"Segoe UI\",sans-serif}main{width:100%;max-width:420px;background:var(--card);border:1px solid var(--line);border-radius:12px;padding:28px}h1{font-size:20px;line-height:1.3;margin:0 0 8px}p{margin:0;color:var(--muted)}`;\n\nfunction resultPage(heading: string, detail: string): string {\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>${escapeHtml(heading)}</title><style>${RESULT_STYLE}</style></head><body><main><h1>${escapeHtml(heading)}</h1><p>${escapeHtml(detail)}</p></main></body></html>`;\n}\n\n/** Holds the browser's request until the exchange settles, so the tab says what happened. */\nasync function loopback(state: string): Promise<{\n readonly redirectUri: string;\n readonly callback: Promise<Callback>;\n readonly answer: (heading: string, detail: string) => void;\n readonly redirect: (url: string) => void;\n readonly close: () => void;\n}> {\n let deliver: (callback: Callback) => void = () => undefined;\n const callback = new Promise<Callback>((resolve) => (deliver = resolve));\n let waiting: ServerResponse | undefined;\n const server: Server = createServer((request, response) => {\n const url = new URL(request.url ?? \"/\", \"http://127.0.0.1\");\n if (\n waiting ||\n url.pathname !== \"/callback\" ||\n url.searchParams.get(\"state\") !== state\n ) {\n response\n .writeHead(404, { \"content-type\": \"text/plain\" })\n .end(\"Not this connection.\");\n return;\n }\n waiting = response;\n const code = url.searchParams.get(\"code\");\n deliver(code ? { code } : { declined: true });\n });\n await new Promise<void>((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n const { port } = server.address() as AddressInfo;\n const answer = (heading: string, detail: string) => {\n if (!waiting || waiting.headersSent) return;\n waiting\n .writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" })\n .end(resultPage(heading, detail));\n };\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n callback,\n answer,\n redirect: (url) => {\n if (!waiting || waiting.headersSent) return;\n waiting.writeHead(303, { location: url, \"cache-control\": \"no-store\" }).end();\n },\n close: () => {\n answer(\"Parlor Host stopped\", \"Go back to your terminal.\");\n server.close();\n },\n };\n}\n\nasync function exchange(\n origin: string,\n key: HostKeyPair,\n code: string,\n codeVerifier: string,\n): Promise<HostGrantResponse> {\n const response = await fetch(`${origin}/_parlor/auth/hosts/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n code,\n codeVerifier,\n publicKey: key.publicKey,\n proof: await key.sign(new TextEncoder().encode(code)),\n }),\n });\n const body = (await response.json().catch(() => undefined)) as\n | (HostGrantResponse & { readonly error?: { readonly message?: string } })\n | undefined;\n if (!response.ok || !body?.grant) {\n throw new Error(body?.error?.message ?? `The app answered HTTP ${response.status}.`);\n }\n return body;\n}\n\nfunction timeout(ms: number): {\n readonly expired: Promise<\"timeout\">;\n readonly clear: () => void;\n} {\n let timer: NodeJS.Timeout | undefined;\n const expired = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), ms);\n });\n return { expired, clear: () => clearTimeout(timer) };\n}\n\n/**\n * Only the host starts a pairing: it makes the PKCE verifier, `state`, the matching code\n * and the key pair, so nothing from the web side can complete one.\n */\nexport async function pair(input: PairInput): Promise<PairOutcome> {\n const origin = new URL(input.appOrigin).origin;\n const appName = await appNameOf(origin);\n const key = await input.keys.create(origin);\n const codeVerifier = base64url(randomBytes(32));\n const pairing: HostPairing = {\n hostName: input.hostName,\n platform: input.platform,\n codeChallenge: base64url(createHash(\"sha256\").update(codeVerifier).digest()),\n state: base64url(randomBytes(16)),\n matchingCode: matchingCode(),\n keyThumbprint: key.thumbprint,\n };\n const grantOf = (granted: HostGrantResponse): AppGrant => ({\n appOrigin: origin,\n appName,\n grantId: granted.grant.id,\n hostId: granted.grant.hostId,\n account: granted.account,\n workspace: workspaceOf(input.workspaceRoot, origin),\n createdAt: Date.now(),\n });\n const deadline = timeout(PAIRING_TIMEOUT_MS);\n try {\n if (input.channel.kind === \"browser\") {\n const listener = await loopback(pairing.state);\n try {\n const query = new URLSearchParams({\n redirectUri: listener.redirectUri,\n ...pairing,\n });\n await input.channel.open(`${origin}/_parlor/auth/hosts/authorize?${query}`);\n const callback = await Promise.race([listener.callback, deadline.expired]);\n if (callback === \"timeout\") return { paired: false, reason: \"timeout\" };\n if (!(\"code\" in callback)) {\n listener.answer(\"Not connected\", \"You cancelled the connection.\");\n return { paired: false, reason: \"declined_in_app\" };\n }\n let granted: HostGrantResponse;\n try {\n granted = await exchange(origin, key, callback.code, codeVerifier);\n } catch (error) {\n listener.answer(\"Not connected\", (error as Error).message);\n throw error;\n }\n await key.keep(granted.grant.id);\n const pairingSecret = base64url(randomBytes(32));\n listener.redirect(\n `${origin}/#${new URLSearchParams({ [HOST_PAIRING_FRAGMENT]: pairingSecret })}`,\n );\n return {\n paired: true,\n grant: { ...grantOf(granted), pairingSecret },\n approvedBy: granted.approvedBy,\n };\n } finally {\n listener.close();\n }\n }\n\n const callback = await Promise.race([\n input.channel.authorize(pairing).then((code) => ({ code })),\n deadline.expired,\n ]);\n if (callback === \"timeout\") return { paired: false, reason: \"timeout\" };\n const granted = await exchange(origin, key, callback.code, codeVerifier);\n const grant = grantOf(granted);\n const confirmed = await Promise.race([\n input.channel.confirm({\n appName,\n appOrigin: origin,\n account: granted.account,\n matchingCode: pairing.matchingCode,\n }),\n deadline.expired,\n ]);\n if (confirmed !== true) {\n await revokeGrant({\n appOrigin: origin,\n appName,\n assert: (request) =>\n hostAssertion(\n (payload) => key.sign(payload),\n { kid: key.thumbprint, grantId: grant.grantId },\n request,\n ),\n }).catch(async (error: Error) => {\n await key.keep(grant.grantId);\n throw new UnrevokedGrant({ ...grant, declined: true }, error);\n });\n return {\n paired: false,\n reason: confirmed === \"timeout\" ? \"timeout\" : \"declined_locally\",\n };\n }\n await key.keep(grant.grantId);\n return { paired: true, grant, approvedBy: granted.approvedBy };\n } finally {\n deadline.clear();\n }\n}\n\ninterface Deferred<T> {\n readonly promise: Promise<T>;\n readonly resolve: (value: T) => void;\n readonly reject: (error: unknown) => void;\n}\n\nfunction deferred<T>(): Deferred<T> {\n let resolve!: (value: T) => void;\n let reject!: (error: unknown) => void;\n const promise = new Promise<T>((done, fail) => {\n resolve = done;\n reject = fail;\n });\n promise.catch(() => undefined);\n return { promise, resolve, reject };\n}\n\nexport interface LocalPairingReview {\n readonly account: string;\n readonly appName: string;\n readonly matchingCode: string;\n}\n\n/**\n * A pairing driven over the control socket by the desktop shell: `pairing` once the host\n * made its key and code, `finish` with the code the signed-in page got from the backend,\n * `confirm` with the user's answer in the native dialog.\n */\nexport interface LocalPairing {\n readonly pairing: Promise<HostPairing>;\n finish(code: string): Promise<LocalPairingReview>;\n confirm(connect: boolean): Promise<PairOutcome>;\n}\n\nexport function startLocalPairing(input: Omit<PairInput, \"channel\">): LocalPairing {\n const ready = deferred<HostPairing>();\n const code = deferred<string>();\n const review = deferred<LocalPairingReview>();\n const decision = deferred<boolean>();\n const outcome = pair({\n ...input,\n channel: {\n kind: \"ipc\",\n authorize: (pairing) => {\n ready.resolve(pairing);\n return code.promise;\n },\n confirm: (shown) => {\n review.resolve({\n account: printable(shown.account.email ?? shown.account.name),\n appName: printable(shown.appName),\n matchingCode: shown.matchingCode,\n });\n return decision.promise;\n },\n },\n });\n outcome.then(\n (result) => {\n if (result.paired) return;\n const stopped = new Error(`The pairing ended: ${result.reason}.`);\n ready.reject(stopped);\n review.reject(stopped);\n },\n (error: unknown) => {\n ready.reject(error);\n review.reject(error);\n },\n );\n outcome.catch(() => undefined);\n return {\n pairing: ready.promise,\n finish: (value) => {\n code.resolve(value);\n return review.promise;\n },\n confirm: (connect) => {\n decision.resolve(connect);\n return outcome;\n },\n };\n}\n\nconst isText = (value: unknown): value is string =>\n typeof value === \"string\" && value !== \"\";\n\ntype Point = JsonWebKey & { readonly x: string; readonly y: string };\n\nconst isPoint = (value: unknown): value is Point => {\n if (typeof value !== \"object\" || value === null) return false;\n const key = value as Record<string, unknown>;\n return key.kty === \"EC\" && key.crv === \"P-256\" && isText(key.x) && isText(key.y);\n};\n\nfunction presenceKeyOf(value: unknown): PairingDevice[\"presenceKey\"] {\n if (typeof value !== \"object\" || value === null) return undefined;\n const key = value as Record<string, unknown>;\n if (!isPoint(key.publicKey)) return undefined;\n const publicKey = { kty: \"EC\", crv: \"P-256\", x: key.publicKey.x, y: key.publicKey.y };\n if (key.format === \"webauthn\" && isText(key.credentialId) && isText(key.rpId)) {\n return {\n format: \"webauthn\",\n credentialId: key.credentialId,\n rpId: key.rpId,\n publicKey,\n };\n }\n if (key.format === \"es256-raw\" && isText(key.keyId)) {\n return { format: \"es256-raw\", keyId: key.keyId, publicKey };\n }\n return undefined;\n}\n\nexport function pairingDeviceOf(value: unknown): PairingDevice | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const device = value as Record<string, unknown>;\n if (!isText(device.deviceId) || !isText(device.name) || !isText(device.platform)) {\n return undefined;\n }\n if (!isPoint(device.signingKey)) return undefined;\n return {\n deviceId: device.deviceId,\n name: device.name,\n platform: device.platform,\n signingKey: {\n kty: \"EC\",\n crv: \"P-256\",\n x: device.signingKey.x,\n y: device.signingKey.y,\n },\n presenceKey: presenceKeyOf(device.presenceKey),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pairing.js","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes, randomInt } from \"node:crypto\";\nimport { createServer, type Server, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nimport {\n type AccountSummary,\n HOST_PAIRING_FRAGMENT,\n type HostGrantResponse,\n} from \"@parlor/auth\";\nimport type { HostPairing, PairingDevice } from \"@parlor/capabilities\";\n\nimport { agentToolsName } from \"./agent-tools.js\";\nimport { hostAssertion } from \"./assertion.js\";\nimport type { AppGrant, HostKeyPair, HostKeys } from \"./grants.js\";\nimport { workspaceOf } from \"./naming.js\";\nimport { printable } from \"./prompter.js\";\nimport { revokeGrant } from \"./revoke.js\";\n\nexport const PAIRING_TIMEOUT_MS = 10 * 60_000;\n\nexport interface PairingReview {\n readonly appName: string;\n readonly appOrigin: string;\n readonly account: AccountSummary;\n readonly matchingCode: string;\n}\n\n/**\n * `browser`: a one-shot 127.0.0.1 listener (RFC 8252) accepting exactly this attempt's\n * `state`; the consent page is the one decision. `ipc`: the desktop shell's signed-in\n * webview gets the code from the backend and hands it over Tauri IPC, with no consent page,\n * so the shell's native dialog confirms the account the host got back.\n */\nexport type PairingChannel =\n | { readonly kind: \"browser\"; open(url: string): Promise<void> }\n | {\n readonly kind: \"ipc\";\n authorize(pairing: HostPairing): Promise<string>;\n confirm(review: PairingReview): Promise<boolean>;\n };\n\nexport interface PairInput {\n readonly appOrigin: string;\n readonly hostName: string;\n readonly platform: string;\n readonly channel: PairingChannel;\n readonly keys: HostKeys;\n readonly workspaceRoot: string;\n /** Offers, on the consent page, to let the coding agents here use the app outside its sidebar. */\n readonly offerAgentTools?: boolean | undefined;\n}\n\nexport type PairOutcome =\n | {\n readonly paired: true;\n readonly grant: AppGrant;\n readonly approvedBy: HostGrantResponse[\"approvedBy\"];\n }\n | {\n readonly paired: false;\n readonly reason: \"declined_locally\" | \"declined_in_app\" | \"timeout\";\n };\n\nexport class UnrevokedGrant extends Error {\n override readonly name = \"UnrevokedGrant\";\n constructor(\n readonly grant: AppGrant,\n cause: Error,\n ) {\n super(cause.message, { cause });\n }\n}\n\nconst MATCHING_ALPHABET = \"ABCDEFGHJKLMNPQRSTUVWXYZ23456789\";\n\nconst base64url = (bytes: Buffer) => bytes.toString(\"base64url\");\n\nfunction matchingCode(): string {\n const part = () =>\n Array.from(\n { length: 3 },\n () => MATCHING_ALPHABET[randomInt(MATCHING_ALPHABET.length)],\n ).join(\"\");\n return `${part()}-${part()}`;\n}\n\nasync function appNameOf(origin: string): Promise<string> {\n const response = await fetch(`${origin}/_parlor/manifest`);\n if (!response.ok) {\n throw new Error(`${origin} answered HTTP ${response.status}; is it a Parlor app?`);\n }\n const manifest = (await response.json()) as {\n readonly app?: { readonly name?: unknown };\n };\n return typeof manifest.app?.name === \"string\"\n ? manifest.app.name\n : new URL(origin).host;\n}\n\ntype Callback =\n | { readonly code: string; readonly agentTools?: boolean | undefined }\n | { readonly declined: true };\n\nconst escapeHtml = (text: string) =>\n printable(text).replace(/[&<>\"']/g, (char) => `&#${char.charCodeAt(0)};`);\n\nconst RESULT_STYLE = `:root{color-scheme:light dark;--bg:#f6f6f4;--card:#fff;--text:#1d1d1b;--muted:#5f5f5a;--line:#e2e2dc}@media (prefers-color-scheme:dark){:root{--bg:#151514;--card:#1f1f1d;--text:#ededea;--muted:#a3a39d;--line:#34342f}}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:16px;background:var(--bg);color:var(--text);font:15px/1.5 system-ui,-apple-system,\"Segoe UI\",sans-serif}main{width:100%;max-width:420px;background:var(--card);border:1px solid var(--line);border-radius:12px;padding:28px}h1{font-size:20px;line-height:1.3;margin:0 0 8px}p{margin:0;color:var(--muted)}`;\n\nfunction resultPage(heading: string, detail: string): string {\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>${escapeHtml(heading)}</title><style>${RESULT_STYLE}</style></head><body><main><h1>${escapeHtml(heading)}</h1><p>${escapeHtml(detail)}</p></main></body></html>`;\n}\n\n/** Holds the browser's request until the exchange settles, so the tab says what happened. */\nasync function loopback(state: string): Promise<{\n readonly redirectUri: string;\n readonly callback: Promise<Callback>;\n readonly answer: (heading: string, detail: string) => void;\n readonly redirect: (url: string) => void;\n readonly close: () => void;\n}> {\n let deliver: (callback: Callback) => void = () => undefined;\n const callback = new Promise<Callback>((resolve) => (deliver = resolve));\n let waiting: ServerResponse | undefined;\n const server: Server = createServer((request, response) => {\n const url = new URL(request.url ?? \"/\", \"http://127.0.0.1\");\n if (\n waiting ||\n url.pathname !== \"/callback\" ||\n url.searchParams.get(\"state\") !== state\n ) {\n response\n .writeHead(404, { \"content-type\": \"text/plain\" })\n .end(\"Not this connection.\");\n return;\n }\n waiting = response;\n const code = url.searchParams.get(\"code\");\n const agentTools = url.searchParams.get(\"agentTools\");\n deliver(\n code\n ? { code, ...(agentTools === null ? {} : { agentTools: agentTools === \"on\" }) }\n : { declined: true },\n );\n });\n await new Promise<void>((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n const { port } = server.address() as AddressInfo;\n const answer = (heading: string, detail: string) => {\n if (!waiting || waiting.headersSent) return;\n waiting\n .writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" })\n .end(resultPage(heading, detail));\n };\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n callback,\n answer,\n redirect: (url) => {\n if (!waiting || waiting.headersSent) return;\n waiting.writeHead(303, { location: url, \"cache-control\": \"no-store\" }).end();\n },\n close: () => {\n answer(\"Parlor Host stopped\", \"Go back to your terminal.\");\n server.close();\n },\n };\n}\n\nasync function exchange(\n origin: string,\n key: HostKeyPair,\n code: string,\n codeVerifier: string,\n): Promise<HostGrantResponse> {\n const response = await fetch(`${origin}/_parlor/auth/hosts/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n code,\n codeVerifier,\n publicKey: key.publicKey,\n proof: await key.sign(new TextEncoder().encode(code)),\n }),\n });\n const body = (await response.json().catch(() => undefined)) as\n | (HostGrantResponse & { readonly error?: { readonly message?: string } })\n | undefined;\n if (!response.ok || !body?.grant) {\n throw new Error(body?.error?.message ?? `The app answered HTTP ${response.status}.`);\n }\n return body;\n}\n\nfunction timeout(ms: number): {\n readonly expired: Promise<\"timeout\">;\n readonly clear: () => void;\n} {\n let timer: NodeJS.Timeout | undefined;\n const expired = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), ms);\n });\n return { expired, clear: () => clearTimeout(timer) };\n}\n\n/** The pairing secret goes nowhere but the app's own origin; an older app has no landing page. */\nfunction landingOf(origin: string, landing: string | undefined): URL {\n const url = landing === undefined ? undefined : URL.parse(landing, origin);\n return url?.origin === origin ? url : new URL(\"/\", origin);\n}\n\n/**\n * Only the host starts a pairing: it makes the PKCE verifier, `state`, the matching code\n * and the key pair, so nothing from the web side can complete one.\n */\nexport async function pair(input: PairInput): Promise<PairOutcome> {\n const origin = new URL(input.appOrigin).origin;\n const appName = await appNameOf(origin);\n const toolsName = input.offerAgentTools ? agentToolsName(appName, origin) : undefined;\n const key = await input.keys.create(origin);\n const codeVerifier = base64url(randomBytes(32));\n const pairing: HostPairing = {\n hostName: input.hostName,\n platform: input.platform,\n codeChallenge: base64url(createHash(\"sha256\").update(codeVerifier).digest()),\n state: base64url(randomBytes(16)),\n matchingCode: matchingCode(),\n keyThumbprint: key.thumbprint,\n };\n const grantOf = (granted: HostGrantResponse): AppGrant => ({\n appOrigin: origin,\n appName,\n grantId: granted.grant.id,\n hostId: granted.grant.hostId,\n account: granted.account,\n workspace: workspaceOf(input.workspaceRoot, origin),\n createdAt: Date.now(),\n });\n const deadline = timeout(PAIRING_TIMEOUT_MS);\n try {\n if (input.channel.kind === \"browser\") {\n const listener = await loopback(pairing.state);\n try {\n const query = new URLSearchParams({\n redirectUri: listener.redirectUri,\n ...pairing,\n ...(toolsName ? { agentTools: \"offered\" } : {}),\n });\n await input.channel.open(`${origin}/_parlor/auth/hosts/authorize?${query}`);\n const callback = await Promise.race([listener.callback, deadline.expired]);\n if (callback === \"timeout\") return { paired: false, reason: \"timeout\" };\n if (!(\"code\" in callback)) {\n listener.answer(\"Not connected\", \"You cancelled the connection.\");\n return { paired: false, reason: \"declined_in_app\" };\n }\n let granted: HostGrantResponse;\n try {\n granted = await exchange(origin, key, callback.code, codeVerifier);\n } catch (error) {\n listener.answer(\"Not connected\", (error as Error).message);\n throw error;\n }\n await key.keep(granted.grant.id);\n const pairingSecret = base64url(randomBytes(32));\n const landing = landingOf(origin, granted.landing);\n const agentTools =\n toolsName && \"agentTools\" in callback ? callback.agentTools : undefined;\n if (agentTools && toolsName) landing.searchParams.set(\"agentTools\", toolsName);\n landing.hash = new URLSearchParams({\n [HOST_PAIRING_FRAGMENT]: pairingSecret,\n }).toString();\n listener.redirect(landing.href);\n return {\n paired: true,\n grant: {\n ...grantOf(granted),\n pairingSecret,\n ...(agentTools === undefined ? {} : { agentTools: { on: agentTools } }),\n },\n approvedBy: granted.approvedBy,\n };\n } finally {\n listener.close();\n }\n }\n\n const callback = await Promise.race([\n input.channel.authorize(pairing).then((code) => ({ code })),\n deadline.expired,\n ]);\n if (callback === \"timeout\") return { paired: false, reason: \"timeout\" };\n const granted = await exchange(origin, key, callback.code, codeVerifier);\n const grant = grantOf(granted);\n const confirmed = await Promise.race([\n input.channel.confirm({\n appName,\n appOrigin: origin,\n account: granted.account,\n matchingCode: pairing.matchingCode,\n }),\n deadline.expired,\n ]);\n if (confirmed !== true) {\n await revokeGrant({\n appOrigin: origin,\n appName,\n assert: (request) =>\n hostAssertion(\n (payload) => key.sign(payload),\n { kid: key.thumbprint, grantId: grant.grantId },\n request,\n ),\n }).catch(async (error: Error) => {\n await key.keep(grant.grantId);\n throw new UnrevokedGrant({ ...grant, declined: true }, error);\n });\n return {\n paired: false,\n reason: confirmed === \"timeout\" ? \"timeout\" : \"declined_locally\",\n };\n }\n await key.keep(grant.grantId);\n return { paired: true, grant, approvedBy: granted.approvedBy };\n } finally {\n deadline.clear();\n }\n}\n\ninterface Deferred<T> {\n readonly promise: Promise<T>;\n readonly resolve: (value: T) => void;\n readonly reject: (error: unknown) => void;\n}\n\nfunction deferred<T>(): Deferred<T> {\n let resolve!: (value: T) => void;\n let reject!: (error: unknown) => void;\n const promise = new Promise<T>((done, fail) => {\n resolve = done;\n reject = fail;\n });\n promise.catch(() => undefined);\n return { promise, resolve, reject };\n}\n\nexport interface LocalPairingReview {\n readonly account: string;\n readonly appName: string;\n readonly matchingCode: string;\n}\n\n/**\n * A pairing driven over the control socket by the desktop shell: `pairing` once the host\n * made its key and code, `finish` with the code the signed-in page got from the backend,\n * `confirm` with the user's answer in the native dialog.\n */\nexport interface LocalPairing {\n readonly pairing: Promise<HostPairing>;\n finish(code: string): Promise<LocalPairingReview>;\n confirm(connect: boolean): Promise<PairOutcome>;\n}\n\nexport function startLocalPairing(input: Omit<PairInput, \"channel\">): LocalPairing {\n const ready = deferred<HostPairing>();\n const code = deferred<string>();\n const review = deferred<LocalPairingReview>();\n const decision = deferred<boolean>();\n const outcome = pair({\n ...input,\n channel: {\n kind: \"ipc\",\n authorize: (pairing) => {\n ready.resolve(pairing);\n return code.promise;\n },\n confirm: (shown) => {\n review.resolve({\n account: printable(shown.account.email ?? shown.account.name),\n appName: printable(shown.appName),\n matchingCode: shown.matchingCode,\n });\n return decision.promise;\n },\n },\n });\n outcome.then(\n (result) => {\n if (result.paired) return;\n const stopped = new Error(`The pairing ended: ${result.reason}.`);\n ready.reject(stopped);\n review.reject(stopped);\n },\n (error: unknown) => {\n ready.reject(error);\n review.reject(error);\n },\n );\n outcome.catch(() => undefined);\n return {\n pairing: ready.promise,\n finish: (value) => {\n code.resolve(value);\n return review.promise;\n },\n confirm: (connect) => {\n decision.resolve(connect);\n return outcome;\n },\n };\n}\n\nconst isText = (value: unknown): value is string =>\n typeof value === \"string\" && value !== \"\";\n\ntype Point = JsonWebKey & { readonly x: string; readonly y: string };\n\nconst isPoint = (value: unknown): value is Point => {\n if (typeof value !== \"object\" || value === null) return false;\n const key = value as Record<string, unknown>;\n return key.kty === \"EC\" && key.crv === \"P-256\" && isText(key.x) && isText(key.y);\n};\n\nfunction presenceKeyOf(value: unknown): PairingDevice[\"presenceKey\"] {\n if (typeof value !== \"object\" || value === null) return undefined;\n const key = value as Record<string, unknown>;\n if (!isPoint(key.publicKey)) return undefined;\n const publicKey = { kty: \"EC\", crv: \"P-256\", x: key.publicKey.x, y: key.publicKey.y };\n if (key.format === \"webauthn\" && isText(key.credentialId) && isText(key.rpId)) {\n return {\n format: \"webauthn\",\n credentialId: key.credentialId,\n rpId: key.rpId,\n publicKey,\n };\n }\n if (key.format === \"es256-raw\" && isText(key.keyId)) {\n return { format: \"es256-raw\", keyId: key.keyId, publicKey };\n }\n return undefined;\n}\n\nexport function pairingDeviceOf(value: unknown): PairingDevice | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const device = value as Record<string, unknown>;\n if (!isText(device.deviceId) || !isText(device.name) || !isText(device.platform)) {\n return undefined;\n }\n if (!isPoint(device.signingKey)) return undefined;\n return {\n deviceId: device.deviceId,\n name: device.name,\n platform: device.platform,\n signingKey: {\n kty: \"EC\",\n crv: \"P-256\",\n x: device.signingKey.x,\n y: device.signingKey.y,\n },\n presenceKey: presenceKeyOf(device.presenceKey),\n };\n}\n"]}
|
package/dist/paths.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface HostPaths {
|
|
|
5
5
|
readonly policy: string;
|
|
6
6
|
readonly sessions: string;
|
|
7
7
|
readonly agents: string;
|
|
8
|
+
/** What each agent last said about itself: its options and the account's allowance. */
|
|
9
|
+
readonly learned: string;
|
|
8
10
|
readonly control: string;
|
|
9
11
|
readonly proxy: string;
|
|
10
12
|
readonly versions: string;
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAA2C,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAA2C,GAAG,SAAS,CAoBtF"}
|
package/dist/paths.js
CHANGED
|
@@ -10,6 +10,7 @@ export function hostPaths(root = join(homedir(), ".parlor", "host")) {
|
|
|
10
10
|
policy: join(root, "policy.json"),
|
|
11
11
|
sessions: join(root, "sessions.json"),
|
|
12
12
|
agents: join(root, "agents.json"),
|
|
13
|
+
learned: join(root, "learned.json"),
|
|
13
14
|
control: join(run, "control.sock"),
|
|
14
15
|
proxy: join(run, "proxy.sock"),
|
|
15
16
|
versions: join(root, "versions"),
|
package/dist/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAsB1C,MAAM,UAAU,SAAS,CAAC,IAAI,GAAW,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACpC,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC;QACjC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;QAC/B,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC;QACjC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC;QACrC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC;QACjC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC;QACnC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC;QAClC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC;QAC9B,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC;QAChC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;QAC9B,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC;QAChC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,uBAAuB,CAAC;QAC3E,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport interface HostPaths {\n readonly root: string;\n readonly grants: string;\n readonly trust: string;\n readonly policy: string;\n readonly sessions: string;\n readonly agents: string;\n /** What each agent last said about itself: its options and the account's allowance. */\n readonly learned: string;\n readonly control: string;\n readonly proxy: string;\n readonly versions: string;\n /** What the login item points at; a link into `versions/<version>/`, never into an app bundle. */\n readonly current: string;\n readonly lock: string;\n readonly logs: string;\n readonly launchAgent: string;\n readonly workspaces: string;\n}\n\nexport function hostPaths(root: string = join(homedir(), \".parlor\", \"host\")): HostPaths {\n const run = join(root, \"run\");\n const home = dirname(dirname(root));\n return {\n root,\n grants: join(root, \"grants.json\"),\n trust: join(root, \"trust.json\"),\n policy: join(root, \"policy.json\"),\n sessions: join(root, \"sessions.json\"),\n agents: join(root, \"agents.json\"),\n learned: join(root, \"learned.json\"),\n control: join(run, \"control.sock\"),\n proxy: join(run, \"proxy.sock\"),\n versions: join(root, \"versions\"),\n current: join(root, \"current\"),\n lock: join(root, \"service.lock\"),\n logs: join(root, \"logs\"),\n launchAgent: join(home, \"Library\", \"LaunchAgents\", \"dev.parlor.host.plist\"),\n workspaces: join(home, \"Parlor\"),\n };\n}\n"]}
|
package/dist/policy.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface RemotePolicy {
|
|
|
19
19
|
readonly promptPresence?: {
|
|
20
20
|
readonly maxAgeHours: number;
|
|
21
21
|
} | "off" | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* `auto`: the agent's own safety review decides, asking only for what it flags, where the
|
|
24
|
+
* agent has one. `ask`: it asks before every action. Absent: `auto`.
|
|
25
|
+
*/
|
|
26
|
+
readonly agentMode?: "auto" | "ask" | undefined;
|
|
22
27
|
}
|
|
23
28
|
export declare const DEFAULT_POLICY: RemotePolicy;
|
|
24
29
|
export declare const DEV_POLICY: RemotePolicy;
|
|
@@ -29,8 +34,13 @@ export declare function presenceDueAt(policy: RemotePolicy, device: TrustedDevic
|
|
|
29
34
|
* app's `confirm: true`, and asking twice only teaches users to click through.
|
|
30
35
|
*/
|
|
31
36
|
export declare function isAppTool(ask: AgentAsk, appServer: string): boolean;
|
|
32
|
-
/**
|
|
33
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The ask item's `approval`; `undefined` lets any trusted device answer without presence.
|
|
39
|
+
* Where presence is wanted, the browser that approved `connect` here answers its own turn's
|
|
40
|
+
* asks in the chat, since the loopback redirect put it on this computer; any other device
|
|
41
|
+
* without presence leaves them to this computer.
|
|
42
|
+
*/
|
|
43
|
+
export declare function approvalFor(policy: RemotePolicy, ask: AgentAsk, device?: Pick<TrustedDevice, "presenceKey" | "via">): "presence" | "computer" | "device" | undefined;
|
|
34
44
|
/** `base` with the settings `parlor-host config` wrote to `file`; read afresh on every call. */
|
|
35
45
|
export declare function loadPolicy(file: string, base: RemotePolicy): Promise<RemotePolicy>;
|
|
36
46
|
export declare function changePolicy(file: string, base: RemotePolicy, change: Partial<RemotePolicy>): Promise<RemotePolicy>;
|
package/dist/policy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,8FAA8F;IAC9F,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,KAAK,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,KAAK,CAAC;IAClD;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,8FAA8F;IAC9F,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,KAAK,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,KAAK,CAAC;IAClD;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,GAAG,SAAS,CAAC;IAC/E;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;CACjD;AAED,eAAO,MAAM,cAAc,EAAE,YAK5B,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,YAKxB,CAAC;AAEF,4FAA4F;AAC5F,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,aAAa,GACpB,MAAM,GAAG,SAAS,CAIpB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,QAAQ,EACb,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,KAAK,CAAC,GAClD,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAKhD;AAOD,gGAAgG;AAChG,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,YAAY,CAAC,CAiBvB;AAaD,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAC5B,OAAO,CAAC,YAAY,CAAC,CAGvB"}
|
package/dist/policy.js
CHANGED
|
@@ -3,11 +3,13 @@ export const DEFAULT_POLICY = {
|
|
|
3
3
|
deviceApproval: "local",
|
|
4
4
|
answers: "presence",
|
|
5
5
|
promptPresence: { maxAgeHours: 18 },
|
|
6
|
+
agentMode: "auto",
|
|
6
7
|
};
|
|
7
8
|
export const DEV_POLICY = {
|
|
8
9
|
deviceApproval: "off",
|
|
9
10
|
answers: "off",
|
|
10
11
|
promptPresence: "off",
|
|
12
|
+
agentMode: "auto",
|
|
11
13
|
};
|
|
12
14
|
/** When `device`'s prompts need Face ID or Touch ID again; undefined when they never do. */
|
|
13
15
|
export function presenceDueAt(policy, device) {
|
|
@@ -23,11 +25,18 @@ export function presenceDueAt(policy, device) {
|
|
|
23
25
|
export function isAppTool(ask, appServer) {
|
|
24
26
|
return ask.kind === "permission" && ask.mcp?.server === appServer;
|
|
25
27
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The ask item's `approval`; `undefined` lets any trusted device answer without presence.
|
|
30
|
+
* Where presence is wanted, the browser that approved `connect` here answers its own turn's
|
|
31
|
+
* asks in the chat, since the loopback redirect put it on this computer; any other device
|
|
32
|
+
* without presence leaves them to this computer.
|
|
33
|
+
*/
|
|
34
|
+
export function approvalFor(policy, ask, device) {
|
|
28
35
|
if (ask.kind === "question" || policy.answers === "off")
|
|
29
36
|
return undefined;
|
|
30
|
-
|
|
37
|
+
if (policy.answers === "computer" || !device || device.presenceKey)
|
|
38
|
+
return policy.answers;
|
|
39
|
+
return device.via === "pairing" ? "device" : "computer";
|
|
31
40
|
}
|
|
32
41
|
const choose = (allowed, value, base) => allowed.find((candidate) => candidate === value) ?? base;
|
|
33
42
|
/** `base` with the settings `parlor-host config` wrote to `file`; read afresh on every call. */
|
|
@@ -43,6 +52,7 @@ export async function loadPolicy(file, base) {
|
|
|
43
52
|
deviceApproval: choose(["local", "off"], overlay.deviceApproval, base.deviceApproval),
|
|
44
53
|
answers: choose(["presence", "computer", "off"], overlay.answers, base.answers),
|
|
45
54
|
promptPresence: promptPresenceOf(overlay.promptPresence, base.promptPresence),
|
|
55
|
+
agentMode: choose(["auto", "ask"], overlay.agentMode, base.agentMode ?? "auto"),
|
|
46
56
|
};
|
|
47
57
|
}
|
|
48
58
|
function promptPresenceOf(value, base) {
|
package/dist/policy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentAsk } from \"./acp.js\";\nimport { jsonFile } from \"./json-file.js\";\nimport type { TrustedDevice } from \"./trust.js\";\n\n/**\n * What this computer enforces on remote turns, whatever the app or its backend says. Set\n * only on this computer (`parlor-host config` or the Mac app's settings).\n */\nexport interface RemotePolicy {\n /** `local`: an untrusted device is approved in a dialog here before its first prompt runs. */\n readonly deviceApproval: \"local\" | \"off\";\n /**\n * Where an answer to the agent's own permission prompts counts: on any trusted device\n * with presence, only on this computer, or (`parlor dev` only) anywhere.\n */\n readonly answers: \"presence\" | \"computer\" | \"off\";\n /**\n * How long a linked device with Face ID or Touch ID sends before its next prompt needs them\n * again. Absent: never.\n */\n readonly promptPresence?: { readonly maxAgeHours: number } | \"off\" | undefined;\n /**\n * `auto`: the agent's own safety review decides, asking only for what it flags, where the\n * agent has one. `ask`: it asks before every action. Absent: `auto`.\n */\n readonly agentMode?: \"auto\" | \"ask\" | undefined;\n}\n\nexport const DEFAULT_POLICY: RemotePolicy = {\n deviceApproval: \"local\",\n answers: \"presence\",\n promptPresence: { maxAgeHours: 18 },\n agentMode: \"auto\",\n};\n\nexport const DEV_POLICY: RemotePolicy = {\n deviceApproval: \"off\",\n answers: \"off\",\n promptPresence: \"off\",\n agentMode: \"auto\",\n};\n\n/** When `device`'s prompts need Face ID or Touch ID again; undefined when they never do. */\nexport function presenceDueAt(\n policy: RemotePolicy,\n device: TrustedDevice,\n): number | undefined {\n const setting = policy.promptPresence;\n if (setting === undefined || setting === \"off\" || !device.presenceKey) return undefined;\n return (device.presenceAt ?? device.approvedAt) + setting.maxAgeHours * 3_600_000;\n}\n\n/**\n * The app's own tools are allowed once without asking: the server already enforces the\n * app's `confirm: true`, and asking twice only teaches users to click through.\n */\nexport function isAppTool(ask: AgentAsk, appServer: string): boolean {\n return ask.kind === \"permission\" && ask.mcp?.server === appServer;\n}\n\n/**\n * The ask item's `approval`; `undefined` lets any trusted device answer without presence.\n * Where presence is wanted, the browser that approved `connect` here answers its own turn's\n * asks in the chat, since the loopback redirect put it on this computer; any other device\n * without presence leaves them to this computer.\n */\nexport function approvalFor(\n policy: RemotePolicy,\n ask: AgentAsk,\n device?: Pick<TrustedDevice, \"presenceKey\" | \"via\">,\n): \"presence\" | \"computer\" | \"device\" | undefined {\n if (ask.kind === \"question\" || policy.answers === \"off\") return undefined;\n if (policy.answers === \"computer\" || !device || device.presenceKey)\n return policy.answers;\n return device.via === \"pairing\" ? \"device\" : \"computer\";\n}\n\ntype Overlay = { readonly [K in keyof RemotePolicy]?: unknown };\n\nconst choose = <T extends string>(allowed: readonly T[], value: unknown, base: T): T =>\n allowed.find((candidate) => candidate === value) ?? base;\n\n/** `base` with the settings `parlor-host config` wrote to `file`; read afresh on every call. */\nexport async function loadPolicy(\n file: string,\n base: RemotePolicy,\n): Promise<RemotePolicy> {\n let overlay: Overlay;\n try {\n overlay = await jsonFile<Overlay>(file).read();\n } catch {\n return base;\n }\n return {\n deviceApproval: choose([\"local\", \"off\"], overlay.deviceApproval, base.deviceApproval),\n answers: choose([\"presence\", \"computer\", \"off\"], overlay.answers, base.answers),\n promptPresence: promptPresenceOf(overlay.promptPresence, base.promptPresence),\n agentMode: choose(\n [\"auto\", \"ask\"] as const,\n overlay.agentMode,\n base.agentMode ?? \"auto\",\n ),\n };\n}\n\nfunction promptPresenceOf(\n value: unknown,\n base: RemotePolicy[\"promptPresence\"],\n): RemotePolicy[\"promptPresence\"] {\n if (value === \"off\") return \"off\";\n const hours = (value as { readonly maxAgeHours?: unknown } | null)?.maxAgeHours;\n return typeof hours === \"number\" && Number.isFinite(hours) && hours > 0\n ? { maxAgeHours: hours }\n : base;\n}\n\nexport async function changePolicy(\n file: string,\n base: RemotePolicy,\n change: Partial<RemotePolicy>,\n): Promise<RemotePolicy> {\n await jsonFile<Overlay>(file).change((overlay) => Object.assign(overlay, change));\n return loadPolicy(file, base);\n}\n"]}
|
package/dist/prompter.d.ts
CHANGED
|
@@ -23,6 +23,11 @@ export interface LocalPrompter {
|
|
|
23
23
|
readonly appName: string;
|
|
24
24
|
readonly device: string;
|
|
25
25
|
}): void;
|
|
26
|
+
/** Passive: the app ended this computer's connection, and the agents here lost it too. */
|
|
27
|
+
disconnected?(input: {
|
|
28
|
+
readonly appName: string;
|
|
29
|
+
readonly removedFrom?: string | undefined;
|
|
30
|
+
}): void;
|
|
26
31
|
}
|
|
27
32
|
export declare const printable: (text: string) => string;
|
|
28
33
|
/** A `[y/N]` question on the terminal that ran a command; only yes is yes. */
|
package/dist/prompter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompter.d.ts","sourceRoot":"","sources":["../src/prompter.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,UAAU,CAAC,KAAK,EAAE;QAChB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS;YAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;KAC3C,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAChC,6FAA6F;IAC7F,WAAW,CAAC,KAAK,EAAE;QACjB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,GAAG,IAAI,CAAC;IACT,uDAAuD;IACvD,YAAY,CAAC,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"prompter.d.ts","sourceRoot":"","sources":["../src/prompter.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,UAAU,CAAC,KAAK,EAAE;QAChB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS;YAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;KAC3C,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAChC,6FAA6F;IAC7F,WAAW,CAAC,KAAK,EAAE;QACjB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,GAAG,IAAI,CAAC;IACT,uDAAuD;IACvD,YAAY,CAAC,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClF,0FAA0F;IAC1F,YAAY,CAAC,CAAC,KAAK,EAAE;QACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3C,GAAG,IAAI,CAAC;CACV;AAID,eAAO,MAAM,SAAS,SAAU,MAAM,WAAkC,CAAC;AAmBzE,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,aAAoB,MAAM,KAAG,OAAO,CAAC,OAAO,CAChB,CAAC;AAElD,0DAA0D;AAC1D,wBAAgB,gBAAgB,IAAI,aAAa,CA2BhD;AAoDD,wBAAgB,iBAAiB,IAAI,aAAa,CAyDjD"}
|
package/dist/prompter.js
CHANGED
|
@@ -30,6 +30,9 @@ export function terminalPrompter() {
|
|
|
30
30
|
const answer = await ask(`${printable(agent)} for ${printable(appName)} asks: ${printable(question)}\n${menu}\nChoice: `);
|
|
31
31
|
return choices[Number(answer?.trim()) - 1]?.id;
|
|
32
32
|
},
|
|
33
|
+
disconnected: ({ appName, removedFrom }) => {
|
|
34
|
+
process.stdout.write(`${printable(appName)} disconnected this computer.${removedFrom ? ` Removed it from ${printable(removedFrom)}.` : ""}\n`);
|
|
35
|
+
},
|
|
33
36
|
deviceLinked: ({ appName, device }) => {
|
|
34
37
|
process.stdout.write(`${printable(device)} was linked to your agents for ${printable(appName)}.\n`);
|
|
35
38
|
},
|
|
@@ -82,6 +85,18 @@ export function macDialogPrompter() {
|
|
|
82
85
|
clipped(`It can use your agents for ${appName}. Not you? Remove it under Devices in the app's Agent sidebar.`),
|
|
83
86
|
]);
|
|
84
87
|
},
|
|
88
|
+
disconnected: ({ appName, removedFrom }) => {
|
|
89
|
+
void osascript([
|
|
90
|
+
"on run argv",
|
|
91
|
+
`display notification (item 2 of argv) with title "${DIALOG_TITLE}" subtitle (item 1 of argv)`,
|
|
92
|
+
"end run",
|
|
93
|
+
], [
|
|
94
|
+
clipped(`${appName} disconnected this computer`),
|
|
95
|
+
clipped(removedFrom
|
|
96
|
+
? `Its sidebar and ${removedFrom} here can no longer use it.`
|
|
97
|
+
: "Its sidebar can no longer use your agents here."),
|
|
98
|
+
]);
|
|
99
|
+
},
|
|
85
100
|
turnStarted: ({ appName, device, agent, preview }) => {
|
|
86
101
|
void osascript([
|
|
87
102
|
"on run argv",
|