@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.
Files changed (64) hide show
  1. package/dist/acp.d.ts +21 -0
  2. package/dist/acp.d.ts.map +1 -1
  3. package/dist/acp.js +138 -12
  4. package/dist/acp.js.map +1 -1
  5. package/dist/agent-options.d.ts +17 -0
  6. package/dist/agent-options.d.ts.map +1 -0
  7. package/dist/agent-options.js +73 -0
  8. package/dist/agent-options.js.map +1 -0
  9. package/dist/agent-tools.d.ts +30 -0
  10. package/dist/agent-tools.d.ts.map +1 -0
  11. package/dist/agent-tools.js +71 -0
  12. package/dist/agent-tools.js.map +1 -0
  13. package/dist/control.d.ts +15 -0
  14. package/dist/control.d.ts.map +1 -1
  15. package/dist/control.js.map +1 -1
  16. package/dist/daemon.d.ts +10 -2
  17. package/dist/daemon.d.ts.map +1 -1
  18. package/dist/daemon.js +149 -14
  19. package/dist/daemon.js.map +1 -1
  20. package/dist/fold.d.ts.map +1 -1
  21. package/dist/fold.js +2 -0
  22. package/dist/fold.js.map +1 -1
  23. package/dist/grants.d.ts +9 -0
  24. package/dist/grants.d.ts.map +1 -1
  25. package/dist/grants.js.map +1 -1
  26. package/dist/learned.d.ts +23 -0
  27. package/dist/learned.d.ts.map +1 -0
  28. package/dist/learned.js +36 -0
  29. package/dist/learned.js.map +1 -0
  30. package/dist/main.js +105 -9
  31. package/dist/main.js.map +1 -1
  32. package/dist/mcp-proxy.d.ts +26 -3
  33. package/dist/mcp-proxy.d.ts.map +1 -1
  34. package/dist/mcp-proxy.js +180 -9
  35. package/dist/mcp-proxy.js.map +1 -1
  36. package/dist/pairing.d.ts +2 -0
  37. package/dist/pairing.d.ts.map +1 -1
  38. package/dist/pairing.js +25 -3
  39. package/dist/pairing.js.map +1 -1
  40. package/dist/paths.d.ts +2 -0
  41. package/dist/paths.d.ts.map +1 -1
  42. package/dist/paths.js +1 -0
  43. package/dist/paths.js.map +1 -1
  44. package/dist/policy.d.ts +12 -2
  45. package/dist/policy.d.ts.map +1 -1
  46. package/dist/policy.js +13 -3
  47. package/dist/policy.js.map +1 -1
  48. package/dist/prompter.d.ts +5 -0
  49. package/dist/prompter.d.ts.map +1 -1
  50. package/dist/prompter.js +15 -0
  51. package/dist/prompter.js.map +1 -1
  52. package/dist/spec-kit.d.ts +2 -1
  53. package/dist/spec-kit.d.ts.map +1 -1
  54. package/dist/spec-kit.js +6 -1
  55. package/dist/spec-kit.js.map +1 -1
  56. package/dist/testing/fake-agent.d.ts +12 -0
  57. package/dist/testing/fake-agent.d.ts.map +1 -1
  58. package/dist/testing/fake-agent.js +11 -1
  59. package/dist/testing/fake-agent.js.map +1 -1
  60. package/dist/turn.d.ts +7 -1
  61. package/dist/turn.d.ts.map +1 -1
  62. package/dist/turn.js +16 -3
  63. package/dist/turn.js.map +1 -1
  64. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"prompter.js","sourceRoot":"","sources":["../src/prompter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAuBhD,MAAM,WAAW,GAAG,wDAAwD,CAAC;AAE7E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAEzE,SAAS,GAAG,CAAC,QAAgB;IAC3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;YAClC,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACvB,IAAI,CAAC,QAAQ;gBAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,MAA0B,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAEnF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAAE,QAAgB,EAAoB,EAAE,CACnE,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;AAElD,0DAA0D;AAC1D,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,UAAU,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;YAC1D,MAAM,IAAI,GAAG,OAAO;iBACjB,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;iBAC9D,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,MAAM,GAAG,MAAM,GAAG,CACtB,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,SAAS,CAAC,OAAO,CAAC,UAAU,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,YAAY,CAChG,CAAC;YACF,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC;QACjD,CAAC;QACD,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;YACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,SAAS,CAAC,MAAM,CAAC,kCAAkC,SAAS,CAAC,OAAO,CAAC,KAAK,CAC9E,CAAC;QACJ,CAAC;QACD,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;YACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,SAAS,CAAC,KAAK,CAAC,mBAAmB,SAAS,CAAC,OAAO,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,OAAO,CAAC,IAAI,CAC9G,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAAG,aAAa,CAAC;AACnC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAC7B,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAC/B,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,eAAe;IACtC,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,KAAK;IACnD,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;AAEtB,SAAS,SAAS,CAChB,KAAwB,EACxB,IAAuB,EACvB,MAAoB;IAEpB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CACN,WAAW,EACX,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,EACzD,EAAE,MAAM,EAAE,EACV,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAC9D,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,MAAM,CACb,IAAY,EACZ,MAAyB,EACzB,MAAoB;IAEpB,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,OAAO,SAAS,CACd,MAAM,CAAC,MAAM,IAAI,kBAAkB;QACjC,CAAC,CAAC;YACE,aAAa;YACb,4DAA4D,YAAY,0BAA0B;YAClG,iCAAiC;YACjC,SAAS;SACV;QACH,CAAC,CAAC;YACE,aAAa;YACb,6DAA6D,YAAY,gCAAgC;YACzG,2CAA2C;YAC3C,yBAAyB;YACzB,SAAS;SACV,EACL,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,EAC3B,MAAM,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,UAAU,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;YAClE,MAAM,KAAK,GAAG,MAAM,MAAM,CACxB,GAAG,KAAK,QAAQ,OAAO,UAAU,QAAQ,EAAE,EAC3C,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EACrC,MAAM,CACP,CAAC;YACF,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC;QACvE,CAAC;QACD,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;YACpC,KAAK,SAAS,CACZ;gBACE,aAAa;gBACb,qDAAqD,YAAY,6BAA6B;gBAC9F,SAAS;aACV,EACD;gBACE,OAAO,CAAC,GAAG,MAAM,aAAa,CAAC;gBAC/B,OAAO,CACL,8BAA8B,OAAO,gEAAgE,CACtG;aACF,CACF,CAAC;QACJ,CAAC;QACD,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;YACnD,KAAK,SAAS,CACZ;gBACE,aAAa;gBACb,qDAAqD,YAAY,yDAAyD;gBAC1H,SAAS;aACV,EACD;gBACE,OAAO,CAAC,GAAG,KAAK,mBAAmB,OAAO,EAAE,CAAC;gBAC7C,OAAO,CAAC,MAAM,CAAC;gBACf,OAAO,CAAC,OAAO,CAAC;aACjB,CACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\n\n/** The computer's own surface, which no app, backend or web page can draw. */\nexport interface LocalPrompter {\n /** The chosen option id, or undefined when dismissed. */\n answerHere(input: {\n readonly appName: string;\n readonly agent: string;\n readonly question: string;\n readonly choices: readonly { readonly id: string; readonly label: string }[];\n readonly signal?: AbortSignal | undefined;\n }): Promise<string | undefined>;\n /** Passive: a turn from another device started here; none for a browser on this computer. */\n turnStarted(input: {\n readonly appName: string;\n readonly device: string;\n readonly agent: string;\n readonly preview: string;\n }): void;\n /** Passive: a device was linked to the agents here. */\n deviceLinked?(input: { readonly appName: string; readonly device: string }): void;\n}\n\nconst UNPRINTABLE = /[\\p{Cc}\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069]/gu;\n\nexport const printable = (text: string) => text.replace(UNPRINTABLE, \"\");\n\nfunction ask(question: string): Promise<string | undefined> {\n return new Promise((resolve) => {\n const lines = createInterface({ input: process.stdin, output: process.stdout });\n let answered = false;\n lines.question(question, (answer) => {\n answered = true;\n lines.close();\n resolve(answer);\n });\n lines.once(\"close\", () => {\n if (!answered) resolve(undefined);\n });\n });\n}\n\nconst yes = (answer: string | undefined) => /^y(es)?$/i.test(answer?.trim() ?? \"\");\n\n/** A `[y/N]` question on the terminal that ran a command; only yes is yes. */\nexport const askYesNo = async (question: string): Promise<boolean> =>\n yes(await ask(`${printable(question)} [y/N] `));\n\n/** Interactive `connect`: asks on the TTY that ran it. */\nexport function terminalPrompter(): LocalPrompter {\n return {\n answerHere: async ({ appName, agent, question, choices }) => {\n const menu = choices\n .map((choice, at) => ` ${at + 1}. ${printable(choice.label)}`)\n .join(\"\\n\");\n const answer = await ask(\n `${printable(agent)} for ${printable(appName)} asks: ${printable(question)}\\n${menu}\\nChoice: `,\n );\n return choices[Number(answer?.trim()) - 1]?.id;\n },\n deviceLinked: ({ appName, device }) => {\n process.stdout.write(\n `${printable(device)} was linked to your agents for ${printable(appName)}.\\n`,\n );\n },\n turnStarted: ({ appName, device, agent, preview }) => {\n process.stdout.write(\n `${printable(agent)} is working for ${printable(appName)} from ${printable(device)}: ${printable(preview)}\\n`,\n );\n },\n };\n}\n\nconst DIALOG_TITLE = \"Parlor Host\";\nconst MAX_DIALOG_BUTTONS = 3;\nconst MAX_DIALOG_TEXT = 600;\n\nconst clipped = (text: string) =>\n printable(text).length > MAX_DIALOG_TEXT\n ? `${printable(text).slice(0, MAX_DIALOG_TEXT)}...`\n : printable(text);\n\nfunction osascript(\n lines: readonly string[],\n args: readonly string[],\n signal?: AbortSignal,\n): Promise<string | undefined> {\n return new Promise((resolve) => {\n execFile(\n \"osascript\",\n [...lines.flatMap((line) => [\"-e\", line]), \"--\", ...args],\n { signal },\n (error, stdout) => resolve(error ? undefined : stdout.trim()),\n );\n });\n}\n\nfunction choose(\n text: string,\n labels: readonly string[],\n signal?: AbortSignal,\n): Promise<string | undefined> {\n const options = labels.map(clipped);\n return osascript(\n labels.length <= MAX_DIALOG_BUTTONS\n ? [\n \"on run argv\",\n `set reply to display dialog (item 1 of argv) with title \"${DIALOG_TITLE}\" buttons (rest of argv)`,\n \"return button returned of reply\",\n \"end run\",\n ]\n : [\n \"on run argv\",\n `set picked to choose from list (rest of argv) with title \"${DIALOG_TITLE}\" with prompt (item 1 of argv)`,\n \"if picked is false then error number -128\",\n \"return item 1 of picked\",\n \"end run\",\n ],\n [clipped(text), ...options],\n signal,\n );\n}\n\nexport function macDialogPrompter(): LocalPrompter {\n return {\n answerHere: async ({ appName, agent, question, choices, signal }) => {\n const label = await choose(\n `${agent} for ${appName} asks: ${question}`,\n choices.map((choice) => choice.label),\n signal,\n );\n return choices.find((choice) => clipped(choice.label) === label)?.id;\n },\n deviceLinked: ({ appName, device }) => {\n void osascript(\n [\n \"on run argv\",\n `display notification (item 2 of argv) with title \"${DIALOG_TITLE}\" subtitle (item 1 of argv)`,\n \"end run\",\n ],\n [\n clipped(`${device} was linked`),\n clipped(\n `It can use your agents for ${appName}. Not you? Remove it under Devices in the app's Agent sidebar.`,\n ),\n ],\n );\n },\n turnStarted: ({ appName, device, agent, preview }) => {\n void osascript(\n [\n \"on run argv\",\n `display notification (item 3 of argv) with title \"${DIALOG_TITLE}\" subtitle (item 1 of argv & \" from \" & item 2 of argv)`,\n \"end run\",\n ],\n [\n clipped(`${agent} is working for ${appName}`),\n clipped(device),\n clipped(preview),\n ],\n );\n },\n };\n}\n"]}
