@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
package/dist/fold.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fold.js","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatItemBody, HostFrame, HostItemDraft } from \"@parlor/chat\";\nimport { asId, type ItemId, type TurnId } from \"@parlor/core\";\n\nimport type { AgentUpdate } from \"./acp.js\";\n\ninterface OpenText {\n readonly id: string;\n readonly kind: \"reply\" | \"reasoning\";\n readonly text: string;\n}\n\ninterface OpenTool {\n readonly id: string;\n readonly title: string;\n readonly app: boolean;\n readonly name: string;\n}\n\nexport interface TurnFold {\n readonly turnId: TurnId;\n readonly open: OpenText | undefined;\n readonly tools: ReadonlyMap<string, OpenTool>;\n readonly plan: Extract<AgentUpdate, { kind: \"plan\" }>[\"entries\"] | undefined;\n readonly nextId: number;\n}\n\nexport interface FoldStep {\n readonly fold: TurnFold;\n /** Ephemeral, streamed now. */\n readonly frames: readonly HostFrame[];\n /** Finished, appended durably. */\n readonly closed: readonly HostItemDraft[];\n}\n\nconst MAX_DETAIL_CHARS = 4_000;\n\ntype ToolFrameStatus = \"pending\" | \"running\";\n\nconst itemId = (id: string): ItemId => asId(\"item\", id);\n\nconst entriesOf = (\n entries: Extract<AgentUpdate, { kind: \"plan\" }>[\"entries\"],\n): ChatItemBody<\"plan\">[\"entries\"] => entries.map((entry) => ({ ...entry }));\n\nconst unchanged = (fold: TurnFold): FoldStep => ({ fold, frames: [], closed: [] });\n\nconst planItemOf = (fold: TurnFold) => itemId(`${fold.turnId}.0`);\n\nfunction allocate(fold: TurnFold): { readonly id: string; readonly fold: TurnFold } {\n return {\n id: `${fold.turnId}.${fold.nextId}`,\n fold: { ...fold, nextId: fold.nextId + 1 },\n };\n}\n\nfunction closeText(fold: TurnFold, interrupted = false): FoldStep {\n const open = fold.open;\n if (!open) return unchanged(fold);\n const closed: HostItemDraft =\n open.kind === \"reply\"\n ? {\n id: itemId(open.id),\n kind: \"reply\",\n body: {\n text: open.text,\n ...(interrupted ? { interrupted: true as const } : {}),\n },\n }\n : { id: itemId(open.id), kind: \"reasoning\", body: { text: open.text } };\n return { fold: { ...fold, open: undefined }, frames: [], closed: [closed] };\n}\n\nfunction toolFrame(tool: OpenTool, status: ToolFrameStatus): HostFrame {\n return {\n type: \"item\",\n item: { id: itemId(tool.id), kind: \"tool\", title: tool.title, status },\n };\n}\n\nfunction toolDraft(\n tool: OpenTool,\n status: ChatItemBody<\"tool\">[\"status\"],\n detail: string | undefined,\n): HostItemDraft {\n return {\n id: itemId(tool.id),\n kind: \"tool\",\n body: {\n title: tool.title,\n tool: { kind: tool.app ? \"app\" : \"local\", name: tool.name },\n status,\n ...(detail ? { detail: detail.slice(0, MAX_DETAIL_CHARS) } : {}),\n },\n };\n}\n\nfunction withTool(fold: TurnFold, toolCallId: string, tool: OpenTool | undefined) {\n const tools = new Map(fold.tools);\n if (tool) tools.set(toolCallId, tool);\n else tools.delete(toolCallId);\n return { ...fold, tools };\n}\n\nfunction appendText(fold: TurnFold, kind: OpenText[\"kind\"], text: string): FoldStep {\n if (text === \"\") return unchanged(fold);\n const switched =\n fold.open && fold.open.kind !== kind ? closeText(fold) : unchanged(fold);\n let current = switched.fold;\n let open = current.open;\n if (!open) {\n const next = allocate(current);\n current = next.fold;\n open = { id: next.id, kind, text: \"\" };\n }\n return {\n fold: { ...current, open: { ...open, text: open.text + text } },\n frames: [\n { type: \"delta\", itemId: itemId(open.id), kind, offset: open.text.length, text },\n ],\n closed: switched.closed,\n };\n}\n\nfunction startTool(\n fold: TurnFold,\n update: Extract<AgentUpdate, { kind: \"tool_start\" }>,\n appServer: string,\n): FoldStep {\n const known = fold.tools.get(update.toolCallId);\n if (known) {\n const tool = { ...known, title: update.title || known.title };\n return {\n fold: withTool(fold, update.toolCallId, tool),\n frames: [toolFrame(tool, \"pending\")],\n closed: [],\n };\n }\n const text = closeText(fold);\n const next = allocate(text.fold);\n const tool: OpenTool = {\n id: next.id,\n title: update.title,\n app: update.mcp !== undefined && update.mcp.server === appServer,\n name: update.mcp?.tool ?? update.toolKind,\n };\n return {\n fold: withTool(next.fold, update.toolCallId, tool),\n frames: [toolFrame(tool, \"pending\")],\n closed: text.closed,\n };\n}\n\nfunction updateTool(\n fold: TurnFold,\n update: Extract<AgentUpdate, { kind: \"tool_update\" }>,\n): FoldStep {\n const known = fold.tools.get(update.toolCallId);\n if (!known) return unchanged(fold);\n const tool = { ...known, title: update.title || known.title };\n if (update.status === \"completed\" || update.status === \"failed\") {\n return {\n fold: withTool(fold, update.toolCallId, undefined),\n frames: [],\n closed: [\n toolDraft(tool, update.status === \"completed\" ? \"done\" : \"failed\", update.detail),\n ],\n };\n }\n return {\n fold: withTool(fold, update.toolCallId, tool),\n frames: [toolFrame(tool, update.status === \"in_progress\" ? \"running\" : \"pending\")],\n closed: [],\n };\n}\n\n/** Pure: knows no ACP, HTTP or time. Item ids are `<turnId>.<n>`. */\nexport function startFold(turnId: TurnId): TurnFold {\n return { turnId, open: undefined, tools: new Map(), plan: undefined, nextId: 1 };\n}\n\n/** `appServer` is the MCP server name the host gave the agent for this app. */\nexport function foldUpdate(\n fold: TurnFold,\n update: AgentUpdate,\n appServer: string,\n): FoldStep {\n switch (update.kind) {\n case \"text\":\n return appendText(fold, \"reply\", update.text);\n case \"thought\":\n return appendText(fold, \"reasoning\", update.text);\n case \"tool_start\":\n return startTool(fold, update, appServer);\n case \"tool_update\":\n return updateTool(fold, update);\n case \"plan\":\n return {\n fold: { ...fold, plan: update.entries },\n frames: [\n {\n type: \"item\",\n item: {\n id: planItemOf(fold),\n kind: \"plan\",\n entries: entriesOf(update.entries),\n },\n },\n ],\n closed: [],\n };\n case \"mode\":\n return unchanged(fold);\n }\n}\n\n/** Closes what is open; `interrupted` marks the last reply when the user stopped the turn. */\nexport function finishFold(fold: TurnFold, interrupted: boolean): FoldStep {\n const text = closeText(fold, interrupted);\n const tools = [...fold.tools.values()].map((tool) =>\n toolDraft(tool, interrupted ? \"cancelled\" : \"done\", undefined),\n );\n const plan: HostItemDraft[] = fold.plan\n ? [{ id: planItemOf(fold), kind: \"plan\", body: { entries: entriesOf(fold.plan) } }]\n : [];\n return {\n fold: { ...text.fold, tools: new Map(), plan: undefined },\n frames: [],\n closed: [...text.closed, ...tools, ...plan],\n };\n}\n"]}
1
+ {"version":3,"file":"fold.js","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatItemBody, HostFrame, HostItemDraft } from \"@parlor/chat\";\nimport { asId, type ItemId, type TurnId } from \"@parlor/core\";\n\nimport type { AgentUpdate } from \"./acp.js\";\n\ninterface OpenText {\n readonly id: string;\n readonly kind: \"reply\" | \"reasoning\";\n readonly text: string;\n}\n\ninterface OpenTool {\n readonly id: string;\n readonly title: string;\n readonly app: boolean;\n readonly name: string;\n}\n\nexport interface TurnFold {\n readonly turnId: TurnId;\n readonly open: OpenText | undefined;\n readonly tools: ReadonlyMap<string, OpenTool>;\n readonly plan: Extract<AgentUpdate, { kind: \"plan\" }>[\"entries\"] | undefined;\n readonly nextId: number;\n}\n\nexport interface FoldStep {\n readonly fold: TurnFold;\n /** Ephemeral, streamed now. */\n readonly frames: readonly HostFrame[];\n /** Finished, appended durably. */\n readonly closed: readonly HostItemDraft[];\n}\n\nconst MAX_DETAIL_CHARS = 4_000;\n\ntype ToolFrameStatus = \"pending\" | \"running\";\n\nconst itemId = (id: string): ItemId => asId(\"item\", id);\n\nconst entriesOf = (\n entries: Extract<AgentUpdate, { kind: \"plan\" }>[\"entries\"],\n): ChatItemBody<\"plan\">[\"entries\"] => entries.map((entry) => ({ ...entry }));\n\nconst unchanged = (fold: TurnFold): FoldStep => ({ fold, frames: [], closed: [] });\n\nconst planItemOf = (fold: TurnFold) => itemId(`${fold.turnId}.0`);\n\nfunction allocate(fold: TurnFold): { readonly id: string; readonly fold: TurnFold } {\n return {\n id: `${fold.turnId}.${fold.nextId}`,\n fold: { ...fold, nextId: fold.nextId + 1 },\n };\n}\n\nfunction closeText(fold: TurnFold, interrupted = false): FoldStep {\n const open = fold.open;\n if (!open) return unchanged(fold);\n const closed: HostItemDraft =\n open.kind === \"reply\"\n ? {\n id: itemId(open.id),\n kind: \"reply\",\n body: {\n text: open.text,\n ...(interrupted ? { interrupted: true as const } : {}),\n },\n }\n : { id: itemId(open.id), kind: \"reasoning\", body: { text: open.text } };\n return { fold: { ...fold, open: undefined }, frames: [], closed: [closed] };\n}\n\nfunction toolFrame(tool: OpenTool, status: ToolFrameStatus): HostFrame {\n return {\n type: \"item\",\n item: { id: itemId(tool.id), kind: \"tool\", title: tool.title, status },\n };\n}\n\nfunction toolDraft(\n tool: OpenTool,\n status: ChatItemBody<\"tool\">[\"status\"],\n detail: string | undefined,\n): HostItemDraft {\n return {\n id: itemId(tool.id),\n kind: \"tool\",\n body: {\n title: tool.title,\n tool: { kind: tool.app ? \"app\" : \"local\", name: tool.name },\n status,\n ...(detail ? { detail: detail.slice(0, MAX_DETAIL_CHARS) } : {}),\n },\n };\n}\n\nfunction withTool(fold: TurnFold, toolCallId: string, tool: OpenTool | undefined) {\n const tools = new Map(fold.tools);\n if (tool) tools.set(toolCallId, tool);\n else tools.delete(toolCallId);\n return { ...fold, tools };\n}\n\nfunction appendText(fold: TurnFold, kind: OpenText[\"kind\"], text: string): FoldStep {\n if (text === \"\") return unchanged(fold);\n const switched =\n fold.open && fold.open.kind !== kind ? closeText(fold) : unchanged(fold);\n let current = switched.fold;\n let open = current.open;\n if (!open) {\n const next = allocate(current);\n current = next.fold;\n open = { id: next.id, kind, text: \"\" };\n }\n return {\n fold: { ...current, open: { ...open, text: open.text + text } },\n frames: [\n { type: \"delta\", itemId: itemId(open.id), kind, offset: open.text.length, text },\n ],\n closed: switched.closed,\n };\n}\n\nfunction startTool(\n fold: TurnFold,\n update: Extract<AgentUpdate, { kind: \"tool_start\" }>,\n appServer: string,\n): FoldStep {\n const known = fold.tools.get(update.toolCallId);\n if (known) {\n const tool = { ...known, title: update.title || known.title };\n return {\n fold: withTool(fold, update.toolCallId, tool),\n frames: [toolFrame(tool, \"pending\")],\n closed: [],\n };\n }\n const text = closeText(fold);\n const next = allocate(text.fold);\n const tool: OpenTool = {\n id: next.id,\n title: update.title,\n app: update.mcp !== undefined && update.mcp.server === appServer,\n name: update.mcp?.tool ?? update.toolKind,\n };\n return {\n fold: withTool(next.fold, update.toolCallId, tool),\n frames: [toolFrame(tool, \"pending\")],\n closed: text.closed,\n };\n}\n\nfunction updateTool(\n fold: TurnFold,\n update: Extract<AgentUpdate, { kind: \"tool_update\" }>,\n): FoldStep {\n const known = fold.tools.get(update.toolCallId);\n if (!known) return unchanged(fold);\n const tool = { ...known, title: update.title || known.title };\n if (update.status === \"completed\" || update.status === \"failed\") {\n return {\n fold: withTool(fold, update.toolCallId, undefined),\n frames: [],\n closed: [\n toolDraft(tool, update.status === \"completed\" ? \"done\" : \"failed\", update.detail),\n ],\n };\n }\n return {\n fold: withTool(fold, update.toolCallId, tool),\n frames: [toolFrame(tool, update.status === \"in_progress\" ? \"running\" : \"pending\")],\n closed: [],\n };\n}\n\n/** Pure: knows no ACP, HTTP or time. Item ids are `<turnId>.<n>`. */\nexport function startFold(turnId: TurnId): TurnFold {\n return { turnId, open: undefined, tools: new Map(), plan: undefined, nextId: 1 };\n}\n\n/** `appServer` is the MCP server name the host gave the agent for this app. */\nexport function foldUpdate(\n fold: TurnFold,\n update: AgentUpdate,\n appServer: string,\n): FoldStep {\n switch (update.kind) {\n case \"text\":\n return appendText(fold, \"reply\", update.text);\n case \"thought\":\n return appendText(fold, \"reasoning\", update.text);\n case \"tool_start\":\n return startTool(fold, update, appServer);\n case \"tool_update\":\n return updateTool(fold, update);\n case \"plan\":\n return {\n fold: { ...fold, plan: update.entries },\n frames: [\n {\n type: \"item\",\n item: {\n id: planItemOf(fold),\n kind: \"plan\",\n entries: entriesOf(update.entries),\n },\n },\n ],\n closed: [],\n };\n case \"mode\":\n case \"options\":\n case \"usage\":\n return unchanged(fold);\n }\n}\n\n/** Closes what is open; `interrupted` marks the last reply when the user stopped the turn. */\nexport function finishFold(fold: TurnFold, interrupted: boolean): FoldStep {\n const text = closeText(fold, interrupted);\n const tools = [...fold.tools.values()].map((tool) =>\n toolDraft(tool, interrupted ? \"cancelled\" : \"done\", undefined),\n );\n const plan: HostItemDraft[] = fold.plan\n ? [{ id: planItemOf(fold), kind: \"plan\", body: { entries: entriesOf(fold.plan) } }]\n : [];\n return {\n fold: { ...text.fold, tools: new Map(), plan: undefined },\n frames: [],\n closed: [...text.closed, ...tools, ...plan],\n };\n}\n"]}
package/dist/grants.d.ts CHANGED
@@ -19,6 +19,15 @@ export interface AppGrant {
19
19
  * browser proves it on its first prompt and is trusted without asking, then this is dropped.
20
20
  */
21
21
  readonly pairingSecret?: string | undefined;
22
+ /**
23
+ * Whether the coding agents on this computer may use the app outside its sidebar, as the
24
+ * user chose when connecting, and the name and agents it is registered under once it is.
25
+ */
26
+ readonly agentTools?: {
27
+ readonly on: boolean;
28
+ readonly name?: string | undefined;
29
+ readonly agents?: readonly string[] | undefined;
30
+ } | undefined;
22
31
  }