1
+ {"version":3,"file":"prompter.js","sourceRoot":"","sources":["../src/prompter.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\n\n/** The computer's own surface, which no app, backend or web page can draw. */\nexport interface LocalPrompter {\n /** The chosen option id, or undefined when dismissed. */\n answerHere(input: {\n readonly appName: string;\n readonly agent: string;\n readonly question: string;\n readonly choices: readonly { readonly id: string; readonly label: string }[];\n readonly signal?: AbortSignal | undefined;\n }): Promise<string | undefined>;\n /** Passive: a turn from another device started here; none for a browser on this computer. */\n turnStarted(input: {\n readonly appName: string;\n readonly device: string;\n readonly agent: string;\n readonly preview: string;\n }): void;\n /** Passive: a device was linked to the agents here. */\n deviceLinked?(input: { readonly appName: string; readonly device: string }): void;\n /** Passive: the app ended this computer's connection, and the agents here lost it too. */\n disconnected?(input: {\n readonly appName: string;\n readonly removedFrom?: string | undefined;\n }): void;\n}\n\nconst UNPRINTABLE = /[\\p{Cc}\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069]/gu;\n\nexport const printable = (text: string) => text.replace(UNPRINTABLE, \"\");\n\nfunction ask(question: string): Promise<string | undefined> {\n return new Promise((resolve) => {\n const lines = createInterface({ input: process.stdin, output: process.stdout });\n let answered = false;\n lines.question(question, (answer) => {\n answered = true;\n lines.close();\n resolve(answer);\n });\n lines.once(\"close\", () => {\n if (!answered) resolve(undefined);\n });\n });\n}\n\nconst yes = (answer: string | undefined) => /^y(es)?$/i.test(answer?.trim() ?? \"\");\n\n/** A `[y/N]` question on the terminal that ran a command; only yes is yes. */\nexport const askYesNo = async (question: string): Promise<boolean> =>\n yes(await ask(`${printable(question)} [y/N] `));\n\n/** Interactive `connect`: asks on the TTY that ran it. */\nexport function terminalPrompter(): LocalPrompter {\n return {\n answerHere: async ({ appName, agent, question, choices }) => {\n const menu = choices\n .map((choice, at) => ` ${at + 1}. ${printable(choice.label)}`)\n .join(\"\\n\");\n const answer = await ask(\n `${printable(agent)} for ${printable(appName)} asks: ${printable(question)}\\n${menu}\\nChoice: `,\n );\n return choices[Number(answer?.trim()) - 1]?.id;\n },\n disconnected: ({ appName, removedFrom }) => {\n process.stdout.write(\n `${printable(appName)} disconnected this computer.${removedFrom ? ` Removed it from ${printable(removedFrom)}.` : \"\"}\\n`,\n );\n },\n deviceLinked: ({ appName, device }) => {\n process.stdout.write(\n `${printable(device)} was linked to your agents for ${printable(appName)}.\\n`,\n );\n },\n turnStarted: ({ appName, device, agent, preview }) => {\n process.stdout.write(\n `${printable(agent)} is working for ${printable(appName)} from ${printable(device)}: ${printable(preview)}\\n`,\n );\n },\n };\n}\n\nconst DIALOG_TITLE = \"Parlor Host\";\nconst MAX_DIALOG_BUTTONS = 3;\nconst MAX_DIALOG_TEXT = 600;\n\nconst clipped = (text: string) =>\n printable(text).length > MAX_DIALOG_TEXT\n ? `${printable(text).slice(0, MAX_DIALOG_TEXT)}...`\n : printable(text);\n\nfunction osascript(\n lines: readonly string[],\n args: readonly string[],\n signal?: AbortSignal,\n): Promise<string | undefined> {\n return new Promise((resolve) => {\n execFile(\n \"osascript\",\n [...lines.flatMap((line) => [\"-e\", line]), \"--\", ...args],\n { signal },\n (error, stdout) => resolve(error ? undefined : stdout.trim()),\n );\n });\n}\n\nfunction choose(\n text: string,\n labels: readonly string[],\n signal?: AbortSignal,\n): Promise<string | undefined> {\n const options = labels.map(clipped);\n return osascript(\n labels.length <= MAX_DIALOG_BUTTONS\n ? [\n \"on run argv\",\n `set reply to display dialog (item 1 of argv) with title \"${DIALOG_TITLE}\" buttons (rest of argv)`,\n \"return button returned of reply\",\n \"end run\",\n ]\n : [\n \"on run argv\",\n `set picked to choose from list (rest of argv) with title \"${DIALOG_TITLE}\" with prompt (item 1 of argv)`,\n \"if picked is false then error number -128\",\n \"return item 1 of picked\",\n \"end run\",\n ],\n [clipped(text), ...options],\n signal,\n );\n}\n\nexport function macDialogPrompter(): LocalPrompter {\n return {\n answerHere: async ({ appName, agent, question, choices, signal }) => {\n const label = await choose(\n `${agent} for ${appName} asks: ${question}`,\n choices.map((choice) => choice.label),\n signal,\n );\n return choices.find((choice) => clipped(choice.label) === label)?.id;\n },\n deviceLinked: ({ appName, device }) => {\n void osascript(\n [\n \"on run argv\",\n `display notification (item 2 of argv) with title \"${DIALOG_TITLE}\" subtitle (item 1 of argv)`,\n \"end run\",\n ],\n [\n clipped(`${device} was linked`),\n clipped(\n `It can use your agents for ${appName}. Not you? Remove it under Devices in the app's Agent sidebar.`,\n ),\n ],\n );\n },\n disconnected: ({ appName, removedFrom }) => {\n void osascript(\n [\n \"on run argv\",\n `display notification (item 2 of argv) with title \"${DIALOG_TITLE}\" subtitle (item 1 of argv)`,\n \"end run\",\n ],\n [\n clipped(`${appName} disconnected this computer`),\n clipped(\n removedFrom\n ? `Its sidebar and ${removedFrom} here can no longer use it.`\n : \"Its sidebar can no longer use your agents here.\",\n ),\n ],\n );\n },\n turnStarted: ({ appName, device, agent, preview }) => {\n void osascript(\n [\n \"on run argv\",\n `display notification (item 3 of argv) with title \"${DIALOG_TITLE}\" subtitle (item 1 of argv & \" from \" & item 2 of argv)`,\n \"end run\",\n ],\n [\n clipped(`${agent} is working for ${appName}`),\n clipped(device),\n clipped(preview),\n ],\n );\n },\n };\n}\n"]}
@@ -9,7 +9,8 @@ export declare class Browser {
9
9
  readonly origin: string;
10
10
  constructor(origin: string);
11
11
  fetch(url: string, init?: RequestInit): Promise<Response>;
12
- consent(url: string, decision: "connect" | "cancel"): Promise<{
12
+ /** Answers the consent page as a browser would, sending its checked boxes but `uncheck`. */
13
+ consent(url: string, decision: "connect" | "cancel", uncheck?: readonly string[]): Promise<{
13
14
  readonly page: string;
14
15
  readonly answer: Response;
15
16
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"spec-kit.d.ts","sourceRoot":"","sources":["../src/spec-kit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spec-kit.d.ts","sourceRoot":"","sources":["../src/spec-kit.ts"],"names":[],"mappings":"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"}
package/dist/spec-kit.js CHANGED
@@ -40,9 +40,14 @@ export class Browser {
40
40
  }
41
41
  return response;
42
42
  }
43
- async consent(url, decision) {
43
+ /** Answers the consent page as a browser would, sending its checked boxes but `uncheck`. */
44
+ async consent(url, decision, uncheck = []) {
44
45
  const page = await (await this.fetch(url)).text();
45
46
  const fields = new URLSearchParams([...page.matchAll(/<input type="hidden" name="([^"]+)" value="([^"]*)">/g)].map((match) => [match[1], match[2].replaceAll("&amp;", "&")]));
47
+ for (const [, name, value] of page.matchAll(/<input type="checkbox" name="([^"]+)" value="([^"]*)" checked>/g)) {
48
+ if (!uncheck.includes(name))
49
+ fields.set(name, value);
50
+ }
46
51
  fields.set("decision", decision);
47
52
  const action = /<form method="post" action="([^"]+)"/.exec(page)?.[1] ?? "";
48
53
  const answer = await this.fetch(action, {
@@ -1 +1 @@
1
- {"version":3,"file":"spec-kit.js","sourceRoot":"","sources":["../src/spec-kit.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n createServer as createHttpServer,\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nimport {\n answerStatement,\n betterAuthProvider,\n deviceCommitment,\n parlorAuth,\n promptStatement,\n statementBytes,\n vouchStatement,\n} from \"@parlor/auth\";\nimport {\n type AnswerToSign,\n HOST_PROTOCOL,\n type ItemSignature,\n type PromptToSign,\n} from \"@parlor/chat\";\nimport { chat } from \"@parlor/chat/server\";\nimport { asId, defineAction } from \"@parlor/core\";\nimport { createRegistry } from \"@parlor/core/registry\";\nimport { createDatabase, type DatabaseHandle } from \"@parlor/data\";\nimport {\n createServer,\n type DeviceKeys,\n type DeviceVouch,\n type FrameworkServer,\n} from \"@parlor/server\";\nimport { betterAuth } from \"better-auth\";\nimport { memoryAdapter } from \"better-auth/adapters/memory\";\nimport { getAuthTables } from \"better-auth/db\";\nimport { serve } from \"srvx\";\nimport { z } from \"zod\";\n\nimport { hostAssertion } from \"./assertion.js\";\nimport type { AppGrant, HostKeys } from \"./grants.js\";\nimport type { TrustedDevice, TrustStore } from \"./trust.js\";\n\nexport const PASSWORD = \"correct horse battery staple\";\n\nconst digest = (password: string) => createHash(\"sha256\").update(password).digest(\"hex\");\n\nexport class Browser {\n readonly #cookies = new Map<string, string>();\n\n constructor(readonly origin: string) {}\n\n async fetch(url: string, init: RequestInit = {}): Promise<Response> {\n const headers = new Headers(init.headers);\n if (this.#cookies.size > 0) {\n headers.set(\n \"cookie\",\n [...this.#cookies].map(([name, value]) => `${name}=${value}`).join(\"; \"),\n );\n }\n if (init.method === \"POST\" && !headers.has(\"origin\"))\n headers.set(\"origin\", this.origin);\n const response = await fetch(new URL(url, this.origin), {\n ...init,\n headers,\n redirect: \"manual\",\n });\n for (const cookie of response.headers.getSetCookie()) {\n const [pair] = cookie.split(\";\");\n const at = pair!.indexOf(\"=\");\n this.#cookies.set(pair!.slice(0, at), pair!.slice(at + 1));\n }\n return response;\n }\n\n async consent(\n url: string,\n decision: \"connect\" | \"cancel\",\n ): Promise<{\n readonly page: string;\n readonly answer: Response;\n }> {\n const page = await (await this.fetch(url)).text();\n const fields = new URLSearchParams(\n [...page.matchAll(/<input type=\"hidden\" name=\"([^\"]+)\" value=\"([^\"]*)\">/g)].map(\n (match) => [match[1]!, match[2]!.replaceAll(\"&amp;\", \"&\")],\n ),\n );\n fields.set(\"decision\", decision);\n const action = /<form method=\"post\" action=\"([^\"]+)\"/.exec(page)?.[1] ?? \"\";\n const answer = await this.fetch(action, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: fields.toString(),\n });\n return { page, answer };\n }\n}\n\nexport interface SpecApp {\n readonly origin: string;\n readonly server: FrameworkServer;\n signUp(name: string): Promise<Browser>;\n close(): Promise<void>;\n}\n\nexport async function startSpecApp(): Promise<SpecApp> {\n const database: DatabaseHandle = await createDatabase({ dataDir: \"memory://\" });\n let server: FrameworkServer | undefined;\n const listener = serve({\n port: 0,\n hostname: \"127.0.0.1\",\n silent: true,\n fetch: (request) => server!.fetch(request),\n });\n await listener.ready();\n const origin = new URL(listener.url!).origin;\n const authOptions = parlorAuth({\n origin,\n signInPage: \"/sign-in\",\n appName: \"Hello\",\n secret: \"a-test-secret-that-is-long-enough-for-better-auth\",\n emailAndPassword: {\n enabled: true,\n password: {\n hash: (password) => Promise.resolve(digest(password)),\n verify: ({ hash, password }) => Promise.resolve(digest(password) === hash),\n },\n },\n });\n const tables = Object.values(getAuthTables(authOptions)).map((table) => [\n table.modelName,\n [],\n ]);\n const instance = betterAuth({\n ...authOptions,\n database: memoryAdapter(Object.fromEntries(tables)),\n });\n server = createServer({\n actions: createRegistry({\n listTasks: defineAction({\n description: \"List tasks.\",\n input: z.object({}),\n run: () => [],\n }),\n }),\n auth: betterAuthProvider({ instance }),\n plugins: [chat()],\n database: {\n read: () => database.read(),\n transaction: (run) => database.transaction(run),\n listen: (channel, handler, options) => database.listen(channel, handler, options),\n },\n });\n return {\n origin,\n server,\n async signUp(name) {\n const browser = new Browser(origin);\n const response = await browser.fetch(\"/_parlor/auth/sign-up/email\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n name,\n email: `${name.toLowerCase()}@example.com`,\n password: PASSWORD,\n }),\n });\n if (response.status !== 200) throw new Error(`sign-up answered ${response.status}`);\n return browser;\n },\n async close() {\n await listener.close(true);\n await server!.close();\n },\n };\n}\n\nexport function memoryHostKeys(): HostKeys & {\n readonly origins: () => readonly string[];\n} {\n const kept = new Map<string, { key: CryptoKey; kid: string; grantId: string }>();\n const sign = (key: CryptoKey) => async (payload: Uint8Array) =>\n Buffer.from(\n await crypto.subtle.sign(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n key,\n new Uint8Array(payload),\n ),\n ).toString(\"base64url\");\n return {\n origins: () => [...kept.keys()],\n async create(appOrigin) {\n const pair = await crypto.subtle.generateKey(\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n const { kty, crv, x, y } = await crypto.subtle.exportKey(\"jwk\", pair.publicKey);\n const publicKey = { kty: kty!, crv: crv!, x: x!, y: y! };\n const thumbprint = createHash(\"sha256\")\n .update(JSON.stringify({ crv, kty, x, y }))\n .digest(\"base64url\");\n return {\n publicKey,\n thumbprint,\n sign: sign(pair.privateKey),\n keep: async (grantId) => {\n kept.set(appOrigin, { key: pair.privateKey, kid: thumbprint, grantId });\n },\n };\n },\n async assert(appOrigin, request) {\n const key = kept.get(appOrigin);\n if (!key) throw new Error(`no key for ${appOrigin}`);\n return hostAssertion(sign(key.key), key, request);\n },\n async remove(appOrigin) {\n kept.delete(appOrigin);\n },\n };\n}\n\nexport function memoryTrust(): TrustStore & { readonly pins: () => TrustedDevice[] } {\n const pinned = new Map<string, TrustedDevice>();\n const keyOf = (appOrigin: string, deviceId: string) => `${appOrigin}\\n${deviceId}`;\n return {\n pins: () => [...pinned.values()],\n get: async (appOrigin, deviceId) => pinned.get(keyOf(appOrigin, deviceId)),\n list: async (appOrigin) =>\n [...pinned.entries()]\n .filter(([key]) => key.startsWith(`${appOrigin}\\n`))\n .map(([, device]) => device),\n pin: async (appOrigin, device) =>\n void pinned.set(keyOf(appOrigin, device.deviceId), device),\n forget: async (appOrigin, deviceId) => {\n for (const key of [...pinned.keys()]) {\n if (\n key.startsWith(`${appOrigin}\\n`) &&\n (deviceId === undefined || key.endsWith(`\\n${deviceId}`))\n ) {\n pinned.delete(key);\n }\n }\n },\n };\n}\n\nconst ES256 = { name: \"ECDSA\", hash: \"SHA-256\" } as const;\n\nexport class TestDevice {\n private constructor(\n readonly keys: DeviceKeys,\n private readonly signing: CryptoKey,\n private readonly presence: CryptoKey,\n ) {}\n\n static async create(\n id = \"device-1\",\n name = \"Chrome on macOS\",\n platform = \"web\",\n ): Promise<TestDevice> {\n const make = async () => {\n const pair = await crypto.subtle.generateKey(\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\", \"verify\"],\n );\n const { kty, crv, x, y } = await crypto.subtle.exportKey(\"jwk\", pair.publicKey);\n return {\n privateKey: pair.privateKey,\n publicKey: { kty: kty!, crv: crv!, x: x!, y: y! },\n };\n };\n const [signing, presence] = [await make(), await make()];\n return new TestDevice(\n {\n id: asId(\"device\", id),\n name,\n platform,\n signingKey: signing.publicKey,\n presenceKey: {\n format: \"es256-raw\",\n keyId: \"presence\",\n publicKey: presence.publicKey,\n },\n },\n signing.privateKey,\n presence.privateKey,\n );\n }\n\n get id() {\n return this.keys.id;\n }\n\n private async sign(\n key: CryptoKey,\n bytes: Uint8Array,\n presence: \"none\" | \"user\",\n at: number,\n ) {\n const signature = await crypto.subtle.sign(ES256, key, new Uint8Array(bytes));\n return {\n deviceId: this.id,\n at,\n signature: {\n keyId: \"key\",\n presence,\n format: \"es256-raw\",\n signature: Buffer.from(signature).toString(\"base64url\"),\n },\n } satisfies ItemSignature;\n }\n\n async signPrompt(\n app: string,\n prompt: PromptToSign,\n at: number,\n presence = false,\n ): Promise<ItemSignature> {\n const statement = await promptStatement(app, prompt, at);\n return this.sign(\n presence ? this.presence : this.signing,\n statementBytes(statement),\n presence ? \"user\" : \"none\",\n at,\n );\n }\n\n async signAnswer(\n app: string,\n answer: AnswerToSign,\n at: number,\n presence: boolean,\n ): Promise<ItemSignature> {\n const statement = await answerStatement(app, answer, at);\n return this.sign(\n presence ? this.presence : this.signing,\n statementBytes(statement),\n presence ? \"user\" : \"none\",\n at,\n );\n }\n\n /** This device's vouch for `device`, signed with its presence key unless `presence` is false. */\n async vouchFor(\n app: string,\n device: DeviceKeys,\n at: number,\n options: { readonly presence?: boolean; readonly commitment?: string } = {},\n ): Promise<DeviceVouch> {\n const commitment =\n options.commitment ??\n (await deviceCommitment(\n app,\n device.id,\n device.signingKey,\n device.presenceKey?.publicKey,\n ));\n const presence = options.presence ?? true;\n const { signature } = await this.sign(\n presence ? this.presence : this.signing,\n statementBytes(vouchStatement(app, this.id, device.id, commitment, at)),\n presence ? \"user\" : \"none\",\n at,\n );\n return { voucher: this.id, commitment, at, signature };\n }\n}\n\nexport interface StubApp {\n readonly origin: string;\n readonly seen: {\n readonly method: string;\n readonly path: string;\n readonly body: string;\n }[];\n close(): Promise<void>;\n}\n\nexport type Route = (\n request: IncomingMessage,\n body: string,\n response: ServerResponse,\n) => boolean;\n\nexport async function stubApp(route: Route): Promise<StubApp> {\n const seen: StubApp[\"seen\"][number][] = [];\n const server: Server = createHttpServer((request, response) => {\n let body = \"\";\n request.setEncoding(\"utf8\");\n request.on(\"data\", (chunk: string) => (body += chunk));\n request.on(\"end\", () => {\n const path = new URL(request.url ?? \"/\", \"http://stub\").pathname;\n seen.push({ method: request.method ?? \"GET\", path, body });\n if (!route(request, body, response)) response.writeHead(404).end();\n });\n });\n await new Promise<void>((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n return {\n origin: `http://127.0.0.1:${(server.address() as AddressInfo).port}`,\n seen,\n close: () =>\n new Promise((resolve) => {\n server.closeAllConnections();\n server.close(() => resolve());\n }),\n };\n}\n\nexport const json = (response: ServerResponse, value: unknown, status = 200) =>\n response\n .writeHead(status, { \"content-type\": \"application/json\" })\n .end(JSON.stringify(value));\n\nexport const welcome = {\n id: \"h\",\n accepted: true,\n protocol: HOST_PROTOCOL,\n leaseMs: 60_000,\n heartbeatMs: 30_000,\n};\n\nexport function hostRoutes(options: {\n readonly wake: boolean;\n readonly turn?: () => unknown;\n}): Route {\n let claimed = false;\n return (request, body, response) => {\n const path = new URL(request.url ?? \"/\", \"http://stub\").pathname;\n const action = path.startsWith(\"/_parlor/chat/actions/\")\n ? path.split(\"/\").at(-1)\n : undefined;\n if (action === \"hostHello\")\n return (json(response, { data: welcome, tags: [] }), true);\n if (action === \"hostClaim\") {\n if (options.turn && !claimed) {\n claimed = true;\n return (json(response, { data: options.turn(), tags: [] }), true);\n }\n return (\n json(response, { error: { code: \"not_found\", message: \"No turn.\" } }, 404), true\n );\n }\n if (action === \"hostSignals\") return (json(response, { data: [], tags: [] }), true);\n if (action) {\n return (\n json(response, {\n data: {\n id: \"t1\",\n leaseUntil: Date.now() + 60_000,\n token: { token: \"turn-2\", expiresAt: Date.now() + 60_000 },\n },\n tags: [],\n }),\n true\n );\n }\n if (path === \"/_parlor/sync/token\") {\n return (\n json(response, {\n protocol: 1,\n transport: \"sse\",\n endpoint: \"/_parlor/sync/stream\",\n pollIntervalMs: 2_000,\n }),\n true\n );\n }\n if (path === \"/_parlor/sync/stream\") {\n response.writeHead(200, { \"content-type\": \"text/event-stream\" });\n response.write(\n `event: handshake\\ndata: ${JSON.stringify({ capabilities: [], keepAliveMs: 10_000 })}\\n\\n`,\n );\n if (options.wake) {\n const events = [{ version: 1, id: \"e1\", tags: [[\"parlor_chat\", \"t1\"]] }];\n response.write(\n `event: message\\ndata: ${JSON.stringify({ events, cursor: \"1.e1\" })}\\n\\n`,\n );\n }\n return true;\n }\n if (path === \"/_parlor/mcp\") {\n const message = JSON.parse(body) as { id?: number; method: string };\n if (message.id === undefined) return (response.writeHead(202).end(), true);\n const result =\n message.method === \"initialize\"\n ? {\n protocolVersion: \"2025-06-18\",\n capabilities: { tools: {} },\n serverInfo: { name: \"stub\", version: \"1\" },\n }\n : message.method === \"tools/list\"\n ? { tools: [] }\n : { content: [{ type: \"text\", text: \"[]\" }] };\n return (json(response, { jsonrpc: \"2.0\", id: message.id, result }), true);\n }\n return false;\n };\n}\n\nexport const claimedTurn = (\n turnId: string,\n agentId: string,\n context: string,\n text: string,\n) => ({\n id: \"t1\",\n turnId,\n agent: { hostId: \"h-A\", agentId },\n clientId: \"tab\",\n prompt: {\n id: `p-${turnId}`,\n threadId: \"t1\",\n turnId,\n seq: 1,\n at: Date.now(),\n author: \"user\",\n kind: \"prompt\",\n body: { text, agent: { hostId: \"h-A\", agentId } },\n },\n device: undefined,\n context,\n token: { token: \"turn-1\", expiresAt: Date.now() + 60_000 },\n leaseUntil: Date.now() + 60_000,\n});\n\nexport const grantFor = (origin: string, name: string, workspace: string): AppGrant => ({\n appOrigin: origin,\n appName: name,\n grantId: asId(\"grant\", `host:${name}`),\n hostId: asId(\"host\", `h-${name}`),\n account: { userId: asId(\"user\", \"alice\"), name: \"Alice\", email: \"alice@example.com\" },\n workspace,\n createdAt: 1,\n});\n"]}
1
+ {"version":3,"file":"spec-kit.js","sourceRoot":"","sources":["../src/spec-kit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,YAAY,IAAI,gBAAgB,GAIjC,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,aAAa,GAGd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAuB,MAAM,cAAc,CAAC;AACnE,OAAO,EACL,YAAY,GAIb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI/C,MAAM,CAAC,MAAM,QAAQ,GAAG,8BAA8B,CAAC;AAEvD,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEzF,MAAM,OAAO,OAAO;IAGG,MAAM;IAFlB,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE9C,YAAqB,MAAc;sBAAd,MAAM;IAAW,CAAC;IAEvC,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,IAAI,GAAgB,EAAE;QAC7C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CACT,QAAQ,EACR,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACzE,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;YACtD,GAAG,IAAI;YACP,OAAO;YACP,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QACH,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,EAAE,GAAG,IAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,4FAA4F;IAC5F,KAAK,CAAC,OAAO,CACX,GAAW,EACX,QAA8B,EAC9B,OAAO,GAAsB,EAAE;QAK/B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,eAAe,CAChC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,uDAAuD,CAAC,CAAC,CAAC,GAAG,CAC7E,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAC3D,CACF,CAAC;QACF,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CACzC,iEAAiE,CAClE,EAAE,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAK,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,IAAK,EAAE,KAAM,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,sCAAsC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;YAChE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;SACxB,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;CACF;AASD,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,QAAQ,GAAmB,MAAM,cAAc,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;IAChF,IAAI,MAAmC,CAAC;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,EAAE,CAAC;QACP,QAAQ,EAAE,WAAW;QACrB,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,MAAO,CAAC,KAAK,CAAC,OAAO,CAAC;KAC3C,CAAC,CAAC;IACH,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAI,CAAC,CAAC,MAAM,CAAC;IAC7C,MAAM,WAAW,GAAG,UAAU,CAAC;QAC7B,MAAM;QACN,UAAU,EAAE,UAAU;QACtB,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,mDAAmD;QAC3D,gBAAgB,EAAE;YAChB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE;gBACR,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACrD,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;aAC3E;SACF;KACF,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QACtE,KAAK,CAAC,SAAS;QACf,EAAE;KACH,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,UAAU,CAAC;QAC1B,GAAG,WAAW;QACd,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;KACpD,CAAC,CAAC;IACH,MAAM,GAAG,YAAY,CAAC;QACpB,OAAO,EAAE,cAAc,CAAC;YACtB,SAAS,EAAE,YAAY,CAAC;gBACtB,WAAW,EAAE,aAAa;gBAC1B,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnB,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE;aACd,CAAC;SACH,CAAC;QACF,IAAI,EAAE,kBAAkB,CAAC,EAAE,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC;QACjB,QAAQ,EAAE;YACR,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE;YAC3B,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC;YAC/C,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;SAClF;KACF,CAAC,CAAC;IACH,OAAO;QACL,MAAM;QACN,MAAM;QACN,KAAK,CAAC,MAAM,CAAC,IAAI;YACf,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE;gBAClE,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,IAAI;oBACJ,KAAK,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc;oBAC1C,QAAQ,EAAE,QAAQ;iBACnB,CAAC;aACH,CAAC,CAAC;YACH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACpF,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,KAAK;YACT,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,MAAM,MAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc;IAG5B,MAAM,IAAI,GAAG,IAAI,GAAG,EAA4D,CAAC;IACjF,MAAM,IAAI,GAAG,CAAC,GAAc,EAAE,EAAE,CAAC,KAAK,EAAE,OAAmB,EAAE,EAAE,CAC7D,MAAM,CAAC,IAAI,CACT,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CACtB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAClC,GAAG,EACH,IAAI,UAAU,CAAC,OAAO,CAAC,CACxB,CACF,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC1B,OAAO;QACL,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,KAAK,CAAC,MAAM,CAAC,SAAS;YACpB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAC1C,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,EACtC,KAAK,EACL,CAAC,MAAM,EAAE,QAAQ,CAAC,CACnB,CAAC;YACF,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAChF,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,GAAI,EAAE,GAAG,EAAE,GAAI,EAAE,CAAC,EAAE,CAAE,EAAE,CAAC,EAAE,CAAE,EAAE,CAAC;YACzD,MAAM,UAAU,GAAG,UAAU,CAAC,QAAQ,CAAC;iBACpC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;iBAC1C,MAAM,CAAC,WAAW,CAAC,CAAC;YACvB,OAAO;gBACL,SAAS;gBACT,UAAU;gBACV,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;gBAC3B,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;oBACtB,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC1E,CAAC;aACF,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAChC,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,cAAc,SAAS,EAAE,CAAC,CAAC;YACrD,OAAO,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,SAAS;YACpB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,MAAM,KAAK,GAAG,CAAC,SAAiB,EAAE,QAAgB,EAAE,EAAE,CAAC,GAAG,SAAS,KAAK,QAAQ,EAAE,CAAC;IACnF,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAChC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC1E,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CACxB,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;aAClB,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,SAAS,IAAI,CAAC,CAAC;aACnD,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC;QAChC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CAC/B,KAAK,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC5D,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE;YACpC,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;gBACrC,IACE,GAAG,CAAC,UAAU,CAAC,GAAG,SAAS,IAAI,CAAC;oBAChC,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC,EACzD,CAAC;oBACD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACrB,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAW,CAAC;AAE1D,MAAM,OAAO,UAAU;IAEV,IAAI;IACI,OAAO;IACP,QAAQ;IAH3B,YACW,IAAgB,EACR,OAAkB,EAClB,QAAmB;oBAF3B,IAAI;uBACI,OAAO;wBACP,QAAQ;IACxB,CAAC;IAEJ,MAAM,CAAC,KAAK,CAAC,MAAM,CACjB,EAAE,GAAG,UAAU,EACf,IAAI,GAAG,iBAAiB,EACxB,QAAQ,GAAG,KAAK;QAEhB,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;YACtB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAC1C,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,EACtC,IAAI,EACJ,CAAC,MAAM,EAAE,QAAQ,CAAC,CACnB,CAAC;YACF,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAChF,OAAO;gBACL,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,SAAS,EAAE,EAAE,GAAG,EAAE,GAAI,EAAE,GAAG,EAAE,GAAI,EAAE,CAAC,EAAE,CAAE,EAAE,CAAC,EAAE,CAAE,EAAE;aAClD,CAAC;QACJ,CAAC,CAAC;QACF,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;QACzD,OAAO,IAAI,UAAU,CACnB;YACE,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YACtB,IAAI;YACJ,QAAQ;YACR,UAAU,EAAE,OAAO,CAAC,SAAS;YAC7B,WAAW,EAAE;gBACX,MAAM,EAAE,WAAW;gBACnB,KAAK,EAAE,UAAU;gBACjB,SAAS,EAAE,QAAQ,CAAC,SAAS;aAC9B;SACF,EACD,OAAO,CAAC,UAAU,EAClB,QAAQ,CAAC,UAAU,CACpB,CAAC;IACJ,CAAC;IAED,IAAI,EAAE;QACJ,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,IAAI,CAChB,GAAc,EACd,KAAiB,EACjB,QAAyB,EACzB,EAAU;QAEV,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9E,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,EAAE;YACjB,EAAE;YACF,SAAS,EAAE;gBACT,KAAK,EAAE,KAAK;gBACZ,QAAQ;gBACR,MAAM,EAAE,WAAW;gBACnB,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;aACxD;SACsB,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,UAAU,CACd,GAAW,EACX,MAAoB,EACpB,EAAU,EACV,QAAQ,GAAG,KAAK;QAEhB,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,IAAI,CACd,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EACvC,cAAc,CAAC,SAAS,CAAC,EACzB,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAC1B,EAAE,CACH,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CACd,GAAW,EACX,MAAoB,EACpB,EAAU,EACV,QAAiB;QAEjB,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,IAAI,CACd,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EACvC,cAAc,CAAC,SAAS,CAAC,EACzB,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAC1B,EAAE,CACH,CAAC;IACJ,CAAC;IAED,iGAAiG;IACjG,KAAK,CAAC,QAAQ,CACZ,GAAW,EACX,MAAkB,EAClB,EAAU,EACV,OAAO,GAAkE,EAAE;QAE3E,MAAM,UAAU,GACd,OAAO,CAAC,UAAU;YAClB,CAAC,MAAM,gBAAgB,CACrB,GAAG,EACH,MAAM,CAAC,EAAE,EACT,MAAM,CAAC,UAAU,EACjB,MAAM,CAAC,WAAW,EAAE,SAAS,CAC9B,CAAC,CAAC;QACL,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;QAC1C,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CACnC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EACvC,cAAc,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,EACvE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAC1B,EAAE,CACH,CAAC;QACF,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;IACzD,CAAC;CACF;AAkBD,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAY;IACxC,MAAM,IAAI,GAA8B,EAAE,CAAC;IAC3C,MAAM,MAAM,GAAW,gBAAgB,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QAC5D,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACrB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC;YACjE,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC;gBAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;QACrE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,OAAO;QACL,MAAM,EAAE,oBAAqB,MAAM,CAAC,OAAO,EAAkB,CAAC,IAAI,EAAE;QACpE,IAAI;QACJ,KAAK,EAAE,GAAG,EAAE,CACV,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YACtB,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC7B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAChC,CAAC,CAAC;KACL,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,QAAwB,EAAE,KAAc,EAAE,MAAM,GAAG,GAAG,EAAE,EAAE,CAC7E,QAAQ;KACL,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;KACzD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,EAAE,EAAE,GAAG;IACP,QAAQ,EAAE,IAAI;IACd,QAAQ,EAAE,aAAa;IACvB,OAAO,EAAE,MAAM;IACf,WAAW,EAAE,MAAM;CACpB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,OAG1B;IACC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QACjC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,wBAAwB,CAAC;YACtD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACxB,CAAC,CAAC,SAAS,CAAC;QACd,IAAI,MAAM,KAAK,WAAW;YACxB,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QAC7D,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;YAC3B,IAAI,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC7B,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YACpE,CAAC;YACD,OAAO,CACL,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,CACjF,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,KAAK,aAAa;YAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QACpF,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CACL,IAAI,CAAC,QAAQ,EAAE;gBACb,IAAI,EAAE;oBACJ,EAAE,EAAE,IAAI;oBACR,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM;oBAC/B,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE;iBAC3D;gBACD,IAAI,EAAE,EAAE;aACT,CAAC;gBACF,IAAI,CACL,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,KAAK,qBAAqB,EAAE,CAAC;YACnC,OAAO,CACL,IAAI,CAAC,QAAQ,EAAE;gBACb,QAAQ,EAAE,CAAC;gBACX,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,sBAAsB;gBAChC,cAAc,EAAE,KAAK;aACtB,CAAC;gBACF,IAAI,CACL,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,KAAK,sBAAsB,EAAE,CAAC;YACpC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACjE,QAAQ,CAAC,KAAK,CACZ,2BAA2B,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,MAAM,CAC3F,CAAC;YACF,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzE,QAAQ,CAAC,KAAK,CACZ,yBAAyB,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,MAAM,CAC1E,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoC,CAAC;YACpE,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS;gBAAE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;YAC3E,MAAM,MAAM,GACV,OAAO,CAAC,MAAM,KAAK,YAAY;gBAC7B,CAAC,CAAC;oBACE,eAAe,EAAE,YAAY;oBAC7B,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;oBAC3B,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE;iBAC3C;gBACH,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,YAAY;oBAC/B,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;oBACf,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,MAAc,EACd,OAAe,EACf,OAAe,EACf,IAAY,EACZ,EAAE,CAAC,CAAC;IACJ,EAAE,EAAE,IAAI;IACR,MAAM;IACN,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE;IACjC,QAAQ,EAAE,KAAK;IACf,MAAM,EAAE;QACN,EAAE,EAAE,KAAK,MAAM,EAAE;QACjB,QAAQ,EAAE,IAAI;QACd,MAAM;QACN,GAAG,EAAE,CAAC;QACN,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;QACd,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;KAClD;IACD,MAAM,EAAE,SAAS;IACjB,OAAO;IACP,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE;IAC1D,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM;CAChC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAE,IAAY,EAAE,SAAiB,EAAY,EAAE,CAAC,CAAC;IACtF,SAAS,EAAE,MAAM;IACjB,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAC;IACtC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;IACjC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE;IACrF,SAAS;IACT,SAAS,EAAE,CAAC;CACb,CAAC,CAAC","sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n createServer as createHttpServer,\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nimport {\n answerStatement,\n betterAuthProvider,\n deviceCommitment,\n parlorAuth,\n promptStatement,\n statementBytes,\n vouchStatement,\n} from \"@parlor/auth\";\nimport {\n type AnswerToSign,\n HOST_PROTOCOL,\n type ItemSignature,\n type PromptToSign,\n} from \"@parlor/chat\";\nimport { chat } from \"@parlor/chat/server\";\nimport { asId, defineAction } from \"@parlor/core\";\nimport { createRegistry } from \"@parlor/core/registry\";\nimport { createDatabase, type DatabaseHandle } from \"@parlor/data\";\nimport {\n createServer,\n type DeviceKeys,\n type DeviceVouch,\n type FrameworkServer,\n} from \"@parlor/server\";\nimport { betterAuth } from \"better-auth\";\nimport { memoryAdapter } from \"better-auth/adapters/memory\";\nimport { getAuthTables } from \"better-auth/db\";\nimport { serve } from \"srvx\";\nimport { z } from \"zod\";\n\nimport { hostAssertion } from \"./assertion.js\";\nimport type { AppGrant, HostKeys } from \"./grants.js\";\nimport type { TrustedDevice, TrustStore } from \"./trust.js\";\n\nexport const PASSWORD = \"correct horse battery staple\";\n\nconst digest = (password: string) => createHash(\"sha256\").update(password).digest(\"hex\");\n\nexport class Browser {\n readonly #cookies = new Map<string, string>();\n\n constructor(readonly origin: string) {}\n\n async fetch(url: string, init: RequestInit = {}): Promise<Response> {\n const headers = new Headers(init.headers);\n if (this.#cookies.size > 0) {\n headers.set(\n \"cookie\",\n [...this.#cookies].map(([name, value]) => `${name}=${value}`).join(\"; \"),\n );\n }\n if (init.method === \"POST\" && !headers.has(\"origin\"))\n headers.set(\"origin\", this.origin);\n const response = await fetch(new URL(url, this.origin), {\n ...init,\n headers,\n redirect: \"manual\",\n });\n for (const cookie of response.headers.getSetCookie()) {\n const [pair] = cookie.split(\";\");\n const at = pair!.indexOf(\"=\");\n this.#cookies.set(pair!.slice(0, at), pair!.slice(at + 1));\n }\n return response;\n }\n\n /** Answers the consent page as a browser would, sending its checked boxes but `uncheck`. */\n async consent(\n url: string,\n decision: \"connect\" | \"cancel\",\n uncheck: readonly string[] = [],\n ): Promise<{\n readonly page: string;\n readonly answer: Response;\n }> {\n const page = await (await this.fetch(url)).text();\n const fields = new URLSearchParams(\n [...page.matchAll(/<input type=\"hidden\" name=\"([^\"]+)\" value=\"([^\"]*)\">/g)].map(\n (match) => [match[1]!, match[2]!.replaceAll(\"&amp;\", \"&\")],\n ),\n );\n for (const [, name, value] of page.matchAll(\n /<input type=\"checkbox\" name=\"([^\"]+)\" value=\"([^\"]*)\" checked>/g,\n )) {\n if (!uncheck.includes(name!)) fields.set(name!, value!);\n }\n fields.set(\"decision\", decision);\n const action = /<form method=\"post\" action=\"([^\"]+)\"/.exec(page)?.[1] ?? \"\";\n const answer = await this.fetch(action, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: fields.toString(),\n });\n return { page, answer };\n }\n}\n\nexport interface SpecApp {\n readonly origin: string;\n readonly server: FrameworkServer;\n signUp(name: string): Promise<Browser>;\n close(): Promise<void>;\n}\n\nexport async function startSpecApp(): Promise<SpecApp> {\n const database: DatabaseHandle = await createDatabase({ dataDir: \"memory://\" });\n let server: FrameworkServer | undefined;\n const listener = serve({\n port: 0,\n hostname: \"127.0.0.1\",\n silent: true,\n fetch: (request) => server!.fetch(request),\n });\n await listener.ready();\n const origin = new URL(listener.url!).origin;\n const authOptions = parlorAuth({\n origin,\n signInPage: \"/sign-in\",\n appName: \"Hello\",\n secret: \"a-test-secret-that-is-long-enough-for-better-auth\",\n emailAndPassword: {\n enabled: true,\n password: {\n hash: (password) => Promise.resolve(digest(password)),\n verify: ({ hash, password }) => Promise.resolve(digest(password) === hash),\n },\n },\n });\n const tables = Object.values(getAuthTables(authOptions)).map((table) => [\n table.modelName,\n [],\n ]);\n const instance = betterAuth({\n ...authOptions,\n database: memoryAdapter(Object.fromEntries(tables)),\n });\n server = createServer({\n actions: createRegistry({\n listTasks: defineAction({\n description: \"List tasks.\",\n input: z.object({}),\n run: () => [],\n }),\n }),\n auth: betterAuthProvider({ instance }),\n plugins: [chat()],\n database: {\n read: () => database.read(),\n transaction: (run) => database.transaction(run),\n listen: (channel, handler, options) => database.listen(channel, handler, options),\n },\n });\n return {\n origin,\n server,\n async signUp(name) {\n const browser = new Browser(origin);\n const response = await browser.fetch(\"/_parlor/auth/sign-up/email\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n name,\n email: `${name.toLowerCase()}@example.com`,\n password: PASSWORD,\n }),\n });\n if (response.status !== 200) throw new Error(`sign-up answered ${response.status}`);\n return browser;\n },\n async close() {\n await listener.close(true);\n await server!.close();\n },\n };\n}\n\nexport function memoryHostKeys(): HostKeys & {\n readonly origins: () => readonly string[];\n} {\n const kept = new Map<string, { key: CryptoKey; kid: string; grantId: string }>();\n const sign = (key: CryptoKey) => async (payload: Uint8Array) =>\n Buffer.from(\n await crypto.subtle.sign(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n key,\n new Uint8Array(payload),\n ),\n ).toString(\"base64url\");\n return {\n origins: () => [...kept.keys()],\n async create(appOrigin) {\n const pair = await crypto.subtle.generateKey(\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n const { kty, crv, x, y } = await crypto.subtle.exportKey(\"jwk\", pair.publicKey);\n const publicKey = { kty: kty!, crv: crv!, x: x!, y: y! };\n const thumbprint = createHash(\"sha256\")\n .update(JSON.stringify({ crv, kty, x, y }))\n .digest(\"base64url\");\n return {\n publicKey,\n thumbprint,\n sign: sign(pair.privateKey),\n keep: async (grantId) => {\n kept.set(appOrigin, { key: pair.privateKey, kid: thumbprint, grantId });\n },\n };\n },\n async assert(appOrigin, request) {\n const key = kept.get(appOrigin);\n if (!key) throw new Error(`no key for ${appOrigin}`);\n return hostAssertion(sign(key.key), key, request);\n },\n async remove(appOrigin) {\n kept.delete(appOrigin);\n },\n };\n}\n\nexport function memoryTrust(): TrustStore & { readonly pins: () => TrustedDevice[] } {\n const pinned = new Map<string, TrustedDevice>();\n const keyOf = (appOrigin: string, deviceId: string) => `${appOrigin}\\n${deviceId}`;\n return {\n pins: () => [...pinned.values()],\n get: async (appOrigin, deviceId) => pinned.get(keyOf(appOrigin, deviceId)),\n list: async (appOrigin) =>\n [...pinned.entries()]\n .filter(([key]) => key.startsWith(`${appOrigin}\\n`))\n .map(([, device]) => device),\n pin: async (appOrigin, device) =>\n void pinned.set(keyOf(appOrigin, device.deviceId), device),\n forget: async (appOrigin, deviceId) => {\n for (const key of [...pinned.keys()]) {\n if (\n key.startsWith(`${appOrigin}\\n`) &&\n (deviceId === undefined || key.endsWith(`\\n${deviceId}`))\n ) {\n pinned.delete(key);\n }\n }\n },\n };\n}\n\nconst ES256 = { name: \"ECDSA\", hash: \"SHA-256\" } as const;\n\nexport class TestDevice {\n private constructor(\n readonly keys: DeviceKeys,\n private readonly signing: CryptoKey,\n private readonly presence: CryptoKey,\n ) {}\n\n static async create(\n id = \"device-1\",\n name = \"Chrome on macOS\",\n platform = \"web\",\n ): Promise<TestDevice> {\n const make = async () => {\n const pair = await crypto.subtle.generateKey(\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\", \"verify\"],\n );\n const { kty, crv, x, y } = await crypto.subtle.exportKey(\"jwk\", pair.publicKey);\n return {\n privateKey: pair.privateKey,\n publicKey: { kty: kty!, crv: crv!, x: x!, y: y! },\n };\n };\n const [signing, presence] = [await make(), await make()];\n return new TestDevice(\n {\n id: asId(\"device\", id),\n name,\n platform,\n signingKey: signing.publicKey,\n presenceKey: {\n format: \"es256-raw\",\n keyId: \"presence\",\n publicKey: presence.publicKey,\n },\n },\n signing.privateKey,\n presence.privateKey,\n );\n }\n\n get id() {\n return this.keys.id;\n }\n\n private async sign(\n key: CryptoKey,\n bytes: Uint8Array,\n presence: \"none\" | \"user\",\n at: number,\n ) {\n const signature = await crypto.subtle.sign(ES256, key, new Uint8Array(bytes));\n return {\n deviceId: this.id,\n at,\n signature: {\n keyId: \"key\",\n presence,\n format: \"es256-raw\",\n signature: Buffer.from(signature).toString(\"base64url\"),\n },\n } satisfies ItemSignature;\n }\n\n async signPrompt(\n app: string,\n prompt: PromptToSign,\n at: number,\n presence = false,\n ): Promise<ItemSignature> {\n const statement = await promptStatement(app, prompt, at);\n return this.sign(\n presence ? this.presence : this.signing,\n statementBytes(statement),\n presence ? \"user\" : \"none\",\n at,\n );\n }\n\n async signAnswer(\n app: string,\n answer: AnswerToSign,\n at: number,\n presence: boolean,\n ): Promise<ItemSignature> {\n const statement = await answerStatement(app, answer, at);\n return this.sign(\n presence ? this.presence : this.signing,\n statementBytes(statement),\n presence ? \"user\" : \"none\",\n at,\n );\n }\n\n /** This device's vouch for `device`, signed with its presence key unless `presence` is false. */\n async vouchFor(\n app: string,\n device: DeviceKeys,\n at: number,\n options: { readonly presence?: boolean; readonly commitment?: string } = {},\n ): Promise<DeviceVouch> {\n const commitment =\n options.commitment ??\n (await deviceCommitment(\n app,\n device.id,\n device.signingKey,\n device.presenceKey?.publicKey,\n ));\n const presence = options.presence ?? true;\n const { signature } = await this.sign(\n presence ? this.presence : this.signing,\n statementBytes(vouchStatement(app, this.id, device.id, commitment, at)),\n presence ? \"user\" : \"none\",\n at,\n );\n return { voucher: this.id, commitment, at, signature };\n }\n}\n\nexport interface StubApp {\n readonly origin: string;\n readonly seen: {\n readonly method: string;\n readonly path: string;\n readonly body: string;\n }[];\n close(): Promise<void>;\n}\n\nexport type Route = (\n request: IncomingMessage,\n body: string,\n response: ServerResponse,\n) => boolean;\n\nexport async function stubApp(route: Route): Promise<StubApp> {\n const seen: StubApp[\"seen\"][number][] = [];\n const server: Server = createHttpServer((request, response) => {\n let body = \"\";\n request.setEncoding(\"utf8\");\n request.on(\"data\", (chunk: string) => (body += chunk));\n request.on(\"end\", () => {\n const path = new URL(request.url ?? \"/\", \"http://stub\").pathname;\n seen.push({ method: request.method ?? \"GET\", path, body });\n if (!route(request, body, response)) response.writeHead(404).end();\n });\n });\n await new Promise<void>((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n return {\n origin: `http://127.0.0.1:${(server.address() as AddressInfo).port}`,\n seen,\n close: () =>\n new Promise((resolve) => {\n server.closeAllConnections();\n server.close(() => resolve());\n }),\n };\n}\n\nexport const json = (response: ServerResponse, value: unknown, status = 200) =>\n response\n .writeHead(status, { \"content-type\": \"application/json\" })\n .end(JSON.stringify(value));\n\nexport const welcome = {\n id: \"h\",\n accepted: true,\n protocol: HOST_PROTOCOL,\n leaseMs: 60_000,\n heartbeatMs: 30_000,\n};\n\nexport function hostRoutes(options: {\n readonly wake: boolean;\n readonly turn?: () => unknown;\n}): Route {\n let claimed = false;\n return (request, body, response) => {\n const path = new URL(request.url ?? \"/\", \"http://stub\").pathname;\n const action = path.startsWith(\"/_parlor/chat/actions/\")\n ? path.split(\"/\").at(-1)\n : undefined;\n if (action === \"hostHello\")\n return (json(response, { data: welcome, tags: [] }), true);\n if (action === \"hostClaim\") {\n if (options.turn && !claimed) {\n claimed = true;\n return (json(response, { data: options.turn(), tags: [] }), true);\n }\n return (\n json(response, { error: { code: \"not_found\", message: \"No turn.\" } }, 404), true\n );\n }\n if (action === \"hostSignals\") return (json(response, { data: [], tags: [] }), true);\n if (action) {\n return (\n json(response, {\n data: {\n id: \"t1\",\n leaseUntil: Date.now() + 60_000,\n token: { token: \"turn-2\", expiresAt: Date.now() + 60_000 },\n },\n tags: [],\n }),\n true\n );\n }\n if (path === \"/_parlor/sync/token\") {\n return (\n json(response, {\n protocol: 1,\n transport: \"sse\",\n endpoint: \"/_parlor/sync/stream\",\n pollIntervalMs: 2_000,\n }),\n true\n );\n }\n if (path === \"/_parlor/sync/stream\") {\n response.writeHead(200, { \"content-type\": \"text/event-stream\" });\n response.write(\n `event: handshake\\ndata: ${JSON.stringify({ capabilities: [], keepAliveMs: 10_000 })}\\n\\n`,\n );\n if (options.wake) {\n const events = [{ version: 1, id: \"e1\", tags: [[\"parlor_chat\", \"t1\"]] }];\n response.write(\n `event: message\\ndata: ${JSON.stringify({ events, cursor: \"1.e1\" })}\\n\\n`,\n );\n }\n return true;\n }\n if (path === \"/_parlor/mcp\") {\n const message = JSON.parse(body) as { id?: number; method: string };\n if (message.id === undefined) return (response.writeHead(202).end(), true);\n const result =\n message.method === \"initialize\"\n ? {\n protocolVersion: \"2025-06-18\",\n capabilities: { tools: {} },\n serverInfo: { name: \"stub\", version: \"1\" },\n }\n : message.method === \"tools/list\"\n ? { tools: [] }\n : { content: [{ type: \"text\", text: \"[]\" }] };\n return (json(response, { jsonrpc: \"2.0\", id: message.id, result }), true);\n }\n return false;\n };\n}\n\nexport const claimedTurn = (\n turnId: string,\n agentId: string,\n context: string,\n text: string,\n) => ({\n id: \"t1\",\n turnId,\n agent: { hostId: \"h-A\", agentId },\n clientId: \"tab\",\n prompt: {\n id: `p-${turnId}`,\n threadId: \"t1\",\n turnId,\n seq: 1,\n at: Date.now(),\n author: \"user\",\n kind: \"prompt\",\n body: { text, agent: { hostId: \"h-A\", agentId } },\n },\n device: undefined,\n context,\n token: { token: \"turn-1\", expiresAt: Date.now() + 60_000 },\n leaseUntil: Date.now() + 60_000,\n});\n\nexport const grantFor = (origin: string, name: string, workspace: string): AppGrant => ({\n appOrigin: origin,\n appName: name,\n grantId: asId(\"grant\", `host:${name}`),\n hostId: asId(\"host\", `h-${name}`),\n account: { userId: asId(\"user\", \"alice\"), name: \"Alice\", email: \"alice@example.com\" },\n workspace,\n createdAt: 1,\n});\n"]}
@@ -1,3 +1,4 @@
1
+ import type { AgentOption, AgentUsage } from "@parlor/chat";
1
2
  import { type AcpStdio, type AgentModes, type StopReason, type ToolKind } from "../acp.js";
2
3
  import type { AgentProfile } from "../agents.js";
3
4
  /**
@@ -13,6 +14,7 @@ export interface FakeAgentScript {
13
14
  readonly close?: boolean | undefined;
14
15
  readonly mcpHttp?: boolean | undefined;
15
16
  readonly modes?: AgentModes | undefined;
17
+ readonly options?: readonly AgentOption[] | undefined;
16
18
  } | undefined;
17
19
  readonly steps: Readonly<Record<string, readonly FakeStep[]>>;
18
20
  /** For a prompt no step names: reply with it. */
@@ -25,6 +27,16 @@ export type FakeStep = {
25
27
  } | {
26
28
  readonly kind: "thought";
27
29
  readonly text: string;
30
+ }
31
+ /** The agent moving itself to another mode, as Claude does off a model without `auto`. */
32
+ | {
33
+ readonly kind: "mode";
34
+ readonly modeId: string;
35
+ }
36
+ /** A rate limit report, as Claude Code sends it on a `usage_update`. */
37
+ | {
38
+ readonly kind: "usage";
39
+ readonly usage: AgentUsage;
28
40
  } | {
29
41
  readonly kind: "tool";
30
42
  readonly title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fake-agent.d.ts","sourceRoot":"","sources":["../../src/testing/fake-agent.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,UAAU,EAKf,KAAK,UAAU,EACf,KAAK,QAAQ,EACd,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,CAAC,EACf;QACE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;KACzC,GACD,SAAS,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC9D,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,MAAM,QAAQ,GAChB;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnD;IACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC9E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AACH,iFAAiF;GAC/E;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC7E;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC9D;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,GACpC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;CAAE,CAAC;AA8GjF,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAiJnF;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAOhF"}
1
+ {"version":3,"file":"fake-agent.d.ts","sourceRoot":"","sources":["../../src/testing/fake-agent.ts"],"names":[],"mappings":"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"}
@@ -121,6 +121,12 @@ export function serveFakeAgent(script, io) {
121
121
  case "thought":
122
122
  await peer.update({ kind: "thought", text: step.text });
123
123
  return undefined;
124
+ case "mode":
125
+ await peer.update({ kind: "mode", modeId: step.modeId });
126
+ return undefined;
127
+ case "usage":
128
+ await peer.update({ kind: "usage", usage: step.usage });
129
+ return undefined;
124
130
  case "tool": {
125
131
  const toolCallId = `tool-${++tools}`;
126
132
  await peer.update({
@@ -207,7 +213,11 @@ export function serveFakeAgent(script, io) {
207
213
  newSession: async (input) => {
208
214
  const id = `fake-session-${sessions.size + 1}`;
209
215
  sessions.set(id, input.mcpServers);
210
- return { id, modes: script.advertise?.modes };
216
+ return {
217
+ id,
218
+ modes: script.advertise?.modes,
219
+ options: script.advertise?.options,
220
+ };
211
221
  },
212
222
  prompt: async (sessionId, blocks, peer) => {
213
223
  const text = blocks.map((block) => block.text).join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"fake-agent.js","sourceRoot":"","sources":["../../src/testing/fake-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\n\nimport {\n type AcpStdio,\n type AgentAsk,\n type AgentAskAnswer,\n type AgentModes,\n type AgentUpdate,\n type PromptBlock,\n serveAcpAgent,\n type StdioMcpServer,\n type StopReason,\n type ToolKind,\n} from \"../acp.js\";\nimport type { AgentProfile } from \"../agents.js\";\n\n/**\n * A scripted agent on the real ACP transport (through `serveAcpAgent`), so the adapter,\n * fold, permission, cancel and MCP proxy paths are tested as Claude Code and Codex drive\n * them. A prompt's text names the step list to play: the whole text, or its last line when\n * the host put its context block first. Every reply chunk goes out prefixed with\n * `[t:<epoch ms>]`, the time it was written.\n */\nexport interface FakeAgentScript {\n readonly advertise?:\n | {\n readonly resume?: boolean | undefined;\n readonly close?: boolean | undefined;\n readonly mcpHttp?: boolean | undefined;\n readonly modes?: AgentModes | undefined;\n }\n | undefined;\n readonly steps: Readonly<Record<string, readonly FakeStep[]>>;\n /** For a prompt no step names: reply with it. */\n readonly fallback?: \"echo\" | undefined;\n}\n\nexport type FakeStep =\n | {\n readonly kind: \"reply\";\n readonly chunks: readonly string[];\n readonly perSecond?: number;\n }\n | { readonly kind: \"thought\"; readonly text: string }\n | {\n readonly kind: \"tool\";\n readonly title: string;\n readonly toolKind: ToolKind;\n readonly mcp?: { readonly server: string; readonly tool: string } | undefined;\n readonly askPermission: boolean;\n /** The permission request's own title, when it differs from the call's. */\n readonly askTitle?: string | undefined;\n }\n /** Calls an app tool through the MCP server the host passed in `session/new`. */\n | { readonly kind: \"call_app\"; readonly tool: string; readonly input: unknown }\n | { readonly kind: \"plan\"; readonly entries: readonly string[] }\n | {\n readonly kind: \"question\";\n readonly message: string;\n readonly schema: Readonly<Record<string, unknown>>;\n }\n | { readonly kind: \"wait_for_cancel\" }\n | { readonly kind: \"exit\"; readonly code: number }\n | { readonly kind: \"stop\"; readonly reason: Exclude<StopReason, \"cancelled\"> };\n\ninterface Peer {\n update(update: AgentUpdate): Promise<void>;\n ask(ask: AgentAsk): Promise<AgentAskAnswer>;\n readonly cancelled: AbortSignal;\n}\n\nconst ALLOW = \"allow\";\nconst REJECT = \"reject\";\n\nconst PERMISSION_OPTIONS = [\n { id: ALLOW, label: \"Allow\", effect: \"allow_once\" },\n { id: REJECT, label: \"Reject\", effect: \"reject_once\" },\n] as const;\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) return resolve();\n const timer = setTimeout(resolve, ms);\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nfunction stepsFor(script: FakeAgentScript, text: string): readonly FakeStep[] {\n const trimmed = text.trim();\n const lastLine = trimmed.split(\"\\n\").at(-1)?.trim() ?? \"\";\n const named = script.steps[trimmed] ?? script.steps[lastLine];\n if (named) return named;\n return [\n {\n kind: \"reply\",\n chunks: [\n script.fallback === \"echo\" ? text : `No step list is named \"${lastLine}\".`,\n ],\n },\n ];\n}\n\nclass McpStdioClient {\n readonly #child: ChildProcessWithoutNullStreams;\n readonly #pending = new Map<number, (message: Record<string, unknown>) => void>();\n readonly #ready: Promise<void>;\n #nextId = 1;\n\n constructor(server: StdioMcpServer) {\n this.#child = spawn(server.command, [...server.args], {\n env: { ...process.env, ...server.env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n createInterface({ input: this.#child.stdout }).on(\"line\", (line) => {\n let message: Record<string, unknown>;\n try {\n message = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return;\n }\n if (typeof message.id === \"number\") this.#pending.get(message.id)?.(message);\n });\n this.#ready = this.request(\"initialize\", {\n protocolVersion: \"2025-06-18\",\n capabilities: {},\n clientInfo: { name: \"fake-agent\", version: \"0.0.0\" },\n }).then(() => this.#send({ jsonrpc: \"2.0\", method: \"notifications/initialized\" }));\n }\n\n #send(message: unknown) {\n this.#child.stdin.write(`${JSON.stringify(message)}\\n`);\n }\n\n request(method: string, params: unknown): Promise<Record<string, unknown>> {\n const id = this.#nextId++;\n return new Promise((resolve) => {\n this.#pending.set(id, (message) => {\n this.#pending.delete(id);\n resolve(message);\n });\n this.#send({ jsonrpc: \"2.0\", id, method, params });\n });\n }\n\n async callTool(name: string, input: unknown): Promise<Record<string, unknown>> {\n await this.#ready;\n return this.request(\"tools/call\", { name, arguments: input });\n }\n\n close() {\n this.#child.kill();\n }\n}\n\nfunction resultText(message: Record<string, unknown>): { text: string; failed: boolean } {\n const error = message.error as { message?: string } | undefined;\n if (error) return { text: error.message ?? \"error\", failed: true };\n const result = message.result as\n | { content?: { type: string; text?: string }[]; isError?: boolean }\n | undefined;\n const text = (result?.content ?? [])\n .flatMap((content) => (content.type === \"text\" && content.text ? [content.text] : []))\n .join(\"\\n\");\n return { text, failed: result?.isError === true };\n}\n\n/** The fixture's entry point: serves `script` on `io` (the process's stdio). */\nexport function serveFakeAgent(script: FakeAgentScript, io: AcpStdio): Promise<void> {\n const sessions = new Map<string, readonly StdioMcpServer[]>();\n const clients = new Map<string, McpStdioClient>();\n let tools = 0;\n\n const permit = async (\n peer: Peer,\n toolCallId: string,\n title: string,\n step: {\n readonly toolKind: ToolKind;\n readonly mcp?: { readonly server: string; readonly tool: string } | undefined;\n },\n ) => {\n const answer = await peer.ask({\n kind: \"permission\",\n toolCallId,\n title,\n toolKind: step.toolKind,\n mcp: step.mcp,\n options: PERMISSION_OPTIONS,\n });\n return answer.kind === \"selected\" && answer.optionId === ALLOW;\n };\n\n const play = async (\n sessionId: string,\n step: FakeStep,\n peer: Peer,\n ): Promise<StopReason | undefined> => {\n switch (step.kind) {\n case \"reply\":\n for (const chunk of step.chunks) {\n if (peer.cancelled.aborted) return \"cancelled\";\n await peer.update({ kind: \"text\", text: `[t:${Date.now()}]${chunk}` });\n if (step.perSecond) await sleep(1000 / step.perSecond, peer.cancelled);\n }\n return undefined;\n case \"thought\":\n await peer.update({ kind: \"thought\", text: step.text });\n return undefined;\n case \"tool\": {\n const toolCallId = `tool-${++tools}`;\n await peer.update({\n kind: \"tool_start\",\n toolCallId,\n title: step.title,\n toolKind: step.toolKind,\n mcp: step.mcp,\n });\n const allowed =\n !step.askPermission ||\n (await permit(peer, toolCallId, step.askTitle ?? step.title, step));\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: allowed ? \"completed\" : \"failed\",\n ...(allowed ? {} : { detail: \"denied\" }),\n });\n return undefined;\n }\n case \"call_app\": {\n const server = sessions.get(sessionId)?.[0];\n if (!server) throw new Error(\"The host gave this session no MCP server.\");\n const toolCallId = `tool-${++tools}`;\n const mcp = { server: server.name, tool: step.tool };\n await peer.update({\n kind: \"tool_start\",\n toolCallId,\n title: step.tool,\n toolKind: \"other\",\n mcp,\n });\n if (!(await permit(peer, toolCallId, step.tool, { toolKind: \"other\", mcp }))) {\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: \"failed\",\n detail: \"denied\",\n });\n return undefined;\n }\n let client = clients.get(sessionId);\n if (!client) {\n client = new McpStdioClient(server);\n clients.set(sessionId, client);\n }\n const { text, failed } = resultText(await client.callTool(step.tool, step.input));\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: failed ? \"failed\" : \"completed\",\n detail: text,\n });\n return undefined;\n }\n case \"plan\":\n await peer.update({\n kind: \"plan\",\n entries: step.entries.map((content) => ({ content, status: \"pending\" })),\n });\n return undefined;\n case \"question\":\n await peer.ask({ kind: \"question\", message: step.message, schema: step.schema });\n return undefined;\n case \"wait_for_cancel\":\n await new Promise<void>((resolve) => {\n if (peer.cancelled.aborted) return resolve();\n peer.cancelled.addEventListener(\"abort\", () => resolve(), { once: true });\n });\n return \"cancelled\";\n case \"exit\":\n return process.exit(step.code);\n case \"stop\":\n return step.reason;\n }\n };\n\n const served = serveAcpAgent(\n {\n capabilities: {\n resume: script.advertise?.resume === true,\n close: script.advertise?.close === true,\n mcpHttp: script.advertise?.mcpHttp === true,\n },\n newSession: async (input) => {\n const id = `fake-session-${sessions.size + 1}`;\n sessions.set(id, input.mcpServers);\n return { id, modes: script.advertise?.modes };\n },\n prompt: async (sessionId, blocks: readonly PromptBlock[], peer) => {\n const text = blocks.map((block) => block.text).join(\"\\n\");\n for (const step of stepsFor(script, text)) {\n if (peer.cancelled.aborted) return \"cancelled\";\n const stop = await play(sessionId, step, peer);\n if (stop) return stop;\n }\n return peer.cancelled.aborted ? \"cancelled\" : \"end_turn\";\n },\n },\n io,\n );\n return served.finally(() => {\n for (const client of clients.values()) client.close();\n });\n}\n\n/** A profile that launches the fixture at `entry` with `scriptFile`. */\nexport function fakeAgentProfile(entry: string, scriptFile: string): AgentProfile {\n return {\n id: \"fake\",\n name: \"Fake agent\",\n launch: { kind: \"native\", command: \"node\", args: [entry, scriptFile] },\n binary: \"node\",\n };\n}\n"]}
1
+ {"version":3,"file":"fake-agent.js","sourceRoot":"","sources":["../../src/testing/fake-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\n\nimport type { AgentOption, AgentUsage } from \"@parlor/chat\";\n\nimport {\n type AcpStdio,\n type AgentAsk,\n type AgentAskAnswer,\n type AgentModes,\n type AgentUpdate,\n type PromptBlock,\n serveAcpAgent,\n type StdioMcpServer,\n type StopReason,\n type ToolKind,\n} from \"../acp.js\";\nimport type { AgentProfile } from \"../agents.js\";\n\n/**\n * A scripted agent on the real ACP transport (through `serveAcpAgent`), so the adapter,\n * fold, permission, cancel and MCP proxy paths are tested as Claude Code and Codex drive\n * them. A prompt's text names the step list to play: the whole text, or its last line when\n * the host put its context block first. Every reply chunk goes out prefixed with\n * `[t:<epoch ms>]`, the time it was written.\n */\nexport interface FakeAgentScript {\n readonly advertise?:\n | {\n readonly resume?: boolean | undefined;\n readonly close?: boolean | undefined;\n readonly mcpHttp?: boolean | undefined;\n readonly modes?: AgentModes | undefined;\n readonly options?: readonly AgentOption[] | undefined;\n }\n | undefined;\n readonly steps: Readonly<Record<string, readonly FakeStep[]>>;\n /** For a prompt no step names: reply with it. */\n readonly fallback?: \"echo\" | undefined;\n}\n\nexport type FakeStep =\n | {\n readonly kind: \"reply\";\n readonly chunks: readonly string[];\n readonly perSecond?: number;\n }\n | { readonly kind: \"thought\"; readonly text: string }\n /** The agent moving itself to another mode, as Claude does off a model without `auto`. */\n | { readonly kind: \"mode\"; readonly modeId: string }\n /** A rate limit report, as Claude Code sends it on a `usage_update`. */\n | { readonly kind: \"usage\"; readonly usage: AgentUsage }\n | {\n readonly kind: \"tool\";\n readonly title: string;\n readonly toolKind: ToolKind;\n readonly mcp?: { readonly server: string; readonly tool: string } | undefined;\n readonly askPermission: boolean;\n /** The permission request's own title, when it differs from the call's. */\n readonly askTitle?: string | undefined;\n }\n /** Calls an app tool through the MCP server the host passed in `session/new`. */\n | { readonly kind: \"call_app\"; readonly tool: string; readonly input: unknown }\n | { readonly kind: \"plan\"; readonly entries: readonly string[] }\n | {\n readonly kind: \"question\";\n readonly message: string;\n readonly schema: Readonly<Record<string, unknown>>;\n }\n | { readonly kind: \"wait_for_cancel\" }\n | { readonly kind: \"exit\"; readonly code: number }\n | { readonly kind: \"stop\"; readonly reason: Exclude<StopReason, \"cancelled\"> };\n\ninterface Peer {\n update(update: AgentUpdate): Promise<void>;\n ask(ask: AgentAsk): Promise<AgentAskAnswer>;\n readonly cancelled: AbortSignal;\n}\n\nconst ALLOW = \"allow\";\nconst REJECT = \"reject\";\n\nconst PERMISSION_OPTIONS = [\n { id: ALLOW, label: \"Allow\", effect: \"allow_once\" },\n { id: REJECT, label: \"Reject\", effect: \"reject_once\" },\n] as const;\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) return resolve();\n const timer = setTimeout(resolve, ms);\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nfunction stepsFor(script: FakeAgentScript, text: string): readonly FakeStep[] {\n const trimmed = text.trim();\n const lastLine = trimmed.split(\"\\n\").at(-1)?.trim() ?? \"\";\n const named = script.steps[trimmed] ?? script.steps[lastLine];\n if (named) return named;\n return [\n {\n kind: \"reply\",\n chunks: [\n script.fallback === \"echo\" ? text : `No step list is named \"${lastLine}\".`,\n ],\n },\n ];\n}\n\nclass McpStdioClient {\n readonly #child: ChildProcessWithoutNullStreams;\n readonly #pending = new Map<number, (message: Record<string, unknown>) => void>();\n readonly #ready: Promise<void>;\n #nextId = 1;\n\n constructor(server: StdioMcpServer) {\n this.#child = spawn(server.command, [...server.args], {\n env: { ...process.env, ...server.env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n createInterface({ input: this.#child.stdout }).on(\"line\", (line) => {\n let message: Record<string, unknown>;\n try {\n message = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return;\n }\n if (typeof message.id === \"number\") this.#pending.get(message.id)?.(message);\n });\n this.#ready = this.request(\"initialize\", {\n protocolVersion: \"2025-06-18\",\n capabilities: {},\n clientInfo: { name: \"fake-agent\", version: \"0.0.0\" },\n }).then(() => this.#send({ jsonrpc: \"2.0\", method: \"notifications/initialized\" }));\n }\n\n #send(message: unknown) {\n this.#child.stdin.write(`${JSON.stringify(message)}\\n`);\n }\n\n request(method: string, params: unknown): Promise<Record<string, unknown>> {\n const id = this.#nextId++;\n return new Promise((resolve) => {\n this.#pending.set(id, (message) => {\n this.#pending.delete(id);\n resolve(message);\n });\n this.#send({ jsonrpc: \"2.0\", id, method, params });\n });\n }\n\n async callTool(name: string, input: unknown): Promise<Record<string, unknown>> {\n await this.#ready;\n return this.request(\"tools/call\", { name, arguments: input });\n }\n\n close() {\n this.#child.kill();\n }\n}\n\nfunction resultText(message: Record<string, unknown>): { text: string; failed: boolean } {\n const error = message.error as { message?: string } | undefined;\n if (error) return { text: error.message ?? \"error\", failed: true };\n const result = message.result as\n | { content?: { type: string; text?: string }[]; isError?: boolean }\n | undefined;\n const text = (result?.content ?? [])\n .flatMap((content) => (content.type === \"text\" && content.text ? [content.text] : []))\n .join(\"\\n\");\n return { text, failed: result?.isError === true };\n}\n\n/** The fixture's entry point: serves `script` on `io` (the process's stdio). */\nexport function serveFakeAgent(script: FakeAgentScript, io: AcpStdio): Promise<void> {\n const sessions = new Map<string, readonly StdioMcpServer[]>();\n const clients = new Map<string, McpStdioClient>();\n let tools = 0;\n\n const permit = async (\n peer: Peer,\n toolCallId: string,\n title: string,\n step: {\n readonly toolKind: ToolKind;\n readonly mcp?: { readonly server: string; readonly tool: string } | undefined;\n },\n ) => {\n const answer = await peer.ask({\n kind: \"permission\",\n toolCallId,\n title,\n toolKind: step.toolKind,\n mcp: step.mcp,\n options: PERMISSION_OPTIONS,\n });\n return answer.kind === \"selected\" && answer.optionId === ALLOW;\n };\n\n const play = async (\n sessionId: string,\n step: FakeStep,\n peer: Peer,\n ): Promise<StopReason | undefined> => {\n switch (step.kind) {\n case \"reply\":\n for (const chunk of step.chunks) {\n if (peer.cancelled.aborted) return \"cancelled\";\n await peer.update({ kind: \"text\", text: `[t:${Date.now()}]${chunk}` });\n if (step.perSecond) await sleep(1000 / step.perSecond, peer.cancelled);\n }\n return undefined;\n case \"thought\":\n await peer.update({ kind: \"thought\", text: step.text });\n return undefined;\n case \"mode\":\n await peer.update({ kind: \"mode\", modeId: step.modeId });\n return undefined;\n case \"usage\":\n await peer.update({ kind: \"usage\", usage: step.usage });\n return undefined;\n case \"tool\": {\n const toolCallId = `tool-${++tools}`;\n await peer.update({\n kind: \"tool_start\",\n toolCallId,\n title: step.title,\n toolKind: step.toolKind,\n mcp: step.mcp,\n });\n const allowed =\n !step.askPermission ||\n (await permit(peer, toolCallId, step.askTitle ?? step.title, step));\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: allowed ? \"completed\" : \"failed\",\n ...(allowed ? {} : { detail: \"denied\" }),\n });\n return undefined;\n }\n case \"call_app\": {\n const server = sessions.get(sessionId)?.[0];\n if (!server) throw new Error(\"The host gave this session no MCP server.\");\n const toolCallId = `tool-${++tools}`;\n const mcp = { server: server.name, tool: step.tool };\n await peer.update({\n kind: \"tool_start\",\n toolCallId,\n title: step.tool,\n toolKind: \"other\",\n mcp,\n });\n if (!(await permit(peer, toolCallId, step.tool, { toolKind: \"other\", mcp }))) {\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: \"failed\",\n detail: \"denied\",\n });\n return undefined;\n }\n let client = clients.get(sessionId);\n if (!client) {\n client = new McpStdioClient(server);\n clients.set(sessionId, client);\n }\n const { text, failed } = resultText(await client.callTool(step.tool, step.input));\n await peer.update({\n kind: \"tool_update\",\n toolCallId,\n status: failed ? \"failed\" : \"completed\",\n detail: text,\n });\n return undefined;\n }\n case \"plan\":\n await peer.update({\n kind: \"plan\",\n entries: step.entries.map((content) => ({ content, status: \"pending\" })),\n });\n return undefined;\n case \"question\":\n await peer.ask({ kind: \"question\", message: step.message, schema: step.schema });\n return undefined;\n case \"wait_for_cancel\":\n await new Promise<void>((resolve) => {\n if (peer.cancelled.aborted) return resolve();\n peer.cancelled.addEventListener(\"abort\", () => resolve(), { once: true });\n });\n return \"cancelled\";\n case \"exit\":\n return process.exit(step.code);\n case \"stop\":\n return step.reason;\n }\n };\n\n const served = serveAcpAgent(\n {\n capabilities: {\n resume: script.advertise?.resume === true,\n close: script.advertise?.close === true,\n mcpHttp: script.advertise?.mcpHttp === true,\n },\n newSession: async (input) => {\n const id = `fake-session-${sessions.size + 1}`;\n sessions.set(id, input.mcpServers);\n return {\n id,\n modes: script.advertise?.modes,\n options: script.advertise?.options,\n };\n },\n prompt: async (sessionId, blocks: readonly PromptBlock[], peer) => {\n const text = blocks.map((block) => block.text).join(\"\\n\");\n for (const step of stepsFor(script, text)) {\n if (peer.cancelled.aborted) return \"cancelled\";\n const stop = await play(sessionId, step, peer);\n if (stop) return stop;\n }\n return peer.cancelled.aborted ? \"cancelled\" : \"end_turn\";\n },\n },\n io,\n );\n return served.finally(() => {\n for (const client of clients.values()) client.close();\n });\n}\n\n/** A profile that launches the fixture at `entry` with `scriptFile`. */\nexport function fakeAgentProfile(entry: string, scriptFile: string): AgentProfile {\n return {\n id: \"fake\",\n name: \"Fake agent\",\n launch: { kind: \"native\", command: \"node\", args: [entry, scriptFile] },\n binary: \"node\",\n };\n}\n"]}
package/dist/turn.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ClaimedTurn, TurnOutcome } from "@parlor/chat";
1
+ import type { AgentOption, AgentUsage, ClaimedTurn, TurnOutcome } from "@parlor/chat";
2
2
  import { type AgentLauncher } from "./acp.js";
3
3
  import type { AgentProfile } from "./agents.js";
4
4
  import type { AppLink } from "./link.js";
@@ -22,6 +22,12 @@ export interface TurnDeps {
22
22
  secret(): Promise<string | undefined>;
23
23
  forget(): Promise<void>;
24
24
  };
25
+ /** What the agent said about itself: the options a prompt may choose, or the account's allowance. */
26
+ readonly learned?: (agentId: string, learned: {
27
+ readonly options: readonly AgentOption[];
28
+ } | {
29
+ readonly usage: AgentUsage;
30
+ }) => void;
25
31
  }
26
32
  export declare const PROMPT_MAX_AGE_MS: number;
27
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAGV,WAAW,EAIX,WAAW,EAEZ,MAAM,cAAc,CAAC;AAItB,OAAO,EAKL,KAAK,aAAa,EAInB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAyC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACtC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;KACzB,CAAC;CACH;AAED,eAAO,MAAM,iBAAiB,QAAc,CAAC;AA0G7C;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,WAAW,CAAC,CAuoBtB"}
1
+ {"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EAGV,WAAW,EAIX,WAAW,EAEZ,MAAM,cAAc,CAAC;AAItB,OAAO,EAKL,KAAK,aAAa,EAInB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAyC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACtC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;KACzB,CAAC;IACF,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,CACjB,OAAO,EAAE,MAAM,EACf,OAAO,EACH;QAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;KAAE,GAC5C;QAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;KAAE,KAC/B,IAAI,CAAC;CACX;AAED,eAAO,MAAM,iBAAiB,QAAc,CAAC;AA0G7C;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,WAAW,CAAC,CAmpBtB"}
package/dist/turn.js CHANGED
@@ -3,6 +3,7 @@ import { answerStatement, checkPairingProof, deviceCommitment, vouchStatement, s
3
3
  import { canonicalBytes, encodeBase64url } from "@parlor/capabilities/encoding";
4
4
  import { ActionError, asId } from "@parlor/core";
5
5
  import { SessionRefused, } from "./acp.js";
6
+ import { optionChanges } from "./agent-options.js";
6
7
  import { finishFold, foldUpdate, startFold } from "./fold.js";
7
8
  import { logEvent } from "./log.js";
8
9
  import { appServerOf, computerName } from "./naming.js";
@@ -372,6 +373,7 @@ export async function runTurn(claimed, deps) {
372
373
  resume: await pool.resumable(grant, threadId, agentId),
373
374
  pinAskMode: true,
374
375
  strictAsk: deps.policy.answers !== "off",
376
+ autoMode: deps.policy.agentMode !== "ask",
375
377
  });
376
378
  live = { session, proxy };
377
379
  pool.remember(grant, threadId, agentId, live);
@@ -387,6 +389,12 @@ export async function runTurn(claimed, deps) {
387
389
  }
388
390
  }
389
391
  const { session } = live;
392
+ deps.learned?.(agentId, { options: session.options });
393
+ for (const change of optionChanges(session.options, claimed.prompt.body.options)) {
394
+ await session.setOption(change.id, change.value).catch((error) => {
395
+ logEvent("option_refused", { agentId, option: change.id, error: String(error) });
396
+ });
397
+ }
390
398
  const exited = connection.exited.then((exit) => {
391
399
  throw new AgentExited(exit);
392
400
  });
@@ -424,8 +432,7 @@ export async function runTurn(claimed, deps) {
424
432
  if (allow)
425
433
  return { kind: "selected", optionId: allow.id };
426
434
  }
427
- const wanted = approvalFor(deps.policy, question);
428
- const approval = wanted === "presence" && device && !device.presenceKey ? "computer" : wanted;
435
+ const approval = approvalFor(deps.policy, question, device);
429
436
  const id = nextId();
430
437
  const dialog = new AbortController();
431
438
  const answered = new Promise((resolve) => pending.set(id, { ask: question, approval, dialog, resolve }));
@@ -464,7 +471,11 @@ export async function runTurn(claimed, deps) {
464
471
  if (waiting.approval === "computer") {
465
472
  return rejected("This question is answered on the computer.");
466
473
  }
467
- if (deps.policy.deviceApproval !== "local" && waiting.approval !== "presence") {
474
+ if (waiting.approval === "device" &&
475
+ item.body.signed?.deviceId !== device?.deviceId) {
476
+ return rejected("This question is answered on the device that asked it.");
477
+ }
478
+ if (deps.policy.deviceApproval !== "local" && waiting.approval === undefined) {
468
479
  return { kind: "accepted" };
469
480
  }
470
481
  const { signed, askId, choice, values } = item.body;
@@ -576,6 +587,8 @@ export async function runTurn(claimed, deps) {
576
587
  let failure;
577
588
  const prompted = session.prompt(blocks, {
578
589
  update: (update) => {
590
+ if (update.kind === "usage")
591
+ deps.learned?.(agentId, { usage: update.usage });
579
592
  const step = foldUpdate(fold, update, appServer);
580
593
  fold = step.fold;
581
594
  if (step.frames.length > 0)