23
32
  /** One key per app grant in the OS keychain; the server holds only its public half. */
24
33
  export interface HostKeys {
@@ -1 +1 @@
1
- {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAKpD,sFAAsF;AACtF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,gGAAgG;IAChG,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACvB,sEAAsE;IACtE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAChD,sDAAsD;IACtD,MAAM,CACJ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GACjE,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,qEAAqE;IACrE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,IAAI,OAAO,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IACrC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IACtD,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAcvD;AA0JD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAE1D;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAMvD"}
1
+ {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAKpD,sFAAsF;AACtF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,gGAAgG;IAChG,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAChB;QACE,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;KACjD,GACD,SAAS,CAAC;CACf;AAED,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACvB,sEAAsE;IACtE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAChD,sDAAsD;IACtD,MAAM,CACJ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GACjE,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,qEAAqE;IACrE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,IAAI,OAAO,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IACrC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IACtD,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAcvD;AA0JD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAE1D;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAMvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"grants.js","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { promisify } from \"node:util\";\n\nimport type { AccountSummary } from \"@parlor/auth\";\nimport type { GrantId, HostId } from \"@parlor/core\";\n\nimport { hostAssertion } from \"./assertion.js\";\nimport { jsonFile } from \"./json-file.js\";\n\n/** One app this computer is connected to. Its key lives in `HostKeys`, never here. */\nexport interface AppGrant {\n readonly appOrigin: string;\n readonly appName: string;\n readonly grantId: GrantId;\n readonly hostId: HostId;\n readonly account: AccountSummary;\n /** The agents' working directory for this app; only the user changes it, here. */\n readonly workspace: string;\n readonly createdAt: number;\n /** A `parlor dev` grant: in memory only. */\n readonly dev?: true | undefined;\n /** Refused here but not yet revoked by the app: kept only to retry the revoke, never served. */\n readonly declined?: true | undefined;\n /**\n * Handed only to the browser that approved `connect`, through the loopback redirect; that\n * browser proves it on its first prompt and is trusted without asking, then this is dropped.\n */\n readonly pairingSecret?: string | undefined;\n}\n\n/** One key per app grant in the OS keychain; the server holds only its public half. */\nexport interface HostKeys {\n /** A pairing's new key pair, kept only once the pairing completes. */\n create(appOrigin: string): Promise<HostKeyPair>;\n /** A fresh `ParlorHost` assertion for one request. */\n assert(\n appOrigin: string,\n request: { readonly method: \"GET\" | \"POST\"; readonly url: string },\n ): Promise<string>;\n remove(appOrigin: string): Promise<void>;\n}\n\nexport interface HostKeyPair {\n readonly publicKey: JsonWebKey;\n /** RFC 7638, the assertion's `kid`. */\n readonly thumbprint: string;\n sign(payload: Uint8Array): Promise<string>;\n /** Binds the key to the grant the pairing produced and stores it. */\n keep(grantId: GrantId): Promise<void>;\n}\n\nexport interface GrantStore {\n list(): Promise<readonly AppGrant[]>;\n get(appOrigin: string): Promise<AppGrant | undefined>;\n save(grant: AppGrant): Promise<void>;\n remove(appOrigin: string): Promise<void>;\n}\n\n/** `~/.parlor/host/grants.json`, 0600, written atomically. */\nexport function fileGrantStore(file: string): GrantStore {\n const grants = jsonFile<Record<string, AppGrant>>(file);\n return {\n list: async () => Object.values(await grants.read()),\n get: async (appOrigin) => (await grants.read())[appOrigin],\n save: (grant) =>\n grants.change((all) => {\n all[grant.appOrigin] = grant;\n }),\n remove: (appOrigin) =>\n grants.change((all) => {\n delete all[appOrigin];\n }),\n };\n}\n\nconst EC_P256 = { name: \"ECDSA\", namedCurve: \"P-256\" } as const;\nconst ES256 = { name: \"ECDSA\", hash: \"SHA-256\" } as const;\nconst ORIGIN = /^https?:\\/\\/[A-Za-z0-9.\\-[\\]:]+$/;\n\ninterface StoredKey {\n readonly jwk: JsonWebKey;\n readonly kid: string;\n readonly grantId: string;\n}\n\ninterface LoadedKey {\n readonly key: CryptoKey;\n readonly kid: string;\n readonly grantId: string;\n}\n\ninterface SecretStore {\n read(account: string): Promise<string | undefined>;\n write(account: string, secret: string): Promise<void>;\n remove(account: string): Promise<void>;\n}\n\nfunction thumbprintOf({ crv, kty, x, y }: JsonWebKey): string {\n return createHash(\"sha256\")\n .update(JSON.stringify({ crv, kty, x, y }))\n .digest(\"base64url\");\n}\n\nconst signer = (key: CryptoKey) => async (payload: Uint8Array) =>\n Buffer.from(await crypto.subtle.sign(ES256, key, new Uint8Array(payload))).toString(\n \"base64url\",\n );\n\nfunction keysIn(secrets: SecretStore): HostKeys {\n const loaded = new Map<string, Promise<LoadedKey | undefined>>();\n const load = (appOrigin: string) => {\n let entry = loaded.get(appOrigin);\n if (!entry) {\n entry = (async () => {\n const secret = await secrets.read(appOrigin);\n if (secret === undefined) return undefined;\n const stored = JSON.parse(\n Buffer.from(secret, \"base64url\").toString(\"utf8\"),\n ) as StoredKey;\n const key = await crypto.subtle.importKey(\"jwk\", stored.jwk, EC_P256, false, [\n \"sign\",\n ]);\n return { key, kid: stored.kid, grantId: stored.grantId };\n })();\n entry.catch(() => loaded.delete(appOrigin));\n loaded.set(appOrigin, entry);\n }\n return entry;\n };\n return {\n async create(appOrigin) {\n const pair = await crypto.subtle.generateKey(EC_P256, true, [\"sign\", \"verify\"]);\n const exported = await crypto.subtle.exportKey(\"jwk\", pair.publicKey);\n const publicKey: JsonWebKey = {\n kty: exported.kty!,\n crv: exported.crv!,\n x: exported.x!,\n y: exported.y!,\n };\n const thumbprint = thumbprintOf(publicKey);\n return {\n publicKey,\n thumbprint,\n sign: signer(pair.privateKey),\n keep: async (grantId) => {\n const jwk = await crypto.subtle.exportKey(\"jwk\", pair.privateKey);\n const stored: StoredKey = { jwk, kid: thumbprint, grantId };\n await secrets.write(\n appOrigin,\n Buffer.from(JSON.stringify(stored)).toString(\"base64url\"),\n );\n loaded.delete(appOrigin);\n },\n };\n },\n async assert(appOrigin, request) {\n const key = await load(appOrigin);\n if (!key) throw new Error(`This computer holds no key for ${appOrigin}.`);\n return hostAssertion(signer(key.key), key, request);\n },\n async remove(appOrigin) {\n loaded.delete(appOrigin);\n await secrets.remove(appOrigin);\n },\n };\n}\n\nconst run = promisify(execFile);\nconst NOT_FOUND = 44;\n\nfunction macKeychain(service: string): SecretStore {\n const checked = (account: string) => {\n if (!ORIGIN.test(account)) throw new Error(`${account} is not an app origin.`);\n return account;\n };\n return {\n async read(account) {\n try {\n const { stdout } = await run(\"security\", [\n \"find-generic-password\",\n \"-s\",\n service,\n \"-a\",\n checked(account),\n \"-w\",\n ]);\n return stdout.trim();\n } catch (error) {\n if ((error as { code?: unknown }).code === NOT_FOUND) return undefined;\n throw error;\n }\n },\n async write(account, secret) {\n // Through `security -i` on stdin, so the private key never shows in a process list.\n const command = `add-generic-password -U -s ${service} -a ${checked(account)} -w ${secret}\\n`;\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"security\", [\"-i\"], { stdio: [\"pipe\", \"ignore\", \"pipe\"] });\n let stderr = \"\";\n child.stderr.on(\"data\", (chunk: Buffer) => (stderr += chunk.toString()));\n child.once(\"error\", reject);\n child.once(\"exit\", (code) =>\n code === 0 && stderr.trim() === \"\"\n ? resolve()\n : reject(new Error(`The Keychain refused the key: ${stderr.trim() || code}`)),\n );\n child.stdin.end(command);\n });\n if ((await this.read(account)) !== secret) {\n throw new Error(\"The Keychain did not keep the key.\");\n }\n },\n async remove(account) {\n try {\n await run(\"security\", [\n \"delete-generic-password\",\n \"-s\",\n service,\n \"-a\",\n checked(account),\n ]);\n } catch (error) {\n if ((error as { code?: unknown }).code !== NOT_FOUND) throw error;\n }\n },\n };\n}\n\n/** The macOS Keychain; other platforms are refused before pairing (`unsupportedHostPlatform`). */\nexport function keychainHostKeys(service: string): HostKeys {\n return keysIn(macKeychain(service));\n}\n\n/** For `parlor-host dev`: one dev host token for every request, nothing on disk. */\nexport function devHostKeys(hostToken: string): HostKeys {\n return {\n create: () => Promise.reject(new Error(\"A parlor dev server needs no pairing.\")),\n assert: async () => hostToken,\n remove: async () => undefined,\n };\n}\n"]}
1
+ {"version":3,"file":"grants.js","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAKtC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA8D1C,8DAA8D;AAC9D,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,MAAM,GAAG,QAAQ,CAA2B,IAAI,CAAC,CAAC;IACxD,OAAO;QACL,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACpD,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC;QAC1D,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACd,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;YACpB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;QAC/B,CAAC,CAAC;QACJ,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,CACpB,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;YACpB,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC,CAAC;KACL,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,CAAC;AAChE,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAW,CAAC;AAC1D,MAAM,MAAM,GAAG,kCAAkC,CAAC;AAoBlD,SAAS,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAc;IAClD,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;SAC1C,MAAM,CAAC,WAAW,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,GAAc,EAAE,EAAE,CAAC,KAAK,EAAE,OAAmB,EAAE,EAAE,CAC/D,MAAM,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CACjF,WAAW,CACZ,CAAC;AAEJ,SAAS,MAAM,CAAC,OAAoB;IAClC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0C,CAAC;IACjE,MAAM,IAAI,GAAG,CAAC,SAAiB,EAAE,EAAE;QACjC,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE;gBAClB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC7C,IAAI,MAAM,KAAK,SAAS;oBAAE,OAAO,SAAS,CAAC;gBAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CACvB,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CACrC,CAAC;gBACf,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;oBAC3E,MAAM;iBACP,CAAC,CAAC;gBACH,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YAC3D,CAAC,CAAC,EAAE,CAAC;YACL,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,SAAS;YACpB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YAChF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACtE,MAAM,SAAS,GAAe;gBAC5B,GAAG,EAAE,QAAQ,CAAC,GAAI;gBAClB,GAAG,EAAE,QAAQ,CAAC,GAAI;gBAClB,CAAC,EAAE,QAAQ,CAAC,CAAE;gBACd,CAAC,EAAE,QAAQ,CAAC,CAAE;aACf,CAAC;YACF,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;YAC3C,OAAO;gBACL,SAAS;gBACT,UAAU;gBACV,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;gBAC7B,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;oBACtB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;oBAClE,MAAM,MAAM,GAAc,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;oBAC5D,MAAM,OAAO,CAAC,KAAK,CACjB,SAAS,EACT,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAC1D,CAAC;oBACF,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC3B,CAAC;aACF,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO;YAC7B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;YAClC,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,SAAS,GAAG,CAAC,CAAC;YAC1E,OAAO,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QACtD,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,SAAS;YACpB,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACzB,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAChC,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,EAAE;QAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,wBAAwB,CAAC,CAAC;QAC/E,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IACF,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,OAAO;YAChB,IAAI,CAAC;gBACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,UAAU,EAAE;oBACvC,uBAAuB;oBACvB,IAAI;oBACJ,OAAO;oBACP,IAAI;oBACJ,OAAO,CAAC,OAAO,CAAC;oBAChB,IAAI;iBACL,CAAC,CAAC;gBACH,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA4B,CAAC,IAAI,KAAK,SAAS;oBAAE,OAAO,SAAS,CAAC;gBACvE,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM;YACzB,oFAAoF;YACpF,MAAM,OAAO,GAAG,8BAA8B,OAAO,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,MAAM,IAAI,CAAC;YAC9F,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC/E,IAAI,MAAM,GAAG,EAAE,CAAC;gBAChB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;gBACzE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC5B,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAC1B,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;oBAChC,CAAC,CAAC,OAAO,EAAE;oBACX,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAChF,CAAC;gBACF,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,OAAO;YAClB,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,UAAU,EAAE;oBACpB,yBAAyB;oBACzB,IAAI;oBACJ,OAAO;oBACP,IAAI;oBACJ,OAAO,CAAC,OAAO,CAAC;iBACjB,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA4B,CAAC,IAAI,KAAK,SAAS;oBAAE,MAAM,KAAK,CAAC;YACpE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAChF,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS;QAC7B,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS;KAC9B,CAAC;AACJ,CAAC","sourcesContent":["import { execFile, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { promisify } from \"node:util\";\n\nimport type { AccountSummary } from \"@parlor/auth\";\nimport type { GrantId, HostId } from \"@parlor/core\";\n\nimport { hostAssertion } from \"./assertion.js\";\nimport { jsonFile } from \"./json-file.js\";\n\n/** One app this computer is connected to. Its key lives in `HostKeys`, never here. */\nexport interface AppGrant {\n readonly appOrigin: string;\n readonly appName: string;\n readonly grantId: GrantId;\n readonly hostId: HostId;\n readonly account: AccountSummary;\n /** The agents' working directory for this app; only the user changes it, here. */\n readonly workspace: string;\n readonly createdAt: number;\n /** A `parlor dev` grant: in memory only. */\n readonly dev?: true | undefined;\n /** Refused here but not yet revoked by the app: kept only to retry the revoke, never served. */\n readonly declined?: true | undefined;\n /**\n * Handed only to the browser that approved `connect`, through the loopback redirect; that\n * browser proves it on its first prompt and is trusted without asking, then this is dropped.\n */\n readonly pairingSecret?: string | undefined;\n /**\n * Whether the coding agents on this computer may use the app outside its sidebar, as the\n * user chose when connecting, and the name and agents it is registered under once it is.\n */\n readonly agentTools?:\n | {\n readonly on: boolean;\n readonly name?: string | undefined;\n readonly agents?: readonly string[] | undefined;\n }\n | undefined;\n}\n\n/** One key per app grant in the OS keychain; the server holds only its public half. */\nexport interface HostKeys {\n /** A pairing's new key pair, kept only once the pairing completes. */\n create(appOrigin: string): Promise<HostKeyPair>;\n /** A fresh `ParlorHost` assertion for one request. */\n assert(\n appOrigin: string,\n request: { readonly method: \"GET\" | \"POST\"; readonly url: string },\n ): Promise<string>;\n remove(appOrigin: string): Promise<void>;\n}\n\nexport interface HostKeyPair {\n readonly publicKey: JsonWebKey;\n /** RFC 7638, the assertion's `kid`. */\n readonly thumbprint: string;\n sign(payload: Uint8Array): Promise<string>;\n /** Binds the key to the grant the pairing produced and stores it. */\n keep(grantId: GrantId): Promise<void>;\n}\n\nexport interface GrantStore {\n list(): Promise<readonly AppGrant[]>;\n get(appOrigin: string): Promise<AppGrant | undefined>;\n save(grant: AppGrant): Promise<void>;\n remove(appOrigin: string): Promise<void>;\n}\n\n/** `~/.parlor/host/grants.json`, 0600, written atomically. */\nexport function fileGrantStore(file: string): GrantStore {\n const grants = jsonFile<Record<string, AppGrant>>(file);\n return {\n list: async () => Object.values(await grants.read()),\n get: async (appOrigin) => (await grants.read())[appOrigin],\n save: (grant) =>\n grants.change((all) => {\n all[grant.appOrigin] = grant;\n }),\n remove: (appOrigin) =>\n grants.change((all) => {\n delete all[appOrigin];\n }),\n };\n}\n\nconst EC_P256 = { name: \"ECDSA\", namedCurve: \"P-256\" } as const;\nconst ES256 = { name: \"ECDSA\", hash: \"SHA-256\" } as const;\nconst ORIGIN = /^https?:\\/\\/[A-Za-z0-9.\\-[\\]:]+$/;\n\ninterface StoredKey {\n readonly jwk: JsonWebKey;\n readonly kid: string;\n readonly grantId: string;\n}\n\ninterface LoadedKey {\n readonly key: CryptoKey;\n readonly kid: string;\n readonly grantId: string;\n}\n\ninterface SecretStore {\n read(account: string): Promise<string | undefined>;\n write(account: string, secret: string): Promise<void>;\n remove(account: string): Promise<void>;\n}\n\nfunction thumbprintOf({ crv, kty, x, y }: JsonWebKey): string {\n return createHash(\"sha256\")\n .update(JSON.stringify({ crv, kty, x, y }))\n .digest(\"base64url\");\n}\n\nconst signer = (key: CryptoKey) => async (payload: Uint8Array) =>\n Buffer.from(await crypto.subtle.sign(ES256, key, new Uint8Array(payload))).toString(\n \"base64url\",\n );\n\nfunction keysIn(secrets: SecretStore): HostKeys {\n const loaded = new Map<string, Promise<LoadedKey | undefined>>();\n const load = (appOrigin: string) => {\n let entry = loaded.get(appOrigin);\n if (!entry) {\n entry = (async () => {\n const secret = await secrets.read(appOrigin);\n if (secret === undefined) return undefined;\n const stored = JSON.parse(\n Buffer.from(secret, \"base64url\").toString(\"utf8\"),\n ) as StoredKey;\n const key = await crypto.subtle.importKey(\"jwk\", stored.jwk, EC_P256, false, [\n \"sign\",\n ]);\n return { key, kid: stored.kid, grantId: stored.grantId };\n })();\n entry.catch(() => loaded.delete(appOrigin));\n loaded.set(appOrigin, entry);\n }\n return entry;\n };\n return {\n async create(appOrigin) {\n const pair = await crypto.subtle.generateKey(EC_P256, true, [\"sign\", \"verify\"]);\n const exported = await crypto.subtle.exportKey(\"jwk\", pair.publicKey);\n const publicKey: JsonWebKey = {\n kty: exported.kty!,\n crv: exported.crv!,\n x: exported.x!,\n y: exported.y!,\n };\n const thumbprint = thumbprintOf(publicKey);\n return {\n publicKey,\n thumbprint,\n sign: signer(pair.privateKey),\n keep: async (grantId) => {\n const jwk = await crypto.subtle.exportKey(\"jwk\", pair.privateKey);\n const stored: StoredKey = { jwk, kid: thumbprint, grantId };\n await secrets.write(\n appOrigin,\n Buffer.from(JSON.stringify(stored)).toString(\"base64url\"),\n );\n loaded.delete(appOrigin);\n },\n };\n },\n async assert(appOrigin, request) {\n const key = await load(appOrigin);\n if (!key) throw new Error(`This computer holds no key for ${appOrigin}.`);\n return hostAssertion(signer(key.key), key, request);\n },\n async remove(appOrigin) {\n loaded.delete(appOrigin);\n await secrets.remove(appOrigin);\n },\n };\n}\n\nconst run = promisify(execFile);\nconst NOT_FOUND = 44;\n\nfunction macKeychain(service: string): SecretStore {\n const checked = (account: string) => {\n if (!ORIGIN.test(account)) throw new Error(`${account} is not an app origin.`);\n return account;\n };\n return {\n async read(account) {\n try {\n const { stdout } = await run(\"security\", [\n \"find-generic-password\",\n \"-s\",\n service,\n \"-a\",\n checked(account),\n \"-w\",\n ]);\n return stdout.trim();\n } catch (error) {\n if ((error as { code?: unknown }).code === NOT_FOUND) return undefined;\n throw error;\n }\n },\n async write(account, secret) {\n // Through `security -i` on stdin, so the private key never shows in a process list.\n const command = `add-generic-password -U -s ${service} -a ${checked(account)} -w ${secret}\\n`;\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"security\", [\"-i\"], { stdio: [\"pipe\", \"ignore\", \"pipe\"] });\n let stderr = \"\";\n child.stderr.on(\"data\", (chunk: Buffer) => (stderr += chunk.toString()));\n child.once(\"error\", reject);\n child.once(\"exit\", (code) =>\n code === 0 && stderr.trim() === \"\"\n ? resolve()\n : reject(new Error(`The Keychain refused the key: ${stderr.trim() || code}`)),\n );\n child.stdin.end(command);\n });\n if ((await this.read(account)) !== secret) {\n throw new Error(\"The Keychain did not keep the key.\");\n }\n },\n async remove(account) {\n try {\n await run(\"security\", [\n \"delete-generic-password\",\n \"-s\",\n service,\n \"-a\",\n checked(account),\n ]);\n } catch (error) {\n if ((error as { code?: unknown }).code !== NOT_FOUND) throw error;\n }\n },\n };\n}\n\n/** The macOS Keychain; other platforms are refused before pairing (`unsupportedHostPlatform`). */\nexport function keychainHostKeys(service: string): HostKeys {\n return keysIn(macKeychain(service));\n}\n\n/** For `parlor-host dev`: one dev host token for every request, nothing on disk. */\nexport function devHostKeys(hostToken: string): HostKeys {\n return {\n create: () => Promise.reject(new Error(\"A parlor dev server needs no pairing.\")),\n assert: async () => hostToken,\n remove: async () => undefined,\n };\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { AgentInfo, AgentOption, AgentUsage } from "@parlor/chat";
2
+ export interface Learned {
3
+ readonly options?: readonly AgentOption[] | undefined;
4
+ readonly usage?: AgentUsage | undefined;
5
+ }
6
+ export type LearnedNews = {
7
+ readonly options: readonly AgentOption[];
8
+ } | {
9
+ readonly usage: AgentUsage;
10
+ };
11
+ /**
12
+ * What each agent last said about itself, kept across restarts: the allowance only arrives
13
+ * on a turn, and a computer that forgot it would show nothing until the next one.
14
+ */
15
+ export interface LearnedStore {
16
+ load(): Promise<void>;
17
+ /** Whether it changed what `describe` returns. */
18
+ learn(agentId: string, news: LearnedNews): Promise<boolean>;
19
+ /** `agents` with what each said about itself, minus usage windows that rolled over. */
20
+ describe(agents: readonly AgentInfo[], now: number): AgentInfo[];
21
+ }
22
+ export declare function learnedStore(file: string): LearnedStore;
23
+ //# sourceMappingURL=learned.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learned.d.ts","sourceRoot":"","sources":["../src/learned.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAKvE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;CAAE,GAC5C;IAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAEnC;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,kDAAkD;IAClD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;CAClE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAmCvD"}
@@ -0,0 +1,36 @@
1
+ import { mergeUsage } from "./agent-options.js";
2
+ import { jsonFile } from "./json-file.js";
3
+ export function learnedStore(file) {
4
+ const stored = jsonFile(file);
5
+ let known = {};
6
+ return {
7
+ load: async () => {
8
+ known = await stored.read().catch(() => ({}));
9
+ },
10
+ learn: async (agentId, news) => {
11
+ const held = known[agentId] ?? {};
12
+ const next = "options" in news
13
+ ? { ...held, options: news.options }
14
+ : { ...held, usage: mergeUsage(held.usage, news.usage) };
15
+ if (JSON.stringify(next) === JSON.stringify(held))
16
+ return false;
17
+ known = { ...known, [agentId]: next };
18
+ await stored.change((all) => {
19
+ all[agentId] = next;
20
+ });
21
+ return true;
22
+ },
23
+ describe: (agents, now) => agents.map((agent) => {
24
+ const { options, usage } = known[agent.id] ?? {};
25
+ const windows = usage?.windows.filter((window) => window.resetsAt === undefined || window.resetsAt > now);
26
+ return {
27
+ ...agent,
28
+ ...(options && options.length > 0 ? { options } : {}),
29
+ ...(usage && windows && windows.length > 0
30
+ ? { usage: { ...usage, windows } }
31
+ : {}),
32
+ };
33
+ }),
34
+ };
35
+ }
36
+ //# sourceMappingURL=learned.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learned.js","sourceRoot":"","sources":["../src/learned.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAuB1C,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,MAAM,GAAG,QAAQ,CAA0B,IAAI,CAAC,CAAC;IACvD,IAAI,KAAK,GAA4B,EAAE,CAAC;IACxC,OAAO;QACL,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAChD,CAAC;QACD,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;YAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,GACR,SAAS,IAAI,IAAI;gBACf,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;gBACpC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7D,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAAE,OAAO,KAAK,CAAC;YAChE,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;YACtC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC1B,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;YACtB,CAAC,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QACD,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CACxB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACnB,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACjD,MAAM,OAAO,GAAG,KAAK,EAAE,OAAO,CAAC,MAAM,CACnC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,GAAG,GAAG,CACnE,CAAC;YACF,OAAO;gBACL,GAAG,KAAK;gBACR,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrD,GAAG,CAAC,KAAK,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;oBACxC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE;oBAClC,CAAC,CAAC,EAAE,CAAC;aACR,CAAC;QACJ,CAAC,CAAC;KACL,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInfo, AgentOption, AgentUsage } from \"@parlor/chat\";\n\nimport { mergeUsage } from \"./agent-options.js\";\nimport { jsonFile } from \"./json-file.js\";\n\nexport interface Learned {\n readonly options?: readonly AgentOption[] | undefined;\n readonly usage?: AgentUsage | undefined;\n}\n\nexport type LearnedNews =\n | { readonly options: readonly AgentOption[] }\n | { readonly usage: AgentUsage };\n\n/**\n * What each agent last said about itself, kept across restarts: the allowance only arrives\n * on a turn, and a computer that forgot it would show nothing until the next one.\n */\nexport interface LearnedStore {\n load(): Promise<void>;\n /** Whether it changed what `describe` returns. */\n learn(agentId: string, news: LearnedNews): Promise<boolean>;\n /** `agents` with what each said about itself, minus usage windows that rolled over. */\n describe(agents: readonly AgentInfo[], now: number): AgentInfo[];\n}\n\nexport function learnedStore(file: string): LearnedStore {\n const stored = jsonFile<Record<string, Learned>>(file);\n let known: Record<string, Learned> = {};\n return {\n load: async () => {\n known = await stored.read().catch(() => ({}));\n },\n learn: async (agentId, news) => {\n const held = known[agentId] ?? {};\n const next: Learned =\n \"options\" in news\n ? { ...held, options: news.options }\n : { ...held, usage: mergeUsage(held.usage, news.usage) };\n if (JSON.stringify(next) === JSON.stringify(held)) return false;\n known = { ...known, [agentId]: next };\n await stored.change((all) => {\n all[agentId] = next;\n });\n return true;\n },\n describe: (agents, now) =>\n agents.map((agent) => {\n const { options, usage } = known[agent.id] ?? {};\n const windows = usage?.windows.filter(\n (window) => window.resetsAt === undefined || window.resetsAt > now,\n );\n return {\n ...agent,\n ...(options && options.length > 0 ? { options } : {}),\n ...(usage && windows && windows.length > 0\n ? { usage: { ...usage, windows } }\n : {}),\n };\n }),\n };\n}\n"]}
package/dist/main.js CHANGED
@@ -30,7 +30,7 @@ function fail(message) {
30
30
  process.exitCode = 1;
31
31
  }
32
32
  async function host() {
33
- const [{ acpLauncher }, { HOST_PROTOCOL }, { CONTROL_PROTOCOL, connectControl }, { createHostDaemon, HostAlreadyRunning }, { fileGrantStore, keychainHostKeys }, { LOCK_TOKEN_ENV }, { createMcpProxy }, { hostPaths }, { changePolicy, DEFAULT_POLICY, loadPolicy }, { createSessionPool }, { macDialogPrompter, terminalPrompter }, { revokeGrant }, { fileTrustStore }, { ensureService },] = await Promise.all([
33
+ const [{ acpLauncher }, { HOST_PROTOCOL }, { CONTROL_PROTOCOL, connectControl }, { createHostDaemon, HostAlreadyRunning }, { fileGrantStore, keychainHostKeys }, { LOCK_TOKEN_ENV }, { createMcpProxy }, { hostPaths }, { changePolicy, DEFAULT_POLICY, loadPolicy }, { createSessionPool }, { macDialogPrompter, terminalPrompter }, { revokeGrant }, { fileTrustStore }, { ensureService }, { unregisterAgentTools, agentNames }, { detectAgents, loadProfiles }, { agentEnvironment },] = await Promise.all([
34
34
  import("./acp.js"),
35
35
  import("@parlor/chat"),
36
36
  import("./control.js"),
@@ -45,6 +45,9 @@ async function host() {
45
45
  import("./revoke.js"),
46
46
  import("./trust.js"),
47
47
  import("./service.js"),
48
+ import("./agent-tools.js"),
49
+ import("./agents.js"),
50
+ import("./shell-path.js"),
48
51
  ]);
49
52
  const release = {
50
53
  version,
@@ -72,12 +75,25 @@ async function host() {
72
75
  client.close();
73
76
  if (!reply.ok)
74
77
  throw new Error(reply.error);
75
- return;
78
+ return reply.agentTools;
76
79
  }
77
80
  const grants = fileGrantStore(paths.grants);
78
81
  const grant = await grants.get(origin);
79
82
  if (!grant)
80
83
  throw new Error(`This computer is not connected to ${origin}.`);
84
+ const tools = grant.agentTools;
85
+ let removed;
86
+ if (tools?.name && tools.agents?.length) {
87
+ const profiles = await loadProfiles(paths.agents);
88
+ const installed = await detectAgents(profiles);
89
+ const listed = { name: tools.name, agents: tools.agents };
90
+ await unregisterAgentTools({
91
+ profiles: profiles.filter((profile) => installed.some((a) => a.id === profile.id)),
92
+ hostCommand: join(paths.root, "bin", "parlor-host"),
93
+ env: agentEnvironment,
94
+ }, listed);
95
+ removed = { name: tools.name, agents: agentNames(profiles, listed) };
96
+ }
81
97
  const keys = keychainHostKeys(KEYCHAIN_SERVICE);
82
98
  await revokeGrant({
83
99
  appOrigin: origin,
@@ -87,6 +103,7 @@ async function host() {
87
103
  await grants.remove(origin);
88
104
  await keys.remove(origin);
89
105
  await fileTrustStore(paths.trust).forget(origin);
106
+ return removed;
90
107
  };
91
108
  return {
92
109
  changePolicy,
@@ -242,6 +259,7 @@ const connect = defineCommand({
242
259
  platform: platformName(),
243
260
  keys: kit.keychainHostKeys(KEYCHAIN_SERVICE),
244
261
  workspaceRoot: paths.workspaces,
262
+ offerAgentTools: true,
245
263
  channel: {
246
264
  kind: "browser",
247
265
  open: async (url) => {
@@ -291,20 +309,74 @@ const connect = defineCommand({
291
309
  fail(reply.error);
292
310
  return;
293
311
  }
294
- process.stdout.write(`Connected to ${printable(grant.appName)} as ${printable(grant.account.email ?? grant.account.name)}. Your agents are ready in its sidebar.\nNot you? Run \`npx @parlor/agent-host disconnect ${origin}\`.\n`);
312
+ const app = printable(grant.appName);
313
+ process.stdout.write(`Connected to ${app} as ${printable(grant.account.email ?? grant.account.name)}.\n${usage(app, reply.agentTools)}Not you? Run \`npx @parlor/agent-host disconnect ${origin}\`.\n`);
295
314
  if (alreadyConnected) {
296
315
  process.stdout.write("This computer was already connected. To use your agents from another browser or device, open the Agent sidebar there and link it.\n");
316
+ if (grant.agentTools === undefined) {
317
+ process.stdout.write(`To use ${app} from Claude Code and Codex here too, run \`npx @parlor/agent-host tools ${origin}\`.\n`);
318
+ }
297
319
  }
298
320
  },
299
321
  });
322
+ /** The two ways to use the app from this computer, for the lines after "Connected". */
323
+ function usage(app, tools) {
324
+ const sidebar = ` - In ${app}'s agent sidebar.\n`;
325
+ if (!tools)
326
+ return `Use your agents in ${app}'s agent sidebar.\n`;
327
+ return `Use it two ways:\n${sidebar} - In ${tools.agents} on this computer: ask them to "use ${tools.name} to ...". They ask you before their first change in each conversation.\n`;
328
+ }
329
+ const tools = defineCommand({
330
+ meta: {
331
+ name: "tools",
332
+ description: "Let the coding agents on this computer use an app outside its sidebar, or stop them",
333
+ },
334
+ args: {
335
+ url: { type: "positional", required: true, description: "The app's URL" },
336
+ off: { type: "boolean", description: "Stop them; the sidebar keeps working" },
337
+ },
338
+ async run({ args }) {
339
+ const kit = await host();
340
+ const origin = new URL(args.url).origin;
341
+ const client = await kit.connectControl(kit.hostPaths().control);
342
+ if (!client) {
343
+ fail(`Parlor Host is not running. Run \`npx @parlor/agent-host connect ${origin}\` first.`);
344
+ return;
345
+ }
346
+ const reply = await client.send({
347
+ op: "agent_tools",
348
+ appOrigin: origin,
349
+ on: !args.off,
350
+ });
351
+ client.close();
352
+ if (!reply.ok) {
353
+ fail(reply.error);
354
+ return;
355
+ }
356
+ process.stdout.write(args.off
357
+ ? `The coding agents on this computer no longer use ${origin}. Its sidebar still works.\n`
358
+ : reply.agentTools
359
+ ? `${reply.agentTools.agents} can use ${origin}: ask them to "use ${reply.agentTools.name} to ...".\n`
360
+ : "No coding agent here can take it: install Claude Code or Codex, then run this again.\n");
361
+ },
362
+ });
300
363
  const disconnect = defineCommand({
301
364
  meta: { name: "disconnect", description: "Disconnect this computer from a Parlor app" },
302
365
  args: { url: { type: "positional", required: true, description: "The app's URL" } },
303
366
  async run({ args }) {
304
367
  const kit = await host();
305
- await kit
306
- .disconnectFrom(new URL(args.url).origin)
307
- .catch((error) => fail(errorText(error)));
368
+ const origin = new URL(args.url).origin;
369
+ let removed;
370
+ try {
371
+ removed = await kit.disconnectFrom(origin);
372
+ }
373
+ catch (error) {
374
+ fail(errorText(error));
375
+ return;
376
+ }
377
+ process.stdout.write(`Disconnected this computer from ${origin}: its sidebar can no longer reach your agents here.\n${removed
378
+ ? `Removed ${removed.name} from ${removed.agents}, so they cannot change it either.\n`
379
+ : ""}`);
308
380
  },
309
381
  });
310
382
  const status = defineCommand({
@@ -400,6 +472,10 @@ const config = defineCommand({
400
472
  type: "string",
401
473
  description: "Hours a linked device sends before it confirms with Face ID or Touch ID again, or off",
402
474
  },
475
+ "agent-mode": {
476
+ type: "string",
477
+ description: "auto: the agent's own safety review decides and asks only for what it flags; ask: it asks before every action",
478
+ },
403
479
  },
404
480
  async run({ args }) {
405
481
  const kit = await host();
@@ -422,6 +498,14 @@ const config = defineCommand({
422
498
  }
423
499
  change = { ...change, promptPresence: hours === "off" ? "off" : { maxAgeHours } };
424
500
  }
501
+ const agentMode = args["agent-mode"];
502
+ if (agentMode !== undefined) {
503
+ if (agentMode !== "auto" && agentMode !== "ask") {
504
+ fail("--agent-mode takes auto or ask.");
505
+ return;
506
+ }
507
+ change = { ...change, agentMode };
508
+ }
425
509
  const current = Object.keys(change).length === 0
426
510
  ? await kit.loadPolicy(policy, kit.DEFAULT_POLICY)
427
511
  : await kit.changePolicy(policy, kit.DEFAULT_POLICY, change);
@@ -494,9 +578,20 @@ function memoryGrants() {
494
578
  }
495
579
  const mcpProxy = defineCommand({
496
580
  meta: { name: "mcp-proxy", description: "Internal: an agent's stdio MCP server" },
497
- async run() {
498
- const { runMcpProxyProcess } = await import("./mcp-proxy.js");
499
- await runMcpProxyProcess(process.env);
581
+ args: {
582
+ app: {
583
+ type: "string",
584
+ description: "Serve this app to a coding agent outside the sidebar",
585
+ },
586
+ },
587
+ async run({ args }) {
588
+ const [{ runMcpProxyProcess }, { hostPaths }] = await Promise.all([
589
+ import("./mcp-proxy.js"),
590
+ import("./paths.js"),
591
+ ]);
592
+ await runMcpProxyProcess(process.env, args.app
593
+ ? { appOrigin: new URL(args.app).origin, socketPath: hostPaths().proxy }
594
+ : undefined);
500
595
  },
501
596
  });
502
597
  void runMain(defineCommand({
@@ -515,6 +610,7 @@ void runMain(defineCommand({
515
610
  config,
516
611
  dev,
517
612
  "mcp-proxy": mcpProxy,
613
+ tools,
518
614
  },
519
615
  }));
520
616
  //# sourceMappingURL=main.js.